authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-06-29 20:00:11+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-07-04 21:01:41+01:00
log089bbd6588d82ccda0646e756006cf5787eadef2
treee6db5043b11058300c2537b30bae4fb5ddd4ef81
parent5f03c025058ddda09bfb3eac283bb88d30ad38cc
signaturelock-open Commit is signed but in an unrecognized format.

Zcu: rework reference traces

Previously, `reference_table` mapped from a `Decl` being referenced to the `Decl` that performed the reference. This is convenient for constructing error messages, but problematic for incremental compilation. This is because on an incremental update, we want to efficiently remove all references triggered by an `AnalUnit` which is being re-analyzed. For this reason, `reference_table` now maps the other way: from the `AnalUnit` *performing* the reference, to the `AnalUnit` whose analysis was triggered. As a general rule, any call to any of the following functions should be preceded by a call to `Sema.addReferenceEntry`: * `Zcu.ensureDeclAnalyzed` * `Sema.ensureDeclAnalyzed` * `Zcu.ensureFuncBodyAnalyzed` * `Zcu.ensureFuncBodyAnalysisQueued` This is not just important for error messages, but also more fundamentally for incremental compilation. When an incremental update occurs, we must determine whether any `AnalUnit` has become unreferenced: in this case, we should ignore its associated error messages, and perhaps even remove it from the binary. For this reason, we no longer store only one reference to every `AnalUnit`, but every reference. At the end of an update, `Zcu.resolveReferences` will construct the reverse mapping, and as such identify which `AnalUnit`s are still referenced. The current implementation doesn't quite do what we need for incremental compilation here, but the framework is in place. Note that `Zcu.resolveReferences` does constitute a non-trivial amount of work on every incremental update. However, for incremental compilation, this work -- which will effectively be a graph traversal over all `AnalUnit` references -- seems strictly necessary. At the moment, this work is only done if the `Zcu` has any errors, when collecting them into the final `ErrorBundle`. An unsolved problem here is how to represent inline function calls in the reference trace. If `foo` performs an inline call to `bar` which references `qux`, then ideally, `bar` would be shown on the reference trace between `foo` and `qux`, but this is not currently the case. The solution here is probably for `Zcu.Reference` to store information about the source locations of active inline calls betweeen the referencer and its reference.

3 files changed, 231 insertions(+), 181 deletions(-)

src/Compilation.zig+59-37
......@@ -31,6 +31,7 @@ const clangMain = @import("main.zig").clangMain;
3131const Zcu = @import("Zcu.zig");
3232/// Deprecated; use `Zcu`.
3333const Module = Zcu;
34const Sema = @import("Sema.zig");
3435const InternPool = @import("InternPool.zig");
3536const Cache = std.Build.Cache;
3637const c_codegen = @import("codegen/c.zig");
......@@ -2939,9 +2940,12 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
29392940 });
29402941 }
29412942 if (comp.module) |zcu| {
2943 var all_references = try zcu.resolveReferences();
2944 defer all_references.deinit(gpa);
2945
29422946 for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| {
29432947 if (error_msg) |msg| {
2944 try addModuleErrorMsg(zcu, &bundle, msg.*);
2948 try addModuleErrorMsg(zcu, &bundle, msg.*, &all_references);
29452949 } else {
29462950 // Must be ZIR errors. Note that this may include AST errors.
29472951 // addZirErrorMessages asserts that the tree is loaded.
......@@ -2950,7 +2954,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
29502954 }
29512955 }
29522956 for (zcu.failed_embed_files.values()) |error_msg| {
2953 try addModuleErrorMsg(zcu, &bundle, error_msg.*);
2957 try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
29542958 }
29552959 for (zcu.failed_analysis.keys(), zcu.failed_analysis.values()) |anal_unit, error_msg| {
29562960 const decl_index = switch (anal_unit.unwrap()) {
......@@ -2962,7 +2966,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
29622966 // We'll try again once parsing succeeds.
29632967 if (!zcu.declFileScope(decl_index).okToReportErrors()) continue;
29642968
2965 try addModuleErrorMsg(zcu, &bundle, error_msg.*);
2969 try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
29662970 if (zcu.cimport_errors.get(anal_unit)) |errors| {
29672971 for (errors.getMessages()) |err_msg_index| {
29682972 const err_msg = errors.getErrorMessage(err_msg_index);
......@@ -2989,12 +2993,12 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
29892993 // Skip errors for Decls within files that had a parse failure.
29902994 // We'll try again once parsing succeeds.
29912995 if (zcu.declFileScope(decl_index).okToReportErrors()) {
2992 try addModuleErrorMsg(zcu, &bundle, error_msg.*);
2996 try addModuleErrorMsg(zcu, &bundle, error_msg.*, &all_references);
29932997 }
29942998 }
29952999 }
29963000 for (zcu.failed_exports.values()) |value| {
2997 try addModuleErrorMsg(zcu, &bundle, value.*);
3001 try addModuleErrorMsg(zcu, &bundle, value.*, &all_references);
29983002 }
29993003
30003004 const actual_error_count = zcu.global_error_set.entries.len - 1;
......@@ -3051,6 +3055,9 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
30513055
30523056 if (comp.module) |zcu| {
30533057 if (bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) {
3058 var all_references = try zcu.resolveReferences();
3059 defer all_references.deinit(gpa);
3060
30543061 const values = zcu.compile_log_sources.values();
30553062 // First one will be the error; subsequent ones will be notes.
30563063 const src_loc = values[0].src().upgrade(zcu);
......@@ -3068,7 +3075,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
30683075 };
30693076 }
30703077
3071 try addModuleErrorMsg(zcu, &bundle, err_msg);
3078 try addModuleErrorMsg(zcu, &bundle, err_msg, &all_references);
30723079 }
30733080 }
30743081
......@@ -3124,7 +3131,12 @@ pub const ErrorNoteHashContext = struct {
31243131 }
31253132};
31263133
3127pub fn addModuleErrorMsg(mod: *Module, eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void {
3134pub fn addModuleErrorMsg(
3135 mod: *Module,
3136 eb: *ErrorBundle.Wip,
3137 module_err_msg: Module.ErrorMsg,
3138 all_references: *const std.AutoHashMapUnmanaged(InternPool.AnalUnit, Zcu.ResolvedReference),
3139) !void {
31283140 const gpa = eb.gpa;
31293141 const ip = &mod.intern_pool;
31303142 const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| {
......@@ -3145,39 +3157,49 @@ pub fn addModuleErrorMsg(mod: *Module, eb: *ErrorBundle.Wip, module_err_msg: Mod
31453157 var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .{};
31463158 defer ref_traces.deinit(gpa);
31473159
3148 const remaining_references: ?u32 = remaining: {
3149 if (mod.comp.reference_trace) |_| {
3150 if (module_err_msg.hidden_references > 0) break :remaining module_err_msg.hidden_references;
3151 } else {
3152 if (module_err_msg.reference_trace.len > 0) break :remaining 0;
3160 if (module_err_msg.reference_trace_root.unwrap()) |rt_root| {
3161 var seen: std.AutoHashMapUnmanaged(InternPool.AnalUnit, void) = .{};
3162 defer seen.deinit(gpa);
3163
3164 const max_references = mod.comp.reference_trace orelse Sema.default_reference_trace_len;
3165
3166 var referenced_by = rt_root;
3167 while (all_references.get(referenced_by)) |ref| {
3168 const gop = try seen.getOrPut(gpa, ref.referencer);
3169 if (gop.found_existing) break;
3170 if (ref_traces.items.len < max_references) {
3171 const src = ref.src.upgrade(mod);
3172 const source = try src.file_scope.getSource(gpa);
3173 const span = try src.span(gpa);
3174 const loc = std.zig.findLineColumn(source.bytes, span.main);
3175 const rt_file_path = try src.file_scope.fullPath(gpa);
3176 const name = switch (ref.referencer.unwrap()) {
3177 .decl => |d| mod.declPtr(d).name,
3178 .func => |f| mod.funcOwnerDeclPtr(f).name,
3179 };
3180 try ref_traces.append(gpa, .{
3181 .decl_name = try eb.addString(name.toSlice(ip)),
3182 .src_loc = try eb.addSourceLocation(.{
3183 .src_path = try eb.addString(rt_file_path),
3184 .span_start = span.start,
3185 .span_main = span.main,
3186 .span_end = span.end,
3187 .line = @intCast(loc.line),
3188 .column = @intCast(loc.column),
3189 .source_line = 0,
3190 }),
3191 });
3192 }
3193 referenced_by = ref.referencer;
31533194 }
3154 break :remaining null;
3155 };
3156 try ref_traces.ensureTotalCapacityPrecise(gpa, module_err_msg.reference_trace.len +
3157 @intFromBool(remaining_references != null));
31583195
3159 for (module_err_msg.reference_trace) |module_reference| {
3160 const source = try module_reference.src_loc.file_scope.getSource(gpa);
3161 const span = try module_reference.src_loc.span(gpa);
3162 const loc = std.zig.findLineColumn(source.bytes, span.main);
3163 const rt_file_path = try module_reference.src_loc.file_scope.fullPath(gpa);
3164 defer gpa.free(rt_file_path);
3165 ref_traces.appendAssumeCapacity(.{
3166 .decl_name = try eb.addString(module_reference.decl.toSlice(ip)),
3167 .src_loc = try eb.addSourceLocation(.{
3168 .src_path = try eb.addString(rt_file_path),
3169 .span_start = span.start,
3170 .span_main = span.main,
3171 .span_end = span.end,
3172 .line = @intCast(loc.line),
3173 .column = @intCast(loc.column),
3174 .source_line = 0,
3175 }),
3176 });
3196 if (seen.count() > ref_traces.items.len) {
3197 try ref_traces.append(gpa, .{
3198 .decl_name = @intCast(seen.count() - ref_traces.items.len),
3199 .src_loc = .none,
3200 });
3201 }
31773202 }
3178 if (remaining_references) |remaining| ref_traces.appendAssumeCapacity(
3179 .{ .decl_name = remaining, .src_loc = .none },
3180 );
31813203
31823204 const src_loc = try eb.addSourceLocation(.{
31833205 .src_path = try eb.addString(file_path),
src/Sema.zig+72-95
......@@ -121,6 +121,11 @@ comptime_allocs: std.ArrayListUnmanaged(ComptimeAlloc) = .{},
121121/// these are flushed to `Zcu.single_exports` or `Zcu.multi_exports`.
122122exports: std.ArrayListUnmanaged(Zcu.Export) = .{},
123123
124/// All references registered so far by this `Sema`. This is a temporary duplicate
125/// of data stored in `Zcu.all_references`. It exists to avoid adding references to
126/// a given `AnalUnit` multiple times.
127references: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .{},
128
124129const MaybeComptimeAlloc = struct {
125130 /// The runtime index of the `alloc` instruction.
126131 runtime_index: Value.RuntimeIndex,
......@@ -2472,87 +2477,57 @@ pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Module.Error
24722477 @setCold(true);
24732478 const gpa = sema.gpa;
24742479 const mod = sema.mod;
2480 const ip = &mod.intern_pool;
24752481
2476 ref: {
2477 errdefer err_msg.destroy(gpa);
2482 if (build_options.enable_debug_extensions and mod.comp.debug_compile_errors) {
2483 var all_references = mod.resolveReferences() catch @panic("out of memory");
2484 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2485 wip_errors.init(gpa) catch @panic("out of memory");
2486 Compilation.addModuleErrorMsg(mod, &wip_errors, err_msg.*, &all_references) catch unreachable;
2487 std.debug.print("compile error during Sema:\n", .{});
2488 var error_bundle = wip_errors.toOwnedBundle("") catch unreachable;
2489 error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
2490 crash_report.compilerPanic("unexpected compile error occurred", null, null);
2491 }
24782492
2479 if (build_options.enable_debug_extensions and mod.comp.debug_compile_errors) {
2480 var wip_errors: std.zig.ErrorBundle.Wip = undefined;
2481 wip_errors.init(gpa) catch unreachable;
2482 Compilation.addModuleErrorMsg(mod, &wip_errors, err_msg.*) catch unreachable;
2483 std.debug.print("compile error during Sema:\n", .{});
2484 var error_bundle = wip_errors.toOwnedBundle("") catch unreachable;
2485 error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
2486 crash_report.compilerPanic("unexpected compile error occurred", null, null);
2493 if (block) |start_block| {
2494 var block_it = start_block;
2495 while (block_it.inlining) |inlining| {
2496 try sema.errNote(
2497 inlining.call_src,
2498 err_msg,
2499 "called from here",
2500 .{},
2501 );
2502 block_it = inlining.call_block;
24872503 }
2504 }
24882505
2489 try mod.failed_analysis.ensureUnusedCapacity(gpa, 1);
2490 try mod.failed_files.ensureUnusedCapacity(gpa, 1);
2491
2492 if (block) |start_block| {
2493 var block_it = start_block;
2494 while (block_it.inlining) |inlining| {
2495 try sema.errNote(
2496 inlining.call_src,
2497 err_msg,
2498 "called from here",
2499 .{},
2500 );
2501 block_it = inlining.call_block;
2502 }
2503
2504 const max_references = refs: {
2505 if (mod.comp.reference_trace) |num| break :refs num;
2506 // Do not add multiple traces without explicit request.
2507 if (mod.failed_analysis.count() > 0) break :ref;
2508 break :refs default_reference_trace_len;
2509 };
2506 const use_ref_trace = if (mod.comp.reference_trace) |n| n > 0 else mod.failed_analysis.count() == 0;
2507 if (use_ref_trace) {
2508 err_msg.reference_trace_root = sema.ownerUnit().toOptional();
2509 }
25102510
2511 var referenced_by = if (sema.owner_func_index != .none)
2512 mod.funcOwnerDeclIndex(sema.owner_func_index)
2513 else
2514 sema.owner_decl_index;
2515 var reference_stack = std.ArrayList(Module.ErrorMsg.Trace).init(gpa);
2516 defer reference_stack.deinit();
2517
2518 // Avoid infinite loops.
2519 var seen = std.AutoHashMap(InternPool.DeclIndex, void).init(gpa);
2520 defer seen.deinit();
2521
2522 while (mod.reference_table.get(referenced_by)) |ref| {
2523 const gop = try seen.getOrPut(ref.referencer);
2524 if (gop.found_existing) break;
2525 if (reference_stack.items.len < max_references) {
2526 const decl = mod.declPtr(ref.referencer);
2527 try reference_stack.append(.{
2528 .decl = decl.name,
2529 .src_loc = ref.src.upgrade(mod),
2530 });
2531 }
2532 referenced_by = ref.referencer;
2533 }
2534 err_msg.reference_trace = try reference_stack.toOwnedSlice();
2535 err_msg.hidden_references = @intCast(seen.count() -| max_references);
2536 }
2511 const gop = try mod.failed_analysis.getOrPut(gpa, sema.ownerUnit());
2512 if (gop.found_existing) {
2513 // If there are multiple errors for the same Decl, prefer the first one added.
2514 sema.err = null;
2515 err_msg.destroy(gpa);
2516 } else {
2517 sema.err = err_msg;
2518 gop.value_ptr.* = err_msg;
25372519 }
2538 const ip = &mod.intern_pool;
2520
25392521 if (sema.owner_func_index != .none) {
25402522 ip.funcAnalysis(sema.owner_func_index).state = .sema_failure;
25412523 } else {
25422524 sema.owner_decl.analysis = .sema_failure;
25432525 }
2526
25442527 if (sema.func_index != .none) {
25452528 ip.funcAnalysis(sema.func_index).state = .sema_failure;
25462529 }
2547 const gop = mod.failed_analysis.getOrPutAssumeCapacity(sema.ownerUnit());
2548 if (gop.found_existing) {
2549 // If there are multiple errors for the same Decl, prefer the first one added.
2550 sema.err = null;
2551 err_msg.destroy(gpa);
2552 } else {
2553 sema.err = err_msg;
2554 gop.value_ptr.* = err_msg;
2555 }
2530
25562531 return error.AnalysisFail;
25572532}
25582533
......@@ -4235,6 +4210,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
42354210 if (mod.intern_pool.isFuncBody(val)) {
42364211 const ty = Type.fromInterned(mod.intern_pool.typeOf(val));
42374212 if (try sema.fnHasRuntimeBits(ty)) {
4213 try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = val }));
42384214 try mod.ensureFuncBodyAnalysisQueued(val);
42394215 }
42404216 }
......@@ -6395,6 +6371,7 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
63956371 } else try sema.lookupIdentifier(block, operand_src, decl_name);
63966372 const options = try sema.resolveExportOptions(block, options_src, extra.options);
63976373 {
6374 try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = decl_index }));
63986375 try sema.ensureDeclAnalyzed(decl_index);
63996376 const exported_decl = mod.declPtr(decl_index);
64006377 if (exported_decl.val.getFunction(mod)) |function| {
......@@ -6446,6 +6423,7 @@ pub fn analyzeExport(
64466423 if (options.linkage == .internal)
64476424 return;
64486425
6426 try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = exported_decl_index }));
64496427 try sema.ensureDeclAnalyzed(exported_decl_index);
64506428 const exported_decl = mod.declPtr(exported_decl_index);
64516429 const export_ty = exported_decl.typeOf(mod);
......@@ -6468,7 +6446,7 @@ pub fn analyzeExport(
64686446 return sema.fail(block, src, "export target cannot be extern", .{});
64696447 }
64706448
6471 try sema.maybeQueueFuncBodyAnalysis(exported_decl_index);
6449 try sema.maybeQueueFuncBodyAnalysis(src, exported_decl_index);
64726450
64736451 try sema.exports.append(gpa, .{
64746452 .opts = options,
......@@ -6699,8 +6677,7 @@ fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
66996677 .no_embedded_nulls,
67006678 );
67016679 const decl_index = try sema.lookupIdentifier(block, src, decl_name);
6702 try sema.addReferencedBy(src, decl_index);
6703 return sema.analyzeDeclRef(decl_index);
6680 return sema.analyzeDeclRef(src, decl_index);
67046681}
67056682
67066683fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
......@@ -7903,6 +7880,7 @@ fn analyzeCall(
79037880
79047881 if (try sema.resolveValue(func)) |func_val| {
79057882 if (mod.intern_pool.isFuncBody(func_val.toIntern())) {
7883 try sema.addReferenceEntry(call_src, AnalUnit.wrap(.{ .func = func_val.toIntern() }));
79067884 try mod.ensureFuncBodyAnalysisQueued(func_val.toIntern());
79077885 }
79087886 }
......@@ -8339,8 +8317,6 @@ fn instantiateGenericCall(
83398317 const callee = mod.funcInfo(callee_index);
83408318 callee.branchQuota(ip).* = @max(callee.branchQuota(ip).*, sema.branch_quota);
83418319
8342 try sema.addReferencedBy(call_src, callee.owner_decl);
8343
83448320 // Make a runtime call to the new function, making sure to omit the comptime args.
83458321 const func_ty = Type.fromInterned(callee.ty);
83468322 const func_ty_info = mod.typeToFunc(func_ty).?;
......@@ -8366,6 +8342,7 @@ fn instantiateGenericCall(
83668342 ip.funcAnalysis(sema.owner_func_index).calls_or_awaits_errorable_fn = true;
83678343 }
83688344
8345 try sema.addReferenceEntry(call_src, AnalUnit.wrap(.{ .func = callee_index }));
83698346 try mod.ensureFuncBodyAnalysisQueued(callee_index);
83708347
83718348 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + runtime_args.items.len);
......@@ -17479,7 +17456,7 @@ fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
1747917456 .@"comptime" => |index| return Air.internedToRef(index),
1748017457 .runtime => |index| index,
1748117458 .decl_val => |decl_index| return sema.analyzeDeclVal(block, src, decl_index),
17482 .decl_ref => |decl_index| return sema.analyzeDeclRef(decl_index),
17459 .decl_ref => |decl_index| return sema.analyzeDeclRef(src, decl_index),
1748317460 };
1748417461
1748517462 // The comptime case is handled already above. Runtime case below.
......@@ -27673,7 +27650,6 @@ fn fieldCallBind(
2767327650 const decl_idx = (try sema.namespaceLookup(block, src, namespace, field_name)) orelse
2767427651 break :found_decl null;
2767527652
27676 try sema.addReferencedBy(src, decl_idx);
2767727653 const decl_val = try sema.analyzeDeclVal(block, src, decl_idx);
2767827654 const decl_type = sema.typeOf(decl_val);
2767927655 if (mod.typeToFunc(decl_type)) |func_type| f: {
......@@ -27829,8 +27805,7 @@ fn namespaceLookupRef(
2782927805 decl_name: InternPool.NullTerminatedString,
2783027806) CompileError!?Air.Inst.Ref {
2783127807 const decl = (try sema.namespaceLookup(block, src, opt_namespace, decl_name)) orelse return null;
27832 try sema.addReferencedBy(src, decl);
27833 return try sema.analyzeDeclRef(decl);
27808 return try sema.analyzeDeclRef(src, decl);
2783427809}
2783527810
2783627811fn namespaceLookupVal(
......@@ -28968,7 +28943,7 @@ fn coerceExtra(
2896828943 if (inst_ty.zigTypeTag(zcu) == .Fn) {
2896928944 const fn_val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
2897028945 const fn_decl = fn_val.pointerDecl(zcu).?;
28971 const inst_as_ptr = try sema.analyzeDeclRef(fn_decl);
28946 const inst_as_ptr = try sema.analyzeDeclRef(inst_src, fn_decl);
2897228947 return sema.coerce(block, dest_ty, inst_as_ptr, inst_src);
2897328948 }
2897428949
......@@ -30521,7 +30496,7 @@ fn coerceVarArgParam(
3052130496 .Fn => fn_ptr: {
3052230497 const fn_val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
3052330498 const fn_decl = fn_val.pointerDecl(mod).?;
30524 break :fn_ptr try sema.analyzeDeclRef(fn_decl);
30499 break :fn_ptr try sema.analyzeDeclRef(inst_src, fn_decl);
3052530500 },
3052630501 .Array => return sema.fail(block, inst_src, "arrays must be passed by reference to variadic function", .{}),
3052730502 .Float => float: {
......@@ -31748,11 +31723,10 @@ fn analyzeDeclVal(
3174831723 src: LazySrcLoc,
3174931724 decl_index: InternPool.DeclIndex,
3175031725) CompileError!Air.Inst.Ref {
31751 try sema.addReferencedBy(src, decl_index);
3175231726 if (sema.decl_val_table.get(decl_index)) |result| {
3175331727 return result;
3175431728 }
31755 const decl_ref = try sema.analyzeDeclRefInner(decl_index, false);
31729 const decl_ref = try sema.analyzeDeclRefInner(src, decl_index, false);
3175631730 const result = try sema.analyzeLoad(block, src, decl_ref, src);
3175731731 if (result.toInterned() != null) {
3175831732 if (!block.is_typeof) {
......@@ -31762,18 +31736,18 @@ fn analyzeDeclVal(
3176231736 return result;
3176331737}
3176431738
31765fn addReferencedBy(
31739fn addReferenceEntry(
3176631740 sema: *Sema,
3176731741 src: LazySrcLoc,
31768 decl_index: InternPool.DeclIndex,
31742 referenced_unit: AnalUnit,
3176931743) !void {
3177031744 if (sema.mod.comp.reference_trace == 0) return;
31771 try sema.mod.reference_table.put(sema.gpa, decl_index, .{
31772 // TODO: this can make the reference trace suboptimal. This will be fixed
31773 // once the reference table is reworked for incremental compilation.
31774 .referencer = sema.owner_decl_index,
31775 .src = src,
31776 });
31745 const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);
31746 if (gop.found_existing) return;
31747 // TODO: we need to figure out how to model inline calls here.
31748 // They aren't references in the analysis sense, but ought to show up in the reference trace!
31749 // Would representing inline calls in the reference table cause excessive memory usage?
31750 try sema.mod.addUnitReference(sema.ownerUnit(), referenced_unit, src);
3177731751}
3177831752
3177931753pub fn ensureDeclAnalyzed(sema: *Sema, decl_index: InternPool.DeclIndex) CompileError!void {
......@@ -31823,16 +31797,17 @@ fn optRefValue(sema: *Sema, opt_val: ?Value) !Value {
3182331797 } })));
3182431798}
3182531799
31826fn analyzeDeclRef(sema: *Sema, decl_index: InternPool.DeclIndex) CompileError!Air.Inst.Ref {
31827 return sema.analyzeDeclRefInner(decl_index, true);
31800fn analyzeDeclRef(sema: *Sema, src: LazySrcLoc, decl_index: InternPool.DeclIndex) CompileError!Air.Inst.Ref {
31801 return sema.analyzeDeclRefInner(src, decl_index, true);
3182831802}
3182931803
3183031804/// Analyze a reference to the decl at the given index. Ensures the underlying decl is analyzed, but
3183131805/// only triggers analysis for function bodies if `analyze_fn_body` is true. If it's possible for a
3183231806/// decl_ref to end up in runtime code, the function body must be analyzed: `analyzeDeclRef` wraps
3183331807/// this function with `analyze_fn_body` set to true.
31834fn analyzeDeclRefInner(sema: *Sema, decl_index: InternPool.DeclIndex, analyze_fn_body: bool) CompileError!Air.Inst.Ref {
31808fn analyzeDeclRefInner(sema: *Sema, src: LazySrcLoc, decl_index: InternPool.DeclIndex, analyze_fn_body: bool) CompileError!Air.Inst.Ref {
3183531809 const mod = sema.mod;
31810 try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .decl = decl_index }));
3183631811 try sema.ensureDeclAnalyzed(decl_index);
3183731812
3183831813 const decl_val = try mod.declPtr(decl_index).valueOrFail();
......@@ -31853,7 +31828,7 @@ fn analyzeDeclRefInner(sema: *Sema, decl_index: InternPool.DeclIndex, analyze_fn
3185331828 },
3185431829 });
3185531830 if (analyze_fn_body) {
31856 try sema.maybeQueueFuncBodyAnalysis(decl_index);
31831 try sema.maybeQueueFuncBodyAnalysis(src, decl_index);
3185731832 }
3185831833 return Air.internedToRef((try mod.intern(.{ .ptr = .{
3185931834 .ty = ptr_ty.toIntern(),
......@@ -31862,12 +31837,13 @@ fn analyzeDeclRefInner(sema: *Sema, decl_index: InternPool.DeclIndex, analyze_fn
3186231837 } })));
3186331838}
3186431839
31865fn maybeQueueFuncBodyAnalysis(sema: *Sema, decl_index: InternPool.DeclIndex) !void {
31840fn maybeQueueFuncBodyAnalysis(sema: *Sema, src: LazySrcLoc, decl_index: InternPool.DeclIndex) !void {
3186631841 const mod = sema.mod;
3186731842 const decl = mod.declPtr(decl_index);
3186831843 const decl_val = try decl.valueOrFail();
3186931844 if (!mod.intern_pool.isFuncBody(decl_val.toIntern())) return;
3187031845 if (!try sema.fnHasRuntimeBits(decl_val.typeOf(mod))) return;
31846 try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = decl_val.toIntern() }));
3187131847 try mod.ensureFuncBodyAnalysisQueued(decl_val.toIntern());
3187231848}
3187331849
......@@ -31882,8 +31858,8 @@ fn analyzeRef(
3188231858
3188331859 if (try sema.resolveValue(operand)) |val| {
3188431860 switch (mod.intern_pool.indexToKey(val.toIntern())) {
31885 .extern_func => |extern_func| return sema.analyzeDeclRef(extern_func.decl),
31886 .func => |func| return sema.analyzeDeclRef(func.owner_decl),
31861 .extern_func => |extern_func| return sema.analyzeDeclRef(src, extern_func.decl),
31862 .func => |func| return sema.analyzeDeclRef(src, func.owner_decl),
3188731863 else => return anonDeclRef(sema, val.toIntern()),
3188831864 }
3188931865 }
......@@ -35834,6 +35810,7 @@ fn resolveInferredErrorSet(
3583435810 }
3583535811 // In this case we are dealing with the actual InferredErrorSet object that
3583635812 // corresponds to the function, not one created to track an inline/comptime call.
35813 try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = func_index }));
3583735814 try sema.ensureFuncBodyAnalyzed(func_index);
3583835815 }
3583935816
src/Zcu.zig+100-49
......@@ -179,10 +179,15 @@ test_functions: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{},
179179/// TODO: the key here will be a `Cau.Index`.
180180global_assembly: std.AutoArrayHashMapUnmanaged(Decl.Index, []u8) = .{},
181181
182reference_table: std.AutoHashMapUnmanaged(Decl.Index, struct {
183 referencer: Decl.Index,
184 src: LazySrcLoc,
185}) = .{},
182/// Key is the `AnalUnit` *performing* the reference. This representation allows
183/// incremental updates to quickly delete references caused by a specific `AnalUnit`.
184/// Value is index into `all_reference` of the first reference triggered by the unit.
185/// The `next` field on the `Reference` forms a linked list of all references
186/// triggered by the key `AnalUnit`.
187reference_table: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{},
188all_references: std.ArrayListUnmanaged(Reference) = .{},
189/// Freelist of indices in `all_references`.
190free_references: std.ArrayListUnmanaged(u32) = .{},
186191
187192panic_messages: [PanicId.len]Decl.OptionalIndex = .{.none} ** PanicId.len,
188193/// The panic function body.
......@@ -290,44 +295,14 @@ pub const Export = struct {
290295 }
291296};
292297
293const ValueArena = struct {
294 state: std.heap.ArenaAllocator.State,
295 state_acquired: ?*std.heap.ArenaAllocator.State = null,
296
297 /// If this ValueArena replaced an existing one during re-analysis, this is the previous instance
298 prev: ?*ValueArena = null,
299
300 /// Returns an allocator backed by either promoting `state`, or by the existing ArenaAllocator
301 /// that has already promoted `state`. `out_arena_allocator` provides storage for the initial promotion,
302 /// and must live until the matching call to release().
303 pub fn acquire(self: *ValueArena, child_allocator: Allocator, out_arena_allocator: *std.heap.ArenaAllocator) Allocator {
304 if (self.state_acquired) |state_acquired| {
305 return @as(*std.heap.ArenaAllocator, @fieldParentPtr("state", state_acquired)).allocator();
306 }
307
308 out_arena_allocator.* = self.state.promote(child_allocator);
309 self.state_acquired = &out_arena_allocator.state;
310 return out_arena_allocator.allocator();
311 }
312
313 /// Releases the allocator acquired by `acquire. `arena_allocator` must match the one passed to `acquire`.
314 pub fn release(self: *ValueArena, arena_allocator: *std.heap.ArenaAllocator) void {
315 if (@as(*std.heap.ArenaAllocator, @fieldParentPtr("state", self.state_acquired.?)) == arena_allocator) {
316 self.state = self.state_acquired.?.*;
317 self.state_acquired = null;
318 }
319 }
320
321 pub fn deinit(self: ValueArena, child_allocator: Allocator) void {
322 assert(self.state_acquired == null);
323
324 const prev = self.prev;
325 self.state.promote(child_allocator).deinit();
326
327 if (prev) |p| {
328 p.deinit(child_allocator);
329 }
330 }
298pub const Reference = struct {
299 /// The `AnalUnit` whose semantic analysis was triggered by this reference.
300 referenced: AnalUnit,
301 /// Index into `all_references` of the next `Reference` triggered by the same `AnalUnit`.
302 /// `std.math.maxInt(u32)` is the sentinel.
303 next: u32,
304 /// The source location of the reference.
305 src: LazySrcLoc,
331306};
332307
333308pub const Decl = struct {
......@@ -758,7 +733,7 @@ pub const File = struct {
758733 /// Whether this file is a part of multiple packages. This is an error condition which will be reported after AstGen.
759734 multi_pkg: bool = false,
760735 /// List of references to this file, used for multi-package errors.
761 references: std.ArrayListUnmanaged(Reference) = .{},
736 references: std.ArrayListUnmanaged(File.Reference) = .{},
762737 /// The hash of the path to this file, used to store `InternPool.TrackedInst`.
763738 path_digest: Cache.BinDigest,
764739
......@@ -925,7 +900,7 @@ pub const File = struct {
925900 }
926901
927902 /// Add a reference to this file during AstGen.
928 pub fn addReference(file: *File, mod: Module, ref: Reference) !void {
903 pub fn addReference(file: *File, mod: Module, ref: File.Reference) !void {
929904 // Don't add the same module root twice. Note that since we always add module roots at the
930905 // front of the references array (see below), this loop is actually O(1) on valid code.
931906 if (ref == .root) {
......@@ -1002,8 +977,7 @@ pub const ErrorMsg = struct {
1002977 src_loc: SrcLoc,
1003978 msg: []const u8,
1004979 notes: []ErrorMsg = &.{},
1005 reference_trace: []Trace = &.{},
1006 hidden_references: u32 = 0,
980 reference_trace_root: AnalUnit.Optional = .none,
1007981
1008982 pub const Trace = struct {
1009983 decl: InternPool.NullTerminatedString,
......@@ -1048,7 +1022,6 @@ pub const ErrorMsg = struct {
10481022 }
10491023 gpa.free(err_msg.notes);
10501024 gpa.free(err_msg.msg);
1051 gpa.free(err_msg.reference_trace);
10521025 err_msg.* = undefined;
10531026 }
10541027};
......@@ -2520,6 +2493,8 @@ pub fn deinit(zcu: *Zcu) void {
25202493 zcu.global_assembly.deinit(gpa);
25212494
25222495 zcu.reference_table.deinit(gpa);
2496 zcu.all_references.deinit(gpa);
2497 zcu.free_references.deinit(gpa);
25232498
25242499 {
25252500 var it = zcu.intern_pool.allocated_namespaces.iterator(0);
......@@ -3462,7 +3437,8 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
34623437 // The exports this Decl performs will be re-discovered, so we remove them here
34633438 // prior to re-analysis.
34643439 if (build_options.only_c) unreachable;
3465 mod.deleteUnitExports(AnalUnit.wrap(.{ .decl = decl_index }));
3440 mod.deleteUnitExports(decl_as_depender);
3441 mod.deleteUnitReferences(decl_as_depender);
34663442 }
34673443
34683444 const sema_result: SemaDeclResult = blk: {
......@@ -3591,7 +3567,8 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In
35913567 if (was_outdated) {
35923568 if (build_options.only_c) unreachable;
35933569 _ = zcu.outdated_ready.swapRemove(func_as_depender);
3594 zcu.deleteUnitExports(AnalUnit.wrap(.{ .func = func_index }));
3570 zcu.deleteUnitExports(func_as_depender);
3571 zcu.deleteUnitReferences(func_as_depender);
35953572 }
35963573
35973574 switch (func.analysis(ip).state) {
......@@ -4967,6 +4944,47 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void {
49674944 }
49684945}
49694946
4947/// Delete all references in `reference_table` which are caused by this `AnalUnit`.
4948/// Re-analysis of the `AnalUnit` will cause appropriate references to be recreated.
4949fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {
4950 const gpa = zcu.gpa;
4951
4952 const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse return;
4953 var idx = kv.value;
4954
4955 while (idx != std.math.maxInt(u32)) {
4956 zcu.free_references.append(gpa, idx) catch {
4957 // This space will be reused eventually, so we need not propagate this error.
4958 // Just leak it for now, and let GC reclaim it later on.
4959 return;
4960 };
4961 idx = zcu.all_references.items[idx].next;
4962 }
4963}
4964
4965pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit, ref_src: LazySrcLoc) Allocator.Error!void {
4966 const gpa = zcu.gpa;
4967
4968 try zcu.reference_table.ensureUnusedCapacity(gpa, 1);
4969
4970 const ref_idx = zcu.free_references.popOrNull() orelse idx: {
4971 _ = try zcu.all_references.addOne(gpa);
4972 break :idx zcu.all_references.items.len - 1;
4973 };
4974
4975 errdefer comptime unreachable;
4976
4977 const gop = zcu.reference_table.getOrPutAssumeCapacity(src_unit);
4978
4979 zcu.all_references.items[ref_idx] = .{
4980 .referenced = referenced_unit,
4981 .next = if (gop.found_existing) gop.value_ptr.* else std.math.maxInt(u32),
4982 .src = ref_src,
4983 };
4984
4985 gop.value_ptr.* = @intCast(ref_idx);
4986}
4987
49704988pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocator) SemaError!Air {
49714989 const tracy = trace(@src());
49724990 defer tracy.end();
......@@ -6447,3 +6465,36 @@ pub fn structPackedFieldBitOffset(
64476465 }
64486466 unreachable; // index out of bounds
64496467}
6468
6469pub const ResolvedReference = struct {
6470 referencer: AnalUnit,
6471 src: LazySrcLoc,
6472};
6473
6474/// Returns a mapping from an `AnalUnit` to where it is referenced.
6475/// TODO: in future, this must be adapted to traverse from roots of analysis. That way, we can
6476/// use the returned map to determine which units have become unreferenced in an incremental update.
6477pub fn resolveReferences(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ResolvedReference) {
6478 const gpa = zcu.gpa;
6479
6480 var result: std.AutoHashMapUnmanaged(AnalUnit, ResolvedReference) = .{};
6481 errdefer result.deinit(gpa);
6482
6483 // This is not a sufficient size, but a lower bound.
6484 try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));
6485
6486 for (zcu.reference_table.keys(), zcu.reference_table.values()) |referencer, first_ref_idx| {
6487 assert(first_ref_idx != std.math.maxInt(u32));
6488 var ref_idx = first_ref_idx;
6489 while (ref_idx != std.math.maxInt(u32)) {
6490 const ref = zcu.all_references.items[ref_idx];
6491 const gop = try result.getOrPut(gpa, ref.referenced);
6492 if (!gop.found_existing) {
6493 gop.value_ptr.* = .{ .referencer = referencer, .src = ref.src };
6494 }
6495 ref_idx = ref.next;
6496 }
6497 }
6498
6499 return result;
6500}