aboutsummaryrefslogtreecommitdiff
path: root/src/Zcu/PerThread.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2025-01-04 05:09:02 +0000
committermlugg <mlugg@mlugg.co.uk>2025-01-04 07:51:19 +0000
commitf01029c4af7d3015c1c3f7d36bb62f7d5afb53a4 (patch)
tree21fd6696e57bc59fb1c097903e8421562d0862f5 /src/Zcu/PerThread.zig
parentfd62912787ee4f26b06ba86560e9aa605095ae04 (diff)
downloadzig-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.zig160
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;