diff options
| author | Matthew Lugg <mlugg@mlugg.co.uk> | 2024-03-14 17:45:21 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-14 17:45:21 +0000 |
| commit | 39459e78ad0d55ada373f9368d173040dd68d837 (patch) | |
| tree | 88efd4a5d646f6fb51515d1798ba52c0c7d62b54 /src/Module.zig | |
| parent | 5c8eda36d6de6e9858a7527af3a1e9851969189e (diff) | |
| parent | 7c3237019454a6009d96eca31f36a1d9e6ce02aa (diff) | |
| download | zig-39459e78ad0d55ada373f9368d173040dd68d837.tar.gz zig-39459e78ad0d55ada373f9368d173040dd68d837.zip | |
Merge pull request #19273 from mlugg/incremental-some-more
compiler: more progress on incremental
Diffstat (limited to 'src/Module.zig')
| -rw-r--r-- | src/Module.zig | 515 |
1 files changed, 378 insertions, 137 deletions
diff --git a/src/Module.zig b/src/Module.zig index 7d20c0f023..8f6def21ae 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -362,7 +362,7 @@ pub const Decl = struct { src_line: u32, /// Index of the ZIR `declaration` instruction from which this `Decl` was created. /// For the root `Decl` of a `File` and legacy anonymous decls, this is `.none`. - zir_decl_index: Zir.Inst.OptionalIndex, + zir_decl_index: InternPool.TrackedInst.Index.Optional, /// Represents the "shallow" analysis status. For example, for decls that are functions, /// the function type is analyzed with this set to `in_progress`, however, the semantic @@ -428,16 +428,9 @@ pub const Decl = struct { const Index = InternPool.DeclIndex; const OptionalIndex = InternPool.OptionalDeclIndex; - /// Asserts that `zir_decl_index` is not `.none`. - fn getDeclaration(decl: Decl, zir: Zir) Zir.Inst.Declaration { - const zir_index = decl.zir_decl_index.unwrap().?; - const pl_node = zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; - return zir.extraData(Zir.Inst.Declaration, pl_node.payload_index).data; - } - pub fn zirBodies(decl: Decl, zcu: *Zcu) Zir.Inst.Declaration.Bodies { const zir = decl.getFileScope(zcu).zir; - const zir_index = decl.zir_decl_index.unwrap().?; + const zir_index = decl.zir_decl_index.unwrap().?.resolve(&zcu.intern_pool); const pl_node = zir.instructions.items(.data)[@intFromEnum(zir_index)].pl_node; const extra = zir.extraData(Zir.Inst.Declaration, pl_node.payload_index); return extra.data.getBodies(@intCast(extra.end), zir); @@ -769,14 +762,14 @@ pub const Namespace = struct { zcu: *Zcu, pub fn hash(ctx: @This(), decl_index: Decl.Index) u32 { - const decl = ctx.module.declPtr(decl_index); + const decl = ctx.zcu.declPtr(decl_index); return std.hash.uint32(@intFromEnum(decl.name)); } pub fn eql(ctx: @This(), a_decl_index: Decl.Index, b_decl_index: Decl.Index, b_index: usize) bool { _ = b_index; - const a_decl = ctx.module.declPtr(a_decl_index); - const b_decl = ctx.module.declPtr(b_decl_index); + const a_decl = ctx.zcu.declPtr(a_decl_index); + const b_decl = ctx.zcu.declPtr(b_decl_index); return a_decl.name == b_decl.name; } }; @@ -2662,16 +2655,15 @@ pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void { if (opt_po_entry) |e| e.value else 0, ); log.debug("outdated: {}", .{depender}); - if (opt_po_entry != null) { + if (opt_po_entry == null) { // This is a new entry with no PO dependencies. try zcu.outdated_ready.put(zcu.gpa, depender, {}); } // If this is a Decl and was not previously PO, we must recursively // mark dependencies on its tyval as PO. - if (opt_po_entry == null) switch (depender.unwrap()) { - .decl => |decl_index| try zcu.markDeclDependenciesPotentiallyOutdated(decl_index), - .func => {}, - }; + if (opt_po_entry == null) { + try zcu.markTransitiveDependersPotentiallyOutdated(depender); + } } } @@ -2701,15 +2693,19 @@ fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { // as no longer PO. switch (depender.unwrap()) { .decl => |decl_index| try zcu.markPoDependeeUpToDate(.{ .decl_val = decl_index }), - .func => {}, + .func => |func_index| try zcu.markPoDependeeUpToDate(.{ .func_ies = func_index }), } } } -/// Given a Decl which is newly outdated or PO, mark all dependers which depend -/// on its tyval as PO. -fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !void { - var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index }); +/// Given a Depender which is newly outdated or PO, mark all Dependers which may +/// in turn be PO, due to a dependency on the original Depender's tyval or IES. +fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: InternPool.Depender) !void { + var it = zcu.intern_pool.dependencyIterator(switch (maybe_outdated.unwrap()) { + .decl => |decl_index| .{ .decl_val = decl_index }, // TODO: also `decl_ref` deps when introduced + .func => |func_index| .{ .func_ies = func_index }, + }); + while (it.next()) |po| { if (zcu.outdated.getPtr(po)) |po_dep_count| { // This dependency is already outdated, but it now has one more PO @@ -2726,14 +2722,9 @@ fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !v continue; } try zcu.potentially_outdated.putNoClobber(zcu.gpa, po, 1); - // If this ia a Decl, we must recursively mark dependencies - // on its tyval as PO. - switch (po.unwrap()) { - .decl => |po_decl| try zcu.markDeclDependenciesPotentiallyOutdated(po_decl), - .func => {}, - } + // This Depender was not already PO, so we must recursively mark its dependers as also PO. + try zcu.markTransitiveDependersPotentiallyOutdated(po); } - // TODO: repeat the above for `decl_ty` dependencies when they are introduced } pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.Depender { @@ -2859,10 +2850,7 @@ pub fn flushRetryableFailures(zcu: *Zcu) !void { // This Depender was not marked PO, but is now outdated. Mark it as // such, then recursively mark transitive dependencies as PO. try zcu.outdated.put(gpa, depender, 0); - switch (depender.unwrap()) { - .decl => |decl| try zcu.markDeclDependenciesPotentiallyOutdated(decl), - .func => {}, - } + try zcu.markTransitiveDependersPotentiallyOutdated(depender); } zcu.retryable_failures.clearRetainingCapacity(); } @@ -2994,6 +2982,15 @@ pub fn mapOldZirToNew( } } +/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. +pub fn ensureFileAnalyzed(zcu: *Zcu, file: *File) SemaError!void { + if (file.root_decl.unwrap()) |existing_root| { + return zcu.ensureDeclAnalyzed(existing_root); + } else { + return zcu.semaFile(file); + } +} + /// This ensures that the Decl will have an up-to-date Type and Value populated. /// However the resolution status of the Type may not be fully resolved. /// For example an inferred error set is not resolved until after `analyzeFnBody`. @@ -3004,6 +3001,11 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { const decl = mod.declPtr(decl_index); + log.debug("ensureDeclAnalyzed '{d}' (name '{}')", .{ + @intFromEnum(decl_index), + decl.name.fmt(&mod.intern_pool), + }); + // Determine whether or not this Decl is outdated, i.e. requires re-analysis // even if `complete`. If a Decl is PO, we pessismistically assume that it // *does* require re-analysis, to ensure that the Decl is definitely @@ -3015,13 +3017,15 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { // dependencies are all up-to-date. const decl_as_depender = InternPool.Depender.wrap(.{ .decl = decl_index }); - const was_outdated = mod.outdated.swapRemove(decl_as_depender) or + const decl_was_outdated = mod.outdated.swapRemove(decl_as_depender) or mod.potentially_outdated.swapRemove(decl_as_depender); - if (was_outdated) { + if (decl_was_outdated) { _ = mod.outdated_ready.swapRemove(decl_as_depender); } + const was_outdated = mod.outdated_file_root.swapRemove(decl_index) or decl_was_outdated; + switch (decl.analysis) { .in_progress => unreachable, @@ -3057,6 +3061,14 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { }; } + if (mod.declIsRoot(decl_index)) { + const changed = try mod.semaFileUpdate(decl.getFileScope(mod), decl_was_outdated); + break :blk .{ + .invalidate_decl_val = changed, + .invalidate_decl_ref = changed, + }; + } + break :blk mod.semaDecl(decl_index) catch |err| switch (err) { error.AnalysisFail => { if (decl.analysis == .in_progress) { @@ -3085,13 +3097,15 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { }; // TODO: we do not yet have separate dependencies for decl values vs types. - if (was_outdated) { + if (decl_was_outdated) { if (sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref) { + log.debug("Decl tv invalidated ('{d}')", .{@intFromEnum(decl_index)}); // This dependency was marked as PO, meaning dependees were waiting // on its analysis result, and it has turned out to be outdated. // Update dependees accordingly. try mod.markDependeeOutdated(.{ .decl_val = decl_index }); } else { + log.debug("Decl tv up-to-date ('{d}')", .{@intFromEnum(decl_index)}); // This dependency was previously PO, but turned out to be up-to-date. // We do not need to queue successive analysis. try mod.markPoDependeeUpToDate(.{ .decl_val = decl_index }); @@ -3099,15 +3113,48 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { } } -pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError!void { +pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.Index) SemaError!void { const tracy = trace(@src()); defer tracy.end(); + const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const func = zcu.funcInfo(func_index); + + // We only care about the uncoerced function. + // We need to do this for the "orphaned function" check below to be valid. + const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); + + const func = zcu.funcInfo(maybe_coerced_func_index); const decl_index = func.owner_decl; const decl = zcu.declPtr(decl_index); + log.debug("ensureFuncBodyAnalyzed '{d}' (instance of '{}')", .{ + @intFromEnum(func_index), + decl.name.fmt(ip), + }); + + // First, our owner decl must be up-to-date. This will always be the case + // during the first update, but may not on successive updates if we happen + // to get analyzed before our parent decl. + try zcu.ensureDeclAnalyzed(decl_index); + + // On an update, it's possible this function changed such that our owner + // decl now refers to a different function, making this one orphaned. If + // that's the case, we should remove this function from the binary. + if (decl.val.ip_index != func_index) { + try zcu.markDependeeOutdated(.{ .func_ies = func_index }); + ip.removeDependenciesForDepender(gpa, InternPool.Depender.wrap(.{ .func = func_index })); + ip.remove(func_index); + @panic("TODO: remove orphaned function from binary"); + } + + // We'll want to remember what the IES used to be before the update for + // dependency invalidation purposes. + const old_resolved_ies = if (func.analysis(ip).inferred_error_set) + func.resolvedErrorSet(ip).* + else + .none; + switch (decl.analysis) { .unreferenced => unreachable, .in_progress => unreachable, @@ -3131,7 +3178,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError } switch (func.analysis(ip).state) { - .success, + .success => if (!was_outdated) return, .sema_failure, .dependency_failure, .codegen_failure, @@ -3141,7 +3188,10 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError .inline_only => unreachable, // don't queue work for this } - const gpa = zcu.gpa; + log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{ + @intFromEnum(func_index), + if (was_outdated) "outdated" else "never analyzed", + }); var tmp_arena = std.heap.ArenaAllocator.init(gpa); defer tmp_arena.deinit(); @@ -3161,6 +3211,20 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, func_index: InternPool.Index) SemaError }; defer air.deinit(gpa); + const invalidate_ies_deps = i: { + if (!was_outdated) break :i false; + if (!func.analysis(ip).inferred_error_set) break :i true; + const new_resolved_ies = func.resolvedErrorSet(ip).*; + break :i new_resolved_ies != old_resolved_ies; + }; + if (invalidate_ies_deps) { + log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)}); + try zcu.markDependeeOutdated(.{ .func_ies = func_index }); + } else if (was_outdated) { + log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)}); + try zcu.markPoDependeeUpToDate(.{ .func_ies = func_index }); + } + const comp = zcu.comp; const dump_air = build_options.enable_debug_extensions and comp.verbose_air; @@ -3299,7 +3363,9 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index) /// https://github.com/ziglang/zig/issues/14307 pub fn semaPkg(mod: *Module, pkg: *Package.Module) !void { const file = (try mod.importPkg(pkg)).file; - return mod.semaFile(file); + if (file.root_decl == .none) { + return mod.semaFile(file); + } } fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespace.Index, file: *File) Allocator.Error!InternPool.Index { @@ -3366,13 +3432,75 @@ fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespa return wip_ty.finish(ip, decl_index, namespace_index.toOptional()); } -/// Regardless of the file status, will create a `Decl` so that we -/// can track dependencies and re-analyze when the file becomes outdated. -pub fn semaFile(mod: *Module, file: *File) SemaError!void { +/// Re-analyze the root Decl 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. +fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool { + const decl = zcu.declPtr(file.root_decl.unwrap().?); + + log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{ + file.mod.fully_qualified_name, + file.sub_file_path, + type_outdated, + }); + + if (file.status != .success_zir) { + if (decl.analysis == .file_failure) { + return false; + } else { + decl.analysis = .file_failure; + return true; + } + } + + if (decl.analysis == .file_failure) { + // No struct type currently exists. Create one! + _ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file); + return true; + } + + assert(decl.has_tv); + assert(decl.owns_tv); + + if (type_outdated) { + // Invalidate the existing type, reusing the decl and namespace. + zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.Depender.wrap(.{ .decl = file.root_decl.unwrap().? })); + zcu.intern_pool.remove(decl.val.toIntern()); + decl.val = undefined; + _ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file); + return true; + } + + // Only the struct's namespace is outdated. + // Preserve the type - just scan the namespace again. + + 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; + const decls = file.zir.bodySlice(extra_index, decls_len); + + if (!type_outdated) { + try zcu.scanNamespace(decl.src_namespace, decls, decl); + } + + return false; +} + +/// Regardless of the file status, will create a `Decl` if none exists so that we can track +/// dependencies and re-analyze when the file becomes outdated. +fn semaFile(mod: *Module, file: *File) SemaError!void { const tracy = trace(@src()); defer tracy.end(); - if (file.root_decl != .none) return; + assert(file.root_decl == .none); const gpa = mod.gpa; log.debug("semaFile mod={s} sub_file_path={s}", .{ @@ -3439,9 +3567,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void { }, .incremental => {}, } - - // Since this is our first time analyzing this file, there can be no dependencies on - // its root Decl. Thus, we do not need to invalidate any dependencies. } const SemaDeclResult = packed struct { @@ -3462,16 +3587,16 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult { return error.AnalysisFail; } - if (mod.declIsRoot(decl_index)) { - // This comes from an `analyze_decl` job on an incremental update where - // this file changed. - @panic("TODO: update root Decl of modified file"); - } else if (decl.owns_tv) { - // We are re-analyzing an owner Decl (for a function or a namespace type). - @panic("TODO: update owner Decl"); + assert(!mod.declIsRoot(decl_index)); + + if (decl.zir_decl_index == .none and decl.owns_tv) { + // We are re-analyzing an anonymous owner Decl (for a function or a namespace type). + return mod.semaAnonOwnerDecl(decl_index); } - const decl_inst = decl.zir_decl_index.unwrap().?; + log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)}); + + const decl_inst = decl.zir_decl_index.unwrap().?.resolve(ip); const gpa = mod.gpa; const zir = decl.getFileScope(mod).zir; @@ -3763,6 +3888,42 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult { return result; } +fn semaAnonOwnerDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult { + const decl = zcu.declPtr(decl_index); + + assert(decl.has_tv); + assert(decl.owns_tv); + + log.debug("semaAnonOwnerDecl '{d}'", .{@intFromEnum(decl_index)}); + + switch (decl.ty.zigTypeTag(zcu)) { + .Fn => @panic("TODO: update fn instance"), + .Type => {}, + else => unreachable, + } + + // We are the owner Decl of a type, and we were marked as outdated. That means the *structure* + // of this type changed; not just its namespace. Therefore, we need a new InternPool index. + // + // However, as soon as we make that, the context that created us will require re-analysis anyway + // (as it depends on this Decl's value), meaning the `struct_decl` (or equivalent) instruction + // will be analyzed again. Since Sema already needs to be able to reconstruct types like this, + // why should we bother implementing it here too when the Sema logic will be hit right after? + // + // So instead, let's just mark this Decl as failed - so that any remaining Decls which genuinely + // reference it (via `@This`) end up silently erroring too - and we'll let Sema make a new type + // with a new Decl. + // + // Yes, this does mean that any type owner Decl has a constant value for its entire lifetime. + zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.Depender.wrap(.{ .decl = decl_index })); + zcu.intern_pool.remove(decl.val.toIntern()); + decl.analysis = .dependency_failure; + return .{ + .invalidate_decl_val = true, + .invalidate_decl_ref = true, + }; +} + pub const ImportFileResult = struct { file: *File, is_new: bool, @@ -4083,26 +4244,87 @@ pub fn scanNamespace( const gpa = zcu.gpa; const namespace = zcu.namespacePtr(namespace_index); + // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather + // than their name. We'll build an efficient mapping now, then discard the current `decls`. + var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index) = .{}; + defer existing_by_inst.deinit(gpa); + + try existing_by_inst.ensureTotalCapacity(gpa, @intCast(namespace.decls.count())); + + for (namespace.decls.keys()) |decl_index| { + const decl = zcu.declPtr(decl_index); + existing_by_inst.putAssumeCapacityNoClobber(decl.zir_decl_index.unwrap().?, decl_index); + } + + var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; + defer seen_decls.deinit(gpa); + try zcu.comp.work_queue.ensureUnusedCapacity(decls.len); + + namespace.decls.clearRetainingCapacity(); try namespace.decls.ensureTotalCapacity(gpa, decls.len); + namespace.usingnamespace_set.clearRetainingCapacity(); + var scan_decl_iter: ScanDeclIter = .{ .zcu = zcu, .namespace_index = namespace_index, .parent_decl = parent_decl, + .seen_decls = &seen_decls, + .existing_by_inst = &existing_by_inst, + .pass = .named, }; for (decls) |decl_inst| { try scanDecl(&scan_decl_iter, decl_inst); } + scan_decl_iter.pass = .unnamed; + for (decls) |decl_inst| { + try scanDecl(&scan_decl_iter, decl_inst); + } + + if (seen_decls.count() != namespace.decls.count()) { + // Do a pass over the namespace contents and remove any decls from the last update + // which were removed in this one. + var i: usize = 0; + while (i < namespace.decls.count()) { + const decl_index = namespace.decls.keys()[i]; + const decl = zcu.declPtr(decl_index); + if (!seen_decls.contains(decl.name)) { + // We must preserve namespace ordering for @typeInfo. + namespace.decls.orderedRemoveAt(i); + i -= 1; + } + } + } } const ScanDeclIter = struct { zcu: *Zcu, namespace_index: Namespace.Index, parent_decl: *Decl, + seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), + existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index), + /// Decl scanning is run in two passes, so that we can detect when a generated + /// name would clash with an explicit name and use a different one. + pass: enum { named, unnamed }, usingnamespace_index: usize = 0, comptime_index: usize = 0, unnamed_test_index: usize = 0, + + fn avoidNameConflict(iter: *ScanDeclIter, comptime fmt: []const u8, args: anytype) !InternPool.NullTerminatedString { + const zcu = iter.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + var name = try ip.getOrPutStringFmt(gpa, fmt, args); + var gop = try iter.seen_decls.getOrPut(gpa, name); + var next_suffix: u32 = 0; + while (gop.found_existing) { + name = try ip.getOrPutStringFmt(gpa, fmt ++ "_{d}", args ++ .{next_suffix}); + gop = try iter.seen_decls.getOrPut(gpa, name); + next_suffix += 1; + } + return name; + } }; fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void { @@ -4126,134 +4348,148 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void // Every Decl needs a name. const decl_name: InternPool.NullTerminatedString, const kind: Decl.Kind, const is_named_test: bool = switch (declaration.name) { .@"comptime" => info: { + if (iter.pass != .unnamed) return; const i = iter.comptime_index; iter.comptime_index += 1; break :info .{ - try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i}), + try iter.avoidNameConflict("comptime_{d}", .{i}), .@"comptime", false, }; }, .@"usingnamespace" => info: { + // TODO: this isn't right! These should be considered unnamed. Name conflicts can happen here. + // The problem is, we need to preserve the decl ordering for `@typeInfo`. + // I'm not bothering to fix this now, since some upcoming changes will change this code significantly anyway. + if (iter.pass != .named) return; const i = iter.usingnamespace_index; iter.usingnamespace_index += 1; break :info .{ - try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i}), + try iter.avoidNameConflict("usingnamespace_{d}", .{i}), .@"usingnamespace", false, }; }, .unnamed_test => info: { + if (iter.pass != .unnamed) return; const i = iter.unnamed_test_index; iter.unnamed_test_index += 1; break :info .{ - try ip.getOrPutStringFmt(gpa, "test_{d}", .{i}), + try iter.avoidNameConflict("test_{d}", .{i}), .@"test", false, }; }, .decltest => info: { + // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. + if (iter.pass != .unnamed) return; assert(declaration.flags.has_doc_comment); const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end])); break :info .{ - try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{name}), + try iter.avoidNameConflict("decltest.{s}", .{name}), .@"test", true, }; }, - _ => if (declaration.name.isNamedTest(zir)) .{ - try ip.getOrPutStringFmt(gpa, "test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}), - .@"test", - true, - } else .{ - try ip.getOrPutString(gpa, zir.nullTerminatedString(declaration.name.toString(zir).?)), - .named, - false, + _ => if (declaration.name.isNamedTest(zir)) info: { + // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. + if (iter.pass != .unnamed) return; + break :info .{ + try iter.avoidNameConflict("test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}), + .@"test", + true, + }; + } else info: { + if (iter.pass != .named) return; + const name = try ip.getOrPutString(gpa, zir.nullTerminatedString(declaration.name.toString(zir).?)); + try iter.seen_decls.putNoClobber(gpa, name, {}); + break :info .{ + name, + .named, + false, + }; }, }; - if (kind == .@"usingnamespace") try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1); + switch (kind) { + .@"usingnamespace" => try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1), + .@"test" => try zcu.test_functions.ensureUnusedCapacity(gpa, 1), + else => {}, + } + + const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst); // We create a Decl for it regardless of analysis status. - const gop = try namespace.decls.getOrPutContextAdapted( - gpa, - decl_name, - DeclAdapter{ .zcu = zcu }, - Namespace.DeclContext{ .zcu = zcu }, - ); - const comp = zcu.comp; - if (!gop.found_existing) { + + const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: { + // We need only update this existing Decl. + const decl = zcu.declPtr(decl_index); + const was_exported = decl.is_exported; + assert(decl.kind == kind); // ZIR tracking should preserve this + assert(decl.alive); + decl.name = decl_name; + decl.src_node = decl_node; + decl.src_line = line; + decl.is_pub = declaration.flags.is_pub; + decl.is_exported = declaration.flags.is_export; + break :decl_index .{ was_exported, decl_index }; + } else decl_index: { + // Create and set up a new Decl. const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node); const new_decl = zcu.declPtr(new_decl_index); new_decl.kind = kind; new_decl.name = decl_name; - if (kind == .@"usingnamespace") { - namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, declaration.flags.is_pub); - } new_decl.src_line = line; - gop.key_ptr.* = new_decl_index; - // Exported decls, comptime decls, usingnamespace decls, and - // test decls if in test mode, get analyzed. - const decl_mod = namespace.file_scope.mod; - const want_analysis = declaration.flags.is_export or switch (kind) { - .anon => unreachable, - .@"comptime", .@"usingnamespace" => true, - .named => false, - .@"test" => a: { - if (!comp.config.is_test) break :a false; - if (decl_mod != zcu.main_mod) break :a false; - if (is_named_test and comp.test_filters.len > 0) { - const decl_fqn = ip.stringToSlice(try namespace.fullyQualifiedName(zcu, decl_name)); - for (comp.test_filters) |test_filter| { - if (mem.indexOf(u8, decl_fqn, test_filter)) |_| break; - } else break :a false; - } - try zcu.test_functions.put(gpa, new_decl_index, {}); - break :a true; - }, - }; - if (want_analysis) { - log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{s}' decl_index={d}", .{ - namespace.file_scope.sub_file_path, ip.stringToSlice(decl_name), new_decl_index, - }); - comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl_index }); - } new_decl.is_pub = declaration.flags.is_pub; new_decl.is_exported = declaration.flags.is_export; - new_decl.zir_decl_index = decl_inst.toOptional(); - new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive. - return; - } - const decl_index = gop.key_ptr.*; + new_decl.zir_decl_index = tracked_inst.toOptional(); + new_decl.alive = true; // This Decl corresponds to an AST node and is therefore always alive. + break :decl_index .{ false, new_decl_index }; + }; + const decl = zcu.declPtr(decl_index); - if (kind == .@"test") { - const src_loc = SrcLoc{ - .file_scope = decl.getFileScope(zcu), - .parent_decl_node = decl.src_node, - .lazy = .{ .token_offset = 1 }, - }; - const msg = try ErrorMsg.create(gpa, src_loc, "duplicate test name: {}", .{ - decl_name.fmt(ip), - }); - errdefer msg.destroy(gpa); - try zcu.failed_decls.putNoClobber(gpa, decl_index, msg); - const other_src_loc = SrcLoc{ - .file_scope = namespace.file_scope, - .parent_decl_node = decl_node, - .lazy = .{ .token_offset = 1 }, - }; - try zcu.errNoteNonLazy(other_src_loc, msg, "other test here", .{}); + + namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu }); + + const comp = zcu.comp; + const decl_mod = namespace.file_scope.mod; + const want_analysis = declaration.flags.is_export or switch (kind) { + .anon => unreachable, + .@"comptime" => true, + .@"usingnamespace" => a: { + namespace.usingnamespace_set.putAssumeCapacityNoClobber(decl_index, declaration.flags.is_pub); + break :a true; + }, + .named => false, + .@"test" => a: { + if (!comp.config.is_test) break :a false; + if (decl_mod != zcu.main_mod) break :a false; + if (is_named_test and comp.test_filters.len > 0) { + const decl_fqn = ip.stringToSlice(try namespace.fullyQualifiedName(zcu, decl_name)); + for (comp.test_filters) |test_filter| { + if (mem.indexOf(u8, decl_fqn, test_filter)) |_| break; + } else break :a false; + } + zcu.test_functions.putAssumeCapacity(decl_index, {}); // may clobber on incremental update + break :a true; + }, + }; + + if (want_analysis) { + // We will not queue analysis if the decl has been analyzed on a previous update and + // `is_export` is unchanged. In this case, the incremental update mechanism will handle + // re-analysis for us if necessary. + if (prev_exported != declaration.flags.is_export or decl.analysis == .unreferenced) { + log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{s}' decl_index={d}", .{ + namespace.file_scope.sub_file_path, ip.stringToSlice(decl_name), decl_index, + }); + comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index }); + } } - // Update the AST node of the decl; even if its contents are unchanged, it may - // have been re-ordered. - decl.src_node = decl_node; - decl.src_line = line; - decl.is_pub = declaration.flags.is_pub; - decl.is_exported = declaration.flags.is_export; - decl.kind = kind; - decl.zir_decl_index = decl_inst.toOptional(); if (decl.getOwnedFunction(zcu) != null) { + // TODO this logic is insufficient; namespaces we don't re-scan may still require + // updated line numbers. Look into this! // TODO Look into detecting when this would be unnecessary by storing enough state // in `Decl` to notice that the line number did not change. comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index }); @@ -4397,6 +4633,11 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato }; defer sema.deinit(); + // Every runtime function has a dependency on the source of the Decl it originates from. + // It also depends on the value of its owner Decl. + try sema.declareDependency(.{ .src_hash = decl.zir_decl_index.unwrap().? }); + try sema.declareDependency(.{ .decl_val = decl_index }); + if (func.analysis(ip).inferred_error_set) { const ies = try arena.create(Sema.InferredErrorSet); ies.* = .{ .func = func_index }; |
