diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2025-01-04 05:09:02 +0000 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2025-01-04 07:51:19 +0000 |
| commit | f01029c4af7d3015c1c3f7d36bb62f7d5afb53a4 (patch) | |
| tree | 21fd6696e57bc59fb1c097903e8421562d0862f5 /src/Zcu/PerThread.zig | |
| parent | fd62912787ee4f26b06ba86560e9aa605095ae04 (diff) | |
| download | zig-f01029c4af7d3015c1c3f7d36bb62f7d5afb53a4.tar.gz zig-f01029c4af7d3015c1c3f7d36bb62f7d5afb53a4.zip | |
incremental: new `AnalUnit` to group dependencies on `std.builtin` decls
This commit reworks how values like the panic handler function are
memoized during a compiler invocation. Previously, the value was
resolved by whichever analysis requested it first, and cached on `Zcu`.
This is problematic for incremental compilation, as after the initial
resolution, no dependencies are marked by users of this memoized state.
This is arguably acceptable for `std.builtin`, but it's definitely not
acceptable for the panic handler/messages, because those can be set by
the user (`std.builtin.Panic` checks `@import("root").Panic`).
So, here we introduce a new kind of `AnalUnit`, called `memoized_state`.
There are 3 such units:
* `.{ .memoized_state = .va_list }` resolves the type `std.builtin.VaList`
* `.{ .memoized_state = .panic }` resolves `std.Panic`
* `.{ .memoized_state = .main }` resolves everything else we want
These units essentially "bundle" the resolution of their corresponding
declarations, storing the results into fields on `Zcu`. This way, when,
for instance, a function wants to call the panic handler, it simply runs
`ensureMemoizedStateResolved`, registering one dependency, and pulls the
values from the `Zcu`. This "bundling" minimizes dependency edges. The 3
units are separated to allow them to act independently: for instance,
the panic handler can use `std.builtin.Type` without triggering a
dependency loop.
Diffstat (limited to 'src/Zcu/PerThread.zig')
| -rw-r--r-- | src/Zcu/PerThread.zig | 160 |
1 files changed, 142 insertions, 18 deletions
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index c382d78efa..854e27ae9b 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -560,6 +560,147 @@ pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.Sem return pt.semaFile(file_index); } +/// Ensures that all memoized state on `Zcu` is up-to-date, performing re-analysis if necessary. +/// Returns `error.AnalysisFail` if an analysis error is encountered; the caller is free to ignore +/// this, since the error is already registered, but it must not use the value of memoized fields. +pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.MemoizedStateStage) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); + + const zcu = pt.zcu; + const gpa = zcu.gpa; + + const unit: AnalUnit = .wrap(.{ .memoized_state = stage }); + + log.debug("ensureMemoizedStateUpToDate", .{}); + + assert(!zcu.analysis_in_progress.contains(unit)); + + const was_outdated = zcu.outdated.swapRemove(unit) or zcu.potentially_outdated.swapRemove(unit); + const prev_failed = zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit); + + if (was_outdated) { + dev.check(.incremental); + _ = zcu.outdated_ready.swapRemove(unit); + // No need for `deleteUnitExports` because we never export anything. + zcu.deleteUnitReferences(unit); + if (zcu.failed_analysis.fetchSwapRemove(unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(unit); + } else { + if (prev_failed) return error.AnalysisFail; + // We use an arbitrary field to check if the state has been resolved yet. + const val = switch (stage) { + .main => zcu.builtin_decl_values.Type, + .panic => zcu.builtin_decl_values.Panic, + .va_list => zcu.builtin_decl_values.VaList, + }; + if (val != .none) return; + } + + const any_changed: bool, const new_failed: bool = if (pt.analyzeMemoizedState(stage)) |any_changed| + .{ any_changed or prev_failed, false } + else |err| switch (err) { + error.AnalysisFail => res: { + if (!zcu.failed_analysis.contains(unit)) { + // If this unit caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, unit, {}); + log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(unit)}); + } + break :res .{ !prev_failed, true }; + }, + error.OutOfMemory => { + // TODO: same as for `ensureComptimeUnitUpToDate` etc + return error.OutOfMemory; + }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + }; + + if (was_outdated) { + const dependee: InternPool.Dependee = .{ .memoized_state = stage }; + if (any_changed) { + try zcu.markDependeeOutdated(.marked_po, dependee); + } else { + try zcu.markPoDependeeUpToDate(dependee); + } + } + + if (new_failed) return error.AnalysisFail; +} + +fn analyzeMemoizedState(pt: Zcu.PerThread, stage: InternPool.MemoizedStateStage) Zcu.CompileError!bool { + const zcu = pt.zcu; + const ip = &zcu.intern_pool; + const gpa = zcu.gpa; + + const unit: AnalUnit = .wrap(.{ .memoized_state = stage }); + + try zcu.analysis_in_progress.put(gpa, unit, {}); + defer assert(zcu.analysis_in_progress.swapRemove(unit)); + + // Before we begin, collect: + // * The type `std`, and its namespace + // * The type `std.builtin`, and its namespace + // * A semi-reasonable source location + const std_file_imported = pt.importPkg(zcu.std_mod) catch return error.AnalysisFail; + try pt.ensureFileAnalyzed(std_file_imported.file_index); + const std_type: Type = .fromInterned(zcu.fileRootType(std_file_imported.file_index)); + const std_namespace = std_type.getNamespaceIndex(zcu); + try pt.ensureNamespaceUpToDate(std_namespace); + const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls); + const builtin_nav = zcu.namespacePtr(std_namespace).pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse + @panic("lib/std.zig is corrupt and missing 'builtin'"); + try pt.ensureNavValUpToDate(builtin_nav); + const builtin_type: Type = .fromInterned(ip.getNav(builtin_nav).status.fully_resolved.val); + const builtin_namespace = builtin_type.getNamespaceIndex(zcu); + try pt.ensureNamespaceUpToDate(builtin_namespace); + const src: Zcu.LazySrcLoc = .{ + .base_node_inst = builtin_type.typeDeclInst(zcu).?, + .offset = .entire_file, + }; + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = .{ .instructions = .empty, .string_bytes = &.{}, .extra = &.{} }, + .owner = unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = std_namespace, + .instructions = .{}, + .inlining = null, + .comptime_reason = .{ .reason = .{ + .src = src, + .r = .{ .simple = .type }, + } }, + .src_base_inst = src.base_node_inst, + .type_name_ctx = .empty, + }; + defer block.instructions.deinit(gpa); + + return sema.analyzeMemoizedState(&block, src, builtin_namespace, stage); +} + /// Ensures that the state of the given `ComptimeUnit` is fully up-to-date, performing re-analysis /// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is /// free to ignore this, since the error is already registered. @@ -2615,7 +2756,7 @@ fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaE // result in circular dependency errors. // TODO: this can go away once we fix backends having to resolve `StackTrace`. // The codegen timing guarantees that the parameter types will be populated. - sema.resolveFnTypes(fn_ty) catch |err| switch (err) { + sema.resolveFnTypes(fn_ty, inner_block.nodeOffset(0)) catch |err| switch (err) { error.GenericPoison => unreachable, error.ComptimeReturn => unreachable, error.ComptimeBreak => unreachable, @@ -3471,23 +3612,6 @@ pub fn structPackedFieldBitOffset( unreachable; // index out of bounds } -pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!InternPool.Nav.Index { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const std_file_imported = pt.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig"); - const std_type = Type.fromInterned(zcu.fileRootType(std_file_imported.file_index)); - const std_namespace = zcu.namespacePtr(std_type.getNamespace(zcu).unwrap().?); - const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls); - const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse - @panic("lib/std.zig is corrupt and missing 'builtin'"); - pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); - const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.fully_resolved.val); - const builtin_namespace = zcu.namespacePtr(builtin_type.getNamespace(zcu).unwrap() orelse @panic("std.builtin is corrupt")); - const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls); - return builtin_namespace.pub_decls.getKeyAdapted(name_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt"); -} - pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Error!Type { const zcu = pt.zcu; const ip = &zcu.intern_pool; |
