diff --git a/src/Compilation.zig b/src/Compilation.zig index 1c469a95c79a..fd55db013900 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3081,7 +3081,7 @@ pub fn totalErrorCount(comp: *Compilation) u32 { for (zcu.failed_analysis.keys()) |anal_unit| { const file_index = switch (anal_unit.unwrap()) { .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope, - .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip).file, + .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file, }; if (zcu.fileByIndex(file_index).okToReportErrors()) { total += 1; @@ -3091,11 +3091,13 @@ pub fn totalErrorCount(comp: *Compilation) u32 { } } - if (zcu.intern_pool.global_error_set.getNamesFromMainThread().len > zcu.error_limit) { - total += 1; + for (zcu.failed_codegen.keys()) |nav| { + if (zcu.navFileScope(nav).okToReportErrors()) { + total += 1; + } } - for (zcu.failed_codegen.keys()) |_| { + if (zcu.intern_pool.global_error_set.getNamesFromMainThread().len > zcu.error_limit) { total += 1; } } @@ -3114,7 +3116,13 @@ pub fn totalErrorCount(comp: *Compilation) u32 { } } - return @as(u32, @intCast(total)); + if (comp.module) |zcu| { + if (total == 0 and zcu.transitive_failed_analysis.count() > 0) { + @panic("Transitive analysis errors, but none actually emitted"); + } + } + + return @intCast(total); } /// This function is temporally single-threaded. @@ -3214,7 +3222,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { for (zcu.failed_analysis.keys(), zcu.failed_analysis.values()) |anal_unit, error_msg| { const file_index = switch (anal_unit.unwrap()) { .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope, - .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip).file, + .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file, }; // Skip errors for AnalUnits within files that had a parse failure. @@ -3243,7 +3251,8 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { } } } - for (zcu.failed_codegen.values()) |error_msg| { + for (zcu.failed_codegen.keys(), zcu.failed_codegen.values()) |nav, error_msg| { + if (!zcu.navFileScope(nav).okToReportErrors()) continue; try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references); } for (zcu.failed_exports.values()) |value| { @@ -3608,10 +3617,9 @@ fn performAllTheWorkInner( // Pre-load these things from our single-threaded context since they // will be needed by the worker threads. const path_digest = zcu.filePathDigest(file_index); - const old_root_type = zcu.fileRootType(file_index); const file = zcu.fileByIndex(file_index); comp.thread_pool.spawnWgId(&astgen_wait_group, workerAstGenFile, .{ - comp, file, file_index, path_digest, old_root_type, zir_prog_node, &astgen_wait_group, .root, + comp, file, file_index, path_digest, zir_prog_node, &astgen_wait_group, .root, }); } } @@ -3649,6 +3657,7 @@ fn performAllTheWorkInner( } try reportMultiModuleErrors(pt); try zcu.flushRetryableFailures(); + zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); } @@ -4283,7 +4292,6 @@ fn workerAstGenFile( file: *Zcu.File, file_index: Zcu.File.Index, path_digest: Cache.BinDigest, - old_root_type: InternPool.Index, prog_node: std.Progress.Node, wg: *WaitGroup, src: Zcu.AstGenSrc, @@ -4292,7 +4300,7 @@ fn workerAstGenFile( defer child_prog_node.end(); const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) }; - pt.astGenFile(file, path_digest, old_root_type) catch |err| switch (err) { + pt.astGenFile(file, path_digest) catch |err| switch (err) { error.AnalysisFail => return, else => { file.status = .retryable_failure; @@ -4323,7 +4331,7 @@ fn workerAstGenFile( // `@import("builtin")` is handled specially. if (mem.eql(u8, import_path, "builtin")) continue; - const import_result, const imported_path_digest, const imported_root_type = blk: { + const import_result, const imported_path_digest = blk: { comp.mutex.lock(); defer comp.mutex.unlock(); @@ -4338,8 +4346,7 @@ fn workerAstGenFile( comp.appendFileSystemInput(fsi, res.file.mod.root, res.file.sub_file_path) catch continue; }; const imported_path_digest = pt.zcu.filePathDigest(res.file_index); - const imported_root_type = pt.zcu.fileRootType(res.file_index); - break :blk .{ res, imported_path_digest, imported_root_type }; + break :blk .{ res, imported_path_digest }; }; if (import_result.is_new) { log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{ @@ -4350,7 +4357,7 @@ fn workerAstGenFile( .import_tok = item.data.token, } }; comp.thread_pool.spawnWgId(wg, workerAstGenFile, .{ - comp, import_result.file, import_result.file_index, imported_path_digest, imported_root_type, prog_node, wg, sub_src, + comp, import_result.file, import_result.file_index, imported_path_digest, prog_node, wg, sub_src, }); } } @@ -6443,7 +6450,8 @@ fn buildOutputFromZig( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); - assert(out.* == null); + // Under incremental compilation, `out` may already be populated from a prior update. + assert(out.* == null or comp.incremental); out.* = try sub_compilation.toCrtFile(); } diff --git a/src/InternPool.zig b/src/InternPool.zig index 571668772bd4..91a58e10e780 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -65,19 +65,49 @@ pub const single_threaded = builtin.single_threaded or !want_multi_threaded; pub const TrackedInst = extern struct { file: FileIndex, inst: Zir.Inst.Index, - comptime { - // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`. - assert(@sizeOf(@This()) == @sizeOf(FileIndex) + @sizeOf(Zir.Inst.Index)); - } + + pub const MaybeLost = extern struct { + file: FileIndex, + inst: ZirIndex, + pub const ZirIndex = enum(u32) { + /// Tracking failed for this ZIR instruction. Uses of it should fail. + lost = std.math.maxInt(u32), + _, + pub fn unwrap(inst: ZirIndex) ?Zir.Inst.Index { + return switch (inst) { + .lost => null, + _ => @enumFromInt(@intFromEnum(inst)), + }; + } + pub fn wrap(inst: Zir.Inst.Index) ZirIndex { + return @enumFromInt(@intFromEnum(inst)); + } + }; + comptime { + // The fields should be tightly packed. See also serialiation logic in `Compilation.saveState`. + assert(@sizeOf(@This()) == @sizeOf(FileIndex) + @sizeOf(ZirIndex)); + } + }; + pub const Index = enum(u32) { _, - pub fn resolveFull(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) TrackedInst { + pub fn resolveFull(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) ?TrackedInst { const tracked_inst_unwrapped = tracked_inst_index.unwrap(ip); const tracked_insts = ip.getLocalShared(tracked_inst_unwrapped.tid).tracked_insts.acquire(); - return tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index]; + const maybe_lost = tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index]; + return .{ + .file = maybe_lost.file, + .inst = maybe_lost.inst.unwrap() orelse return null, + }; } - pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) Zir.Inst.Index { - return i.resolveFull(ip).inst; + pub fn resolveFile(tracked_inst_index: TrackedInst.Index, ip: *const InternPool) FileIndex { + const tracked_inst_unwrapped = tracked_inst_index.unwrap(ip); + const tracked_insts = ip.getLocalShared(tracked_inst_unwrapped.tid).tracked_insts.acquire(); + const maybe_lost = tracked_insts.view().items(.@"0")[tracked_inst_unwrapped.index]; + return maybe_lost.file; + } + pub fn resolve(i: TrackedInst.Index, ip: *const InternPool) ?Zir.Inst.Index { + return (i.resolveFull(ip) orelse return null).inst; } pub fn toOptional(i: TrackedInst.Index) Optional { @@ -120,7 +150,11 @@ pub fn trackZir( tid: Zcu.PerThread.Id, key: TrackedInst, ) Allocator.Error!TrackedInst.Index { - const full_hash = Hash.hash(0, std.mem.asBytes(&key)); + const maybe_lost_key: TrackedInst.MaybeLost = .{ + .file = key.file, + .inst = TrackedInst.MaybeLost.ZirIndex.wrap(key.inst), + }; + const full_hash = Hash.hash(0, std.mem.asBytes(&maybe_lost_key)); const hash: u32 = @truncate(full_hash >> 32); const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; var map = shard.shared.tracked_inst_map.acquire(); @@ -132,12 +166,11 @@ pub fn trackZir( const entry = &map.entries[map_index]; const index = entry.acquire().unwrap() orelse break; if (entry.hash != hash) continue; - if (std.meta.eql(index.resolveFull(ip), key)) return index; + if (std.meta.eql(index.resolveFull(ip) orelse continue, key)) return index; } shard.mutate.tracked_inst_map.mutex.lock(); defer shard.mutate.tracked_inst_map.mutex.unlock(); if (map.entries != shard.shared.tracked_inst_map.entries) { - shard.mutate.tracked_inst_map.len += 1; map = shard.shared.tracked_inst_map; map_mask = map.header().mask(); map_index = hash; @@ -147,7 +180,7 @@ pub fn trackZir( const entry = &map.entries[map_index]; const index = entry.acquire().unwrap() orelse break; if (entry.hash != hash) continue; - if (std.meta.eql(index.resolveFull(ip), key)) return index; + if (std.meta.eql(index.resolveFull(ip) orelse continue, key)) return index; } defer shard.mutate.tracked_inst_map.len += 1; const local = ip.getLocal(tid); @@ -161,7 +194,7 @@ pub fn trackZir( .tid = tid, .index = list.mutate.len, }).wrap(ip); - list.appendAssumeCapacity(.{key}); + list.appendAssumeCapacity(.{maybe_lost_key}); entry.release(index.toOptional()); return index; } @@ -205,12 +238,91 @@ pub fn trackZir( .tid = tid, .index = list.mutate.len, }).wrap(ip); - list.appendAssumeCapacity(.{key}); + list.appendAssumeCapacity(.{maybe_lost_key}); map.entries[map_index] = .{ .value = index.toOptional(), .hash = hash }; shard.shared.tracked_inst_map.release(new_map); return index; } +pub fn rehashTrackedInsts( + ip: *InternPool, + gpa: Allocator, + /// TODO: maybe don't take this? it doesn't actually matter, only one thread is running at this point + tid: Zcu.PerThread.Id, +) Allocator.Error!void { + // TODO: this function doesn't handle OOM well. What should it do? + // Indeed, what should anyone do when they run out of memory? + + // We don't lock anything, as this function assumes that no other thread is + // accessing `tracked_insts`. This is necessary because we're going to be + // iterating the `TrackedInst`s in each `Local`, so we have to know that + // none will be added as we work. + + // Figure out how big each shard need to be and store it in its mutate `len`. + for (ip.shards) |*shard| shard.mutate.tracked_inst_map.len = 0; + for (ip.locals) |*local| { + // `getMutableTrackedInsts` is okay only because no other thread is currently active. + // We need the `mutate` for the len. + for (local.getMutableTrackedInsts(gpa).viewAllowEmpty().items(.@"0")) |tracked_inst| { + if (tracked_inst.inst == .lost) continue; // we can ignore this one! + const full_hash = Hash.hash(0, std.mem.asBytes(&tracked_inst)); + const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; + shard.mutate.tracked_inst_map.len += 1; + } + } + + const Map = Shard.Map(TrackedInst.Index.Optional); + + const arena_state = &ip.getLocal(tid).mutate.arena; + + // We know how big each shard must be, so ensure we have the capacity we need. + for (ip.shards) |*shard| { + const want_capacity = std.math.ceilPowerOfTwo(u32, shard.mutate.tracked_inst_map.len * 5 / 3) catch unreachable; + const have_capacity = shard.shared.tracked_inst_map.header().capacity; // no acquire because we hold the mutex + if (have_capacity >= want_capacity) { + @memset(shard.shared.tracked_inst_map.entries[0..have_capacity], .{ .value = .none, .hash = undefined }); + continue; + } + var arena = arena_state.promote(gpa); + defer arena_state.* = arena.state; + const new_map_buf = try arena.allocator().alignedAlloc( + u8, + Map.alignment, + Map.entries_offset + want_capacity * @sizeOf(Map.Entry), + ); + const new_map: Map = .{ .entries = @ptrCast(new_map_buf[Map.entries_offset..].ptr) }; + new_map.header().* = .{ .capacity = want_capacity }; + @memset(new_map.entries[0..want_capacity], .{ .value = .none, .hash = undefined }); + shard.shared.tracked_inst_map.release(new_map); + } + + // Now, actually insert the items. + for (ip.locals, 0..) |*local, local_tid| { + // `getMutableTrackedInsts` is okay only because no other thread is currently active. + // We need the `mutate` for the len. + for (local.getMutableTrackedInsts(gpa).viewAllowEmpty().items(.@"0"), 0..) |tracked_inst, local_inst_index| { + if (tracked_inst.inst == .lost) continue; // we can ignore this one! + const full_hash = Hash.hash(0, std.mem.asBytes(&tracked_inst)); + const hash: u32 = @truncate(full_hash >> 32); + const shard = &ip.shards[@intCast(full_hash & (ip.shards.len - 1))]; + const map = shard.shared.tracked_inst_map; // no acquire because we hold the mutex + const map_mask = map.header().mask(); + var map_index = hash; + const entry = while (true) : (map_index += 1) { + map_index &= map_mask; + const entry = &map.entries[map_index]; + if (entry.acquire() == .none) break entry; + }; + const index = TrackedInst.Index.Unwrapped.wrap(.{ + .tid = @enumFromInt(local_tid), + .index = @intCast(local_inst_index), + }, ip); + entry.hash = hash; + entry.release(index.toOptional()); + } + } +} + /// Analysis Unit. Represents a single entity which undergoes semantic analysis. /// This is either a `Cau` or a runtime function. /// The LSB is used as a tag bit. @@ -728,7 +840,7 @@ const Local = struct { else => @compileError("unsupported host"), }; const Strings = List(struct { u8 }); - const TrackedInsts = List(struct { TrackedInst }); + const TrackedInsts = List(struct { TrackedInst.MaybeLost }); const Maps = List(struct { FieldMap }); const Caus = List(struct { Cau }); const Navs = List(Nav.Repr); @@ -959,6 +1071,14 @@ const Local = struct { mutable.list.release(new_list); } + pub fn viewAllowEmpty(mutable: Mutable) View { + const capacity = mutable.list.header().capacity; + return .{ + .bytes = mutable.list.bytes, + .len = mutable.mutate.len, + .capacity = capacity, + }; + } pub fn view(mutable: Mutable) View { const capacity = mutable.list.header().capacity; assert(capacity > 0); // optimizes `MultiArrayList.Slice.items` @@ -996,7 +1116,6 @@ const Local = struct { fn header(list: ListSelf) *Header { return @ptrFromInt(@intFromPtr(list.bytes) - bytes_offset); } - pub fn view(list: ListSelf) View { const capacity = list.header().capacity; assert(capacity > 0); // optimizes `MultiArrayList.Slice.items` @@ -11000,7 +11119,6 @@ pub fn getOrPutTrailingString( shard.mutate.string_map.mutex.lock(); defer shard.mutate.string_map.mutex.unlock(); if (map.entries != shard.shared.string_map.entries) { - shard.mutate.string_map.len += 1; map = shard.shared.string_map; map_mask = map.header().mask(); map_index = hash; diff --git a/src/Sema.zig b/src/Sema.zig index d891995fd0a2..84999c8a86b2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -999,7 +999,7 @@ fn analyzeBodyInner( // The hashmap lookup in here is a little expensive, and LLVM fails to optimize it away. if (build_options.enable_logging) { std.log.scoped(.sema_zir).debug("sema ZIR {s} %{d}", .{ sub_file_path: { - const file_index = block.src_base_inst.resolveFull(&zcu.intern_pool).file; + const file_index = block.src_base_inst.resolveFile(&zcu.intern_pool); const file = zcu.fileByIndex(file_index); break :sub_file_path file.sub_file_path; }, inst }); @@ -2873,7 +2873,7 @@ fn createTypeName( .anon => {}, // handled after switch .parent => return block.type_name_ctx, .func => func_strat: { - const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip)); + const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip) orelse return error.AnalysisFail); const zir_tags = sema.code.instructions.items(.tag); var buf: std.ArrayListUnmanaged(u8) = .{}; @@ -5487,7 +5487,7 @@ fn failWithBadMemberAccess( .Enum => "enum", else => unreachable, }; - if (agg_ty.typeDeclInst(zcu)) |inst| if (inst.resolve(ip) == .main_struct_inst) { + if (agg_ty.typeDeclInst(zcu)) |inst| if ((inst.resolve(ip) orelse return error.AnalysisFail) == .main_struct_inst) { return sema.fail(block, field_src, "root struct of file '{}' has no member named '{}'", .{ agg_ty.fmt(pt), field_name.fmt(ip), }); @@ -6041,8 +6041,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); const path_digest = zcu.filePathDigest(result.file_index); - const old_root_type = zcu.fileRootType(result.file_index); - pt.astGenFile(result.file, path_digest, old_root_type) catch |err| + pt.astGenFile(result.file, path_digest) catch |err| return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); // TODO: register some kind of dependency on the file. @@ -7778,7 +7777,7 @@ fn analyzeCall( // the AIR instructions of the callsite. The callee could be a generic function // which means its parameter type expressions must be resolved in order and used // to successively coerce the arguments. - const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip)); + const fn_info = ics.callee().code.getFnInfo(module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); try ics.callee().inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); var arg_i: u32 = 0; @@ -7823,7 +7822,7 @@ fn analyzeCall( // each of the parameters, resolving the return type and providing it to the child // `Sema` so that it can be used for the `ret_ptr` instruction. const ret_ty_inst = if (fn_info.ret_ty_body.len != 0) - try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip)) + try sema.resolveInlineBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst.resolve(ip) orelse return error.AnalysisFail) else try sema.resolveInst(fn_info.ret_ty_ref); const ret_ty_src: LazySrcLoc = .{ .base_node_inst = module_fn.zir_body_inst, .offset = .{ .node_offset_fn_type_ret_ty = 0 } }; @@ -8210,7 +8209,7 @@ fn instantiateGenericCall( const fn_nav = ip.getNav(generic_owner_func.owner_nav); const fn_cau = ip.getCau(fn_nav.analysis_owner.unwrap().?); const fn_zir = zcu.namespacePtr(fn_cau.namespace).fileScope(zcu).zir; - const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip)); + const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail); const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count()); @memset(comptime_args, .none); @@ -9416,7 +9415,7 @@ fn zirFunc( break :cau generic_owner_nav.analysis_owner.unwrap().?; } else sema.owner.unwrap().cau; const fn_is_exported = exported: { - const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip); + const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail; const zir_decl = sema.code.getDeclaration(decl_inst)[0]; break :exported zir_decl.flags.is_export; }; @@ -26125,7 +26124,7 @@ fn zirVarExtended( const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); const decl_inst, const decl_bodies = decl: { - const decl_inst = sema.getOwnerCauDeclInst().resolve(ip); + const decl_inst = sema.getOwnerCauDeclInst().resolve(ip) orelse return error.AnalysisFail; const zir_decl, const extra_end = sema.code.getDeclaration(decl_inst); break :decl .{ decl_inst, zir_decl.getBodies(extra_end, sema.code) }; }; @@ -26354,7 +26353,7 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A break :decl_inst cau.zir_index; } else sema.getOwnerCauDeclInst(); // not an instantiation so we're analyzing a function declaration Cau - const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&mod.intern_pool))[0]; + const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&mod.intern_pool) orelse return error.AnalysisFail)[0]; if (zir_decl.flags.is_export) { break :cc .C; } @@ -35505,7 +35504,7 @@ fn semaBackingIntType(pt: Zcu.PerThread, struct_type: InternPool.LoadedStructTyp break :blk accumulator; }; - const zir_index = struct_type.zir_index.unwrap().?.resolve(ip); + const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail; const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .struct_decl); const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); @@ -36120,7 +36119,7 @@ fn semaStructFields( const cau_index = struct_type.cau.unwrap().?; const namespace_index = ip.getCau(cau_index).namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; - const zir_index = struct_type.zir_index.unwrap().?.resolve(ip); + const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail; const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); @@ -36343,7 +36342,7 @@ fn semaStructFieldInits( const cau_index = struct_type.cau.unwrap().?; const namespace_index = ip.getCau(cau_index).namespace; const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir; - const zir_index = struct_type.zir_index.unwrap().?.resolve(ip); + const zir_index = struct_type.zir_index.unwrap().?.resolve(ip) orelse return error.AnalysisFail; const fields_len, const small, var extra_index = structZirInfo(zir, zir_index); var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa); @@ -36477,7 +36476,7 @@ fn semaUnionFields(pt: Zcu.PerThread, arena: Allocator, union_ty: InternPool.Ind const ip = &zcu.intern_pool; const cau_index = union_type.cau; const zir = zcu.namespacePtr(union_type.namespace).fileScope(zcu).zir; - const zir_index = union_type.zir_index.resolve(ip); + const zir_index = union_type.zir_index.resolve(ip) orelse return error.AnalysisFail; const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended; assert(extended.opcode == .union_decl); const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small); diff --git a/src/Type.zig b/src/Type.zig index 2e3a493f18ba..c113e0734e48 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -3437,7 +3437,7 @@ pub fn typeDeclSrcLine(ty: Type, zcu: *Zcu) ?u32 { }, else => return null, }; - const info = tracked.resolveFull(&zcu.intern_pool); + const info = tracked.resolveFull(&zcu.intern_pool) orelse return null; const file = zcu.fileByIndex(info.file); assert(file.zir_loaded); const zir = file.zir; diff --git a/src/Zcu.zig b/src/Zcu.zig index da78e20cf30e..d0889ea62c01 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -162,12 +162,6 @@ outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, /// Such `AnalUnit`s are ready for immediate re-analysis. /// See `findOutdatedToAnalyze` for details. outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .{}, -/// This contains a set of struct types whose corresponding `Cau` may not be in -/// `outdated`, but are the root types of files which have updated source and -/// thus must be re-analyzed. If such a type is only in this set, the struct type -/// index may be preserved (only the namespace might change). If its owned `Cau` -/// is also outdated, the struct type index must be recreated. -outdated_file_root: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .{}, /// This contains a list of AnalUnit whose analysis or codegen failed, but the /// failure was something like running out of disk space, and trying again may /// succeed. On the next update, we will flush this list, marking all members of @@ -2025,7 +2019,7 @@ pub const LazySrcLoc = struct { pub fn resolveBaseNode(base_node_inst: InternPool.TrackedInst.Index, zcu: *Zcu) struct { *File, Ast.Node.Index } { const ip = &zcu.intern_pool; const file_index, const zir_inst = inst: { - const info = base_node_inst.resolveFull(ip); + const info = base_node_inst.resolveFull(ip) orelse @panic("TODO: resolve source location relative to lost inst"); break :inst .{ info.file, info.inst }; }; const file = zcu.fileByIndex(file_index); @@ -2148,7 +2142,6 @@ pub fn deinit(zcu: *Zcu) void { zcu.potentially_outdated.deinit(gpa); zcu.outdated.deinit(gpa); zcu.outdated_ready.deinit(gpa); - zcu.outdated_file_root.deinit(gpa); zcu.retryable_failures.deinit(gpa); zcu.test_functions.deinit(gpa); @@ -2355,8 +2348,6 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUni pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { if (!zcu.comp.incremental) return null; - if (true) @panic("TODO: findOutdatedToAnalyze"); - if (zcu.outdated.count() == 0 and zcu.potentially_outdated.count() == 0) { log.debug("findOutdatedToAnalyze: no outdated depender", .{}); return null; @@ -2381,87 +2372,57 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { return zcu.outdated_ready.keys()[0]; } - // Next, we will see if there is any outdated file root which was not in - // `outdated`. This set will be small (number of files changed in this - // update), so it's alright for us to just iterate here. - for (zcu.outdated_file_root.keys()) |file_decl| { - const decl_depender = AnalUnit.wrap(.{ .decl = file_decl }); - if (zcu.outdated.contains(decl_depender)) { - // Since we didn't hit this in the first loop, this Decl must have - // pending dependencies, so is ineligible. - continue; - } - if (zcu.potentially_outdated.contains(decl_depender)) { - // This Decl's struct may or may not need to be recreated depending - // on whether it is outdated. If we analyzed it now, we would have - // to assume it was outdated and recreate it! - continue; - } - log.debug("findOutdatedToAnalyze: outdated file root decl '{d}'", .{file_decl}); - return decl_depender; - } - - // There is no single AnalUnit which is ready for re-analysis. Instead, we - // must assume that some Decl with PO dependencies is outdated - e.g. in the - // above example we arbitrarily pick one of A or B. We should select a Decl, - // since a Decl is definitely responsible for the loop in the dependency - // graph (since you can't depend on a runtime function analysis!). + // There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some + // Cau with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of + // A or B. We should select a Cau, since a Cau is definitely responsible for the loop in the + // dependency graph (since IES dependencies can't have loops). We should also, of course, not + // select a Cau owned by a `comptime` declaration, since you can't depend on those! - // The choice of this Decl could have a big impact on how much total - // analysis we perform, since if analysis concludes its tyval is unchanged, - // then other PO AnalUnit may be resolved as up-to-date. To hopefully avoid - // doing too much work, let's find a Decl which the most things depend on - - // the idea is that this will resolve a lot of loops (but this is only a - // heuristic). + // The choice of this Cau could have a big impact on how much total analysis we perform, since + // if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit + // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a Decl + // which the most things depend on - the idea is that this will resolve a lot of loops (but this + // is only a heuristic). log.debug("findOutdatedToAnalyze: no trivial ready, using heuristic; {d} outdated, {d} PO", .{ zcu.outdated.count(), zcu.potentially_outdated.count(), }); - const Decl = {}; - - var chosen_decl_idx: ?Decl.Index = null; - var chosen_decl_dependers: u32 = undefined; - - for (zcu.outdated.keys()) |depender| { - const decl_index = switch (depender.unwrap()) { - .decl => |d| d, - .func => continue, - }; - - var n: u32 = 0; - var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index }); - while (it.next()) |_| n += 1; + const ip = &zcu.intern_pool; - if (chosen_decl_idx == null or n > chosen_decl_dependers) { - chosen_decl_idx = decl_index; - chosen_decl_dependers = n; - } - } + var chosen_cau: ?InternPool.Cau.Index = null; + var chosen_cau_dependers: u32 = undefined; - for (zcu.potentially_outdated.keys()) |depender| { - const decl_index = switch (depender.unwrap()) { - .decl => |d| d, - .func => continue, - }; + inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| { + for (outdated_units) |unit| { + const cau = switch (unit.unwrap()) { + .cau => |cau| cau, + .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice + }; + const cau_owner = ip.getCau(cau).owner; - var n: u32 = 0; - var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index }); - while (it.next()) |_| n += 1; + var n: u32 = 0; + var it = ip.dependencyIterator(switch (cau_owner.unwrap()) { + .none => continue, // there can be no dependencies on this `Cau` so it is a terrible choice + .type => |ty| .{ .interned = ty }, + .nav => |nav| .{ .nav_val = nav }, + }); + while (it.next()) |_| n += 1; - if (chosen_decl_idx == null or n > chosen_decl_dependers) { - chosen_decl_idx = decl_index; - chosen_decl_dependers = n; + if (chosen_cau == null or n > chosen_cau_dependers) { + chosen_cau = cau; + chosen_cau_dependers = n; + } } } - log.debug("findOutdatedToAnalyze: heuristic returned Decl {d} ({d} dependers)", .{ - chosen_decl_idx.?, - chosen_decl_dependers, + log.debug("findOutdatedToAnalyze: heuristic returned Cau {d} ({d} dependers)", .{ + @intFromEnum(chosen_cau.?), + chosen_cau_dependers, }); - return AnalUnit.wrap(.{ .decl = chosen_decl_idx.? }); + return AnalUnit.wrap(.{ .cau = chosen_cau.? }); } /// During an incremental update, before semantic analysis, call this to flush all values from @@ -2583,7 +2544,7 @@ pub fn mapOldZirToNew( break :inst unnamed_tests.items[unnamed_test_idx]; }, _ => inst: { - const name_nts = new_decl.name.toString(old_zir).?; + const name_nts = new_decl.name.toString(new_zir).?; const name = new_zir.nullTerminatedString(name_nts); if (new_decl.name.isNamedTest(new_zir)) { break :inst named_tests.get(name) orelse continue; @@ -3093,7 +3054,7 @@ pub fn navSrcLoc(zcu: *const Zcu, nav_index: InternPool.Nav.Index) LazySrcLoc { pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 { const ip = &zcu.intern_pool; - const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip); + const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip).?; const zir = zcu.fileByIndex(inst_info.file).zir; const inst = zir.instructions.get(@intFromEnum(inst_info.inst)); assert(inst.tag == .declaration); @@ -3106,7 +3067,7 @@ pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value { pub fn navFileScopeIndex(zcu: *Zcu, nav: InternPool.Nav.Index) File.Index { const ip = &zcu.intern_pool; - return ip.getNav(nav).srcInst(ip).resolveFull(ip).file; + return ip.getNav(nav).srcInst(ip).resolveFile(ip); } pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File { @@ -3115,6 +3076,6 @@ pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File { pub fn cauFileScope(zcu: *Zcu, cau: InternPool.Cau.Index) *File { const ip = &zcu.intern_pool; - const file_index = ip.getCau(cau).zir_index.resolveFull(ip).file; + const file_index = ip.getCau(cau).zir_index.resolveFile(ip); return zcu.fileByIndex(file_index); } diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index b353331d9573..37a3aced09e5 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -39,7 +39,6 @@ pub fn astGenFile( pt: Zcu.PerThread, file: *Zcu.File, path_digest: Cache.BinDigest, - old_root_type: InternPool.Index, ) !void { dev.check(.ast_gen); assert(!file.mod.isBuiltin()); @@ -299,25 +298,15 @@ pub fn astGenFile( file.status = .astgen_failure; return error.AnalysisFail; } - - if (old_root_type != .none) { - // The root of this file must be re-analyzed, since the file has changed. - comp.mutex.lock(); - defer comp.mutex.unlock(); - - log.debug("outdated file root type: {}", .{old_root_type}); - try zcu.outdated_file_root.put(gpa, old_root_type, {}); - } } const UpdatedFile = struct { - file_index: Zcu.File.Index, file: *Zcu.File, inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index), }; -fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.ArrayListUnmanaged(UpdatedFile)) void { - for (updated_files.items) |*elem| elem.inst_map.deinit(gpa); +fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.AutoArrayHashMapUnmanaged(Zcu.File.Index, UpdatedFile)) void { + for (updated_files.values()) |*elem| elem.inst_map.deinit(gpa); updated_files.deinit(gpa); } @@ -328,143 +317,166 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { const gpa = zcu.gpa; // We need to visit every updated File for every TrackedInst in InternPool. - var updated_files: std.ArrayListUnmanaged(UpdatedFile) = .{}; + var updated_files: std.AutoArrayHashMapUnmanaged(Zcu.File.Index, UpdatedFile) = .{}; defer cleanupUpdatedFiles(gpa, &updated_files); for (zcu.import_table.values()) |file_index| { const file = zcu.fileByIndex(file_index); const old_zir = file.prev_zir orelse continue; const new_zir = file.zir; - try updated_files.append(gpa, .{ - .file_index = file_index, + const gop = try updated_files.getOrPut(gpa, file_index); + assert(!gop.found_existing); + gop.value_ptr.* = .{ .file = file, .inst_map = .{}, - }); - const inst_map = &updated_files.items[updated_files.items.len - 1].inst_map; - try Zcu.mapOldZirToNew(gpa, old_zir.*, new_zir, inst_map); + }; + if (!new_zir.hasCompileErrors()) { + try Zcu.mapOldZirToNew(gpa, old_zir.*, file.zir, &gop.value_ptr.inst_map); + } } - if (updated_files.items.len == 0) + if (updated_files.count() == 0) return; for (ip.locals, 0..) |*local, tid| { const tracked_insts_list = local.getMutableTrackedInsts(gpa); - for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| { - for (updated_files.items) |updated_file| { - const file_index = updated_file.file_index; - if (tracked_inst.file != file_index) continue; - - const file = updated_file.file; - const old_zir = file.prev_zir.?.*; - const new_zir = file.zir; - const old_tag = old_zir.instructions.items(.tag); - const old_data = old_zir.instructions.items(.data); - const inst_map = &updated_file.inst_map; - - const old_inst = tracked_inst.inst; - const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{ - .tid = @enumFromInt(tid), - .index = @intCast(tracked_inst_unwrapped_index), - }).wrap(ip); - tracked_inst.inst = inst_map.get(old_inst) orelse { - // Tracking failed for this instruction. Invalidate associated `src_hash` deps. - log.debug("tracking failed for %{d}", .{old_inst}); - try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); - continue; - }; + for (tracked_insts_list.viewAllowEmpty().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| { + const file_index = tracked_inst.file; + const updated_file = updated_files.get(file_index) orelse continue; - if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: { - if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| { - if (std.zig.srcHashEql(old_hash, new_hash)) { - break :hash_changed; - } - log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{ - old_inst, - tracked_inst.inst, - std.fmt.fmtSliceHexLower(&old_hash), - std.fmt.fmtSliceHexLower(&new_hash), - }); + const file = updated_file.file; + + if (file.zir.hasCompileErrors()) { + // If we mark this as outdated now, users of this inst will just get a transitive analysis failure. + // Ultimately, they would end up throwing out potentially useful analysis results. + // So, do nothing. We already have the file failure -- that's sufficient for now! + continue; + } + const old_inst = tracked_inst.inst.unwrap() orelse continue; // we can't continue tracking lost insts + const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{ + .tid = @enumFromInt(tid), + .index = @intCast(tracked_inst_unwrapped_index), + }).wrap(ip); + const new_inst = updated_file.inst_map.get(old_inst) orelse { + // Tracking failed for this instruction. Invalidate associated `src_hash` deps. + log.debug("tracking failed for %{d}", .{old_inst}); + tracked_inst.inst = .lost; + try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); + continue; + }; + tracked_inst.inst = InternPool.TrackedInst.MaybeLost.ZirIndex.wrap(new_inst); + + const old_zir = file.prev_zir.?.*; + const new_zir = file.zir; + const old_tag = old_zir.instructions.items(.tag); + const old_data = old_zir.instructions.items(.data); + + if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: { + if (new_zir.getAssociatedSrcHash(new_inst)) |new_hash| { + if (std.zig.srcHashEql(old_hash, new_hash)) { + break :hash_changed; } - // The source hash associated with this instruction changed - invalidate relevant dependencies. - try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); + log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{ + old_inst, + new_inst, + std.fmt.fmtSliceHexLower(&old_hash), + std.fmt.fmtSliceHexLower(&new_hash), + }); } + // The source hash associated with this instruction changed - invalidate relevant dependencies. + try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index }); + } - // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies. - const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) { - .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) { - .struct_decl, .union_decl, .opaque_decl, .enum_decl => true, - else => false, - }, + // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies. + const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) { + .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) { + .struct_decl, .union_decl, .opaque_decl, .enum_decl => true, else => false, - }; - if (!has_namespace) continue; - - var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; - defer old_names.deinit(zcu.gpa); - { - var it = old_zir.declIterator(old_inst); - while (it.next()) |decl_inst| { - const decl_name = old_zir.getDeclaration(decl_inst)[0].name; - switch (decl_name) { - .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, - _ => if (decl_name.isNamedTest(old_zir)) continue, - } - const name_zir = decl_name.toString(old_zir).?; - const name_ip = try zcu.intern_pool.getOrPutString( - zcu.gpa, - pt.tid, - old_zir.nullTerminatedString(name_zir), - .no_embedded_nulls, - ); - try old_names.put(zcu.gpa, name_ip, {}); + }, + else => false, + }; + if (!has_namespace) continue; + + var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; + defer old_names.deinit(zcu.gpa); + { + var it = old_zir.declIterator(old_inst); + while (it.next()) |decl_inst| { + const decl_name = old_zir.getDeclaration(decl_inst)[0].name; + switch (decl_name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, + _ => if (decl_name.isNamedTest(old_zir)) continue, } + const name_zir = decl_name.toString(old_zir).?; + const name_ip = try zcu.intern_pool.getOrPutString( + zcu.gpa, + pt.tid, + old_zir.nullTerminatedString(name_zir), + .no_embedded_nulls, + ); + try old_names.put(zcu.gpa, name_ip, {}); } - var any_change = false; - { - var it = new_zir.declIterator(tracked_inst.inst); - while (it.next()) |decl_inst| { - const decl_name = new_zir.getDeclaration(decl_inst)[0].name; - switch (decl_name) { - .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, - _ => if (decl_name.isNamedTest(new_zir)) continue, - } - const name_zir = decl_name.toString(new_zir).?; - const name_ip = try zcu.intern_pool.getOrPutString( - zcu.gpa, - pt.tid, - new_zir.nullTerminatedString(name_zir), - .no_embedded_nulls, - ); - if (!old_names.swapRemove(name_ip)) continue; - // Name added - any_change = true; - try zcu.markDependeeOutdated(.{ .namespace_name = .{ - .namespace = tracked_inst_index, - .name = name_ip, - } }); + } + var any_change = false; + { + var it = new_zir.declIterator(new_inst); + while (it.next()) |decl_inst| { + const decl_name = new_zir.getDeclaration(decl_inst)[0].name; + switch (decl_name) { + .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue, + _ => if (decl_name.isNamedTest(new_zir)) continue, } - } - // The only elements remaining in `old_names` now are any names which were removed. - for (old_names.keys()) |name_ip| { + const name_zir = decl_name.toString(new_zir).?; + const name_ip = try zcu.intern_pool.getOrPutString( + zcu.gpa, + pt.tid, + new_zir.nullTerminatedString(name_zir), + .no_embedded_nulls, + ); + if (!old_names.swapRemove(name_ip)) continue; + // Name added any_change = true; try zcu.markDependeeOutdated(.{ .namespace_name = .{ .namespace = tracked_inst_index, .name = name_ip, } }); } + } + // The only elements remaining in `old_names` now are any names which were removed. + for (old_names.keys()) |name_ip| { + any_change = true; + try zcu.markDependeeOutdated(.{ .namespace_name = .{ + .namespace = tracked_inst_index, + .name = name_ip, + } }); + } - if (any_change) { - try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index }); - } + if (any_change) { + try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index }); } } } - for (updated_files.items) |updated_file| { + try ip.rehashTrackedInsts(gpa, pt.tid); + + for (updated_files.keys(), updated_files.values()) |file_index, updated_file| { const file = updated_file.file; - const prev_zir = file.prev_zir.?; - file.prev_zir = null; - prev_zir.deinit(gpa); - gpa.destroy(prev_zir); + if (file.zir.hasCompileErrors()) { + // Keep `prev_zir` around: it's the last non-error ZIR. + // Don't update the namespace, as we have no new data to update *to*. + } else { + const prev_zir = file.prev_zir.?; + file.prev_zir = null; + prev_zir.deinit(gpa); + gpa.destroy(prev_zir); + + // For every file which has changed, re-scan the namespace of the file's root struct type. + // These types are special-cased because they don't have an enclosing declaration which will + // be re-analyzed (causing the struct's namespace to be re-scanned). It's fine to do this + // now because this work is fast (no actual Sema work is happening, we're just updating the + // namespace contents). We must do this after updating ZIR refs above, since `scanNamespace` + // will track some instructions. + try pt.updateFileNamespace(file_index); + } } } @@ -473,6 +485,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const file_root_type = pt.zcu.fileRootType(file_index); if (file_root_type != .none) { + // The namespace is already up-to-date thanks to the `updateFileNamespace` calls at the + // start of this update. We just have to check whether the type itself is okay! const file_root_type_cau = pt.zcu.intern_pool.loadStructType(file_root_type).cau.unwrap().?; return pt.ensureCauAnalyzed(file_root_type_cau); } else { @@ -493,7 +507,6 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index }); const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip); log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)}); @@ -516,12 +529,9 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu _ = zcu.outdated_ready.swapRemove(anal_unit); } - // TODO: this only works if namespace lookups in Sema trigger `ensureCauAnalyzed`, because - // `outdated_file_root` information is not "viral", so we need that a namespace lookup first - // handles the case where the file root is not an outdated *type* but does have an outdated - // *namespace*. A more logically simple alternative may be for a file's root struct to register - // a dependency on the file's entire source code (hash). Alternatively, we could make sure that - // these are always handled first in an update. Actually, that's probably the best option. + const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + + // TODO: document this elsewhere mlugg! // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: // `const S = struct { ... };` // We are adding or removing a declaration within this `struct`. @@ -535,16 +545,12 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu // * we basically do `scanDecls`, updating the namespace as needed // * TODO: optimize this to make sure we only do it once a generation i guess? // * so everyone lived happily ever after - const file_root_outdated = switch (cau.owner.unwrap()) { - .type => |ty| zcu.outdated_file_root.swapRemove(ty), - .nav, .none => false, - }; if (zcu.fileByIndex(inst_info.file).status != .success_zir) { return error.AnalysisFail; } - if (!cau_outdated and !file_root_outdated) { + if (!cau_outdated) { // We can trust the current information about this `Cau`. if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { return error.AnalysisFail; @@ -571,10 +577,13 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu const sema_result: SemaCauResult = res: { if (inst_info.inst == .main_struct_inst) { - const changed = try pt.semaFileUpdate(inst_info.file, cau_outdated); + // Note that this is definitely a *recreation* due to outdated, because + // this instruction indicates that `cau.owner` is a `type`, which only + // reaches here if `cau_outdated`. + try pt.recreateFileRoot(inst_info.file); break :res .{ - .invalidate_decl_val = changed, - .invalidate_decl_ref = changed, + .invalidate_decl_val = true, + .invalidate_decl_ref = true, }; } @@ -690,8 +699,8 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter zcu.potentially_outdated.swapRemove(anal_unit); if (func_outdated) { - dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); + dev.check(.incremental); zcu.deleteUnitExports(anal_unit); zcu.deleteUnitReferences(anal_unit); } @@ -920,12 +929,9 @@ fn createFileRootStruct( return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); } -/// Re-analyze the root type of a file on an incremental update. -/// If `type_outdated`, the struct type itself is considered outdated and is -/// reconstructed at a new InternPool index. Otherwise, the namespace is just -/// re-analyzed. Returns whether the decl's tyval was invalidated. -/// Returns `error.AnalysisFail` if the file has an error. -fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated: bool) Zcu.SemaError!bool { +/// Recreate the root type of a file after it becomes outdated. A new struct type +/// is constructed at a new InternPool index, reusing the namespace for efficiency. +fn recreateFileRoot(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const zcu = pt.zcu; const ip = &zcu.intern_pool; const file = zcu.fileByIndex(file_index); @@ -934,48 +940,58 @@ fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated: assert(file_root_type != .none); - log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{ + log.debug("recreateFileRoot mod={s} sub_file_path={s}", .{ file.mod.fully_qualified_name, file.sub_file_path, - type_outdated, }); if (file.status != .success_zir) { return error.AnalysisFail; } - if (type_outdated) { - // Invalidate the existing type, reusing its namespace. - const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?; - ip.removeDependenciesForDepender( - zcu.gpa, - InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }), - ); - ip.remove(pt.tid, file_root_type); - _ = try pt.createFileRootStruct(file_index, namespace_index); - return true; - } - - // Only the struct's namespace is outdated. - // Preserve the type - just scan the namespace again. + // Invalidate the existing type, reusing its namespace. + const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?; + ip.removeDependenciesForDepender( + zcu.gpa, + InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }), + ); + ip.remove(pt.tid, file_root_type); + _ = try pt.createFileRootStruct(file_index, namespace_index); +} - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); +/// Re-scan the namespace of a file's root struct type on an incremental update. +/// The file must have successfully populated ZIR. +/// If the file's root struct type is not populated (the file is unreferenced), nothing is done. +/// This is called by `updateZirRefs` for all updated files before the main work loop. +/// This function does not perform any semantic analysis. +fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void { + const zcu = pt.zcu; - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; - extra_index += @intFromBool(small.has_fields_len); - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - const decls = file.zir.bodySlice(extra_index, decls_len); + const file = zcu.fileByIndex(file_index); + assert(file.status == .success_zir); + const file_root_type = zcu.fileRootType(file_index); + if (file_root_type == .none) return; - if (!type_outdated) { - try pt.scanNamespace(namespace_index, decls); - } + log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{ + file.mod.fully_qualified_name, + file.sub_file_path, + }); - return false; + const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu); + const decls = decls: { + const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; + const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); + + var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; + extra_index += @intFromBool(small.has_fields_len); + const decls_len = if (small.has_decls_len) blk: { + const decls_len = file.zir.extra[extra_index]; + extra_index += 1; + break :blk decls_len; + } else 0; + break :decls file.zir.bodySlice(extra_index, decls_len); + }; + try pt.scanNamespace(namespace_index, decls); } /// Regardless of the file status, will create a `Decl` if none exists so that we can track @@ -1052,7 +1068,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index }); const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip); + const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; const file = zcu.fileByIndex(inst_info.file); const zir = file.zir; @@ -1944,6 +1960,9 @@ const ScanDeclIter = struct { const cau, const nav = if (existing_cau) |cau_index| cau_nav: { const nav_index = ip.getCau(cau_index).owner.unwrap().nav; const nav = ip.getNav(nav_index); + if (nav.name != name) { + std.debug.panic("'{}' vs '{}'", .{ nav.name.fmt(ip), name.fmt(ip) }); + } assert(nav.name == name); assert(nav.fqn == fqn); break :cau_nav .{ cau_index, nav_index }; @@ -2011,7 +2030,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index }); const func = zcu.funcInfo(func_index); - const inst_info = func.zir_body_inst.resolveFull(ip); + const inst_info = func.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail; const file = zcu.fileByIndex(inst_info.file); const zir = file.zir; @@ -2097,7 +2116,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! }; defer inner_block.instructions.deinit(gpa); - const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip)); + const fn_info = sema.code.getFnInfo(func.zirBodyInstUnordered(ip).resolve(ip) orelse return error.AnalysisFail); // Here we are performing "runtime semantic analysis" for a function body, which means // we must map the parameter ZIR instructions to `arg` AIR instructions. diff --git a/src/codegen.zig b/src/codegen.zig index af77dcc50cad..0c592c6f19a5 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -98,7 +98,7 @@ pub fn generateLazyFunction( debug_output: DebugInfoOutput, ) CodeGenError!Result { const zcu = pt.zcu; - const file = Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(&zcu.intern_pool).file; + const file = Type.fromInterned(lazy_sym.ty).typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(&zcu.intern_pool); const target = zcu.fileByIndex(file).mod.resolved_target.result; switch (target_util.zigBackend(target, false)) { else => unreachable, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 03a1ea374618..7c35a178a0a8 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2585,7 +2585,7 @@ pub fn genTypeDecl( const ty = Type.fromInterned(index); _ = try renderTypePrefix(.flush, global_ctype_pool, zcu, writer, global_ctype, .suffix, .{}); try writer.writeByte(';'); - const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file; + const file_scope = ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip); if (!zcu.fileByIndex(file_scope).mod.strip) try writer.print(" /* {} */", .{ ty.containerTypeName(ip).fmt(ip), }); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index fd95082428f8..acceea175a79 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1959,7 +1959,7 @@ pub const Object = struct { ); } - const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file); + const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip)); const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace| try o.namespaceToDebugScope(parent_namespace) else @@ -2137,7 +2137,7 @@ pub const Object = struct { const name = try o.allocTypeName(ty); defer gpa.free(name); - const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file); + const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip)); const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace| try o.namespaceToDebugScope(parent_namespace) else @@ -2772,7 +2772,7 @@ pub const Object = struct { fn makeEmptyNamespaceDebugType(o: *Object, ty: Type) !Builder.Metadata { const zcu = o.pt.zcu; const ip = &zcu.intern_pool; - const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFull(ip).file); + const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip)); const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace| try o.namespaceToDebugScope(parent_namespace) else