authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-04-22 18:04:52+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-04-22 18:04:52+01:00
log6a7ca4b8b0fcce9e5f6a4d3f799e83021929c975
treea6fe2551bef8d54f69b79e896e6bd8b56eac9b08
parentffd85ffcda3c36b2cda0783ab285ede8b0fc55af
parent8c9c24e09b8a1e1ea92a16edd654a4b27e9ecf94
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #23617 from mlugg/incr-fixes

incremental: fixes

8 files changed, 462 insertions(+), 67 deletions(-)

src/Compilation.zig+84-39
...@@ -2262,8 +2262,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2262,8 +2262,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2262 const pt: Zcu.PerThread = .activate(zcu, .main);2262 const pt: Zcu.PerThread = .activate(zcu, .main);
2263 defer pt.deactivate();2263 defer pt.deactivate();
22642264
2265 zcu.compile_log_text.shrinkAndFree(gpa, 0);
2266
2267 zcu.skip_analysis_this_update = false;2265 zcu.skip_analysis_this_update = false;
22682266
2269 // Make sure std.zig is inside the import_table. We unconditionally need2267 // Make sure std.zig is inside the import_table. We unconditionally need
...@@ -3323,30 +3321,15 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {...@@ -3323,30 +3321,15 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
3323 err: *?Error,3321 err: *?Error,
33243322
3325 const Error = @typeInfo(3323 const Error = @typeInfo(
3326 @typeInfo(@TypeOf(Zcu.SrcLoc.span)).@"fn".return_type.?,3324 @typeInfo(@TypeOf(Zcu.LazySrcLoc.lessThan)).@"fn".return_type.?,
3327 ).error_union.error_set;3325 ).error_union.error_set;
33283326
3329 pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {3327 pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool {
3330 if (ctx.err.*) |_| return lhs_index < rhs_index;3328 if (ctx.err.* != null) return lhs_index < rhs_index;
3331 const lhs_src_loc = ctx.errors[lhs_index].src_loc.upgradeOrLost(ctx.zcu) orelse {3329 return ctx.errors[lhs_index].src_loc.lessThan(ctx.errors[rhs_index].src_loc, ctx.zcu) catch |e| {
3332 // LHS source location lost, so should never be referenced. Just sort it to the end.
3333 return false;
3334 };
3335 const rhs_src_loc = ctx.errors[rhs_index].src_loc.upgradeOrLost(ctx.zcu) orelse {
3336 // RHS source location lost, so should never be referenced. Just sort it to the end.
3337 return true;
3338 };
3339 return if (lhs_src_loc.file_scope != rhs_src_loc.file_scope) std.mem.order(
3340 u8,
3341 lhs_src_loc.file_scope.sub_file_path,
3342 rhs_src_loc.file_scope.sub_file_path,
3343 ).compare(.lt) else (lhs_src_loc.span(ctx.zcu.gpa) catch |e| {
3344 ctx.err.* = e;
3345 return lhs_index < rhs_index;
3346 }).main < (rhs_src_loc.span(ctx.zcu.gpa) catch |e| {
3347 ctx.err.* = e;3330 ctx.err.* = e;
3348 return lhs_index < rhs_index;3331 return lhs_index < rhs_index;
3349 }).main;3332 };
3350 }3333 }
3351 };3334 };
33523335
...@@ -3450,28 +3433,76 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {...@@ -3450,28 +3433,76 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
34503433
3451 try comp.link_diags.addMessagesToBundle(&bundle, comp.bin_file);3434 try comp.link_diags.addMessagesToBundle(&bundle, comp.bin_file);
34523435
3453 if (comp.zcu) |zcu| {3436 const compile_log_text: []const u8 = compile_log_text: {
3454 if (!zcu.skip_analysis_this_update and bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) {3437 const zcu = comp.zcu orelse break :compile_log_text "";
3455 const values = zcu.compile_log_sources.values();3438 if (zcu.skip_analysis_this_update) break :compile_log_text "";
3456 // First one will be the error; subsequent ones will be notes.3439 if (zcu.compile_logs.count() == 0) break :compile_log_text "";
3457 const src_loc = values[0].src();3440
3458 const err_msg: Zcu.ErrorMsg = .{3441 // If there are no other errors, we include a "found compile log statement" error.
3459 .src_loc = src_loc,3442 // Otherwise, we just show the compile log output, with no error.
3460 .msg = "found compile log statement",3443 const include_compile_log_sources = bundle.root_list.items.len == 0;
3461 .notes = try gpa.alloc(Zcu.ErrorMsg, zcu.compile_log_sources.count() - 1),3444
3462 };3445 const refs = try zcu.resolveReferences();
3463 defer gpa.free(err_msg.notes);3446
3447 var messages: std.ArrayListUnmanaged(Zcu.ErrorMsg) = .empty;
3448 defer messages.deinit(gpa);
3449 for (zcu.compile_logs.keys(), zcu.compile_logs.values()) |logging_unit, compile_log| {
3450 if (!refs.contains(logging_unit)) continue;
3451 try messages.append(gpa, .{
3452 .src_loc = compile_log.src(),
3453 .msg = undefined, // populated later
3454 .notes = &.{},
3455 // We actually clear this later for most of these, but we populate
3456 // this field for now to avoid having to allocate more data to track
3457 // which compile log text this corresponds to.
3458 .reference_trace_root = logging_unit.toOptional(),
3459 });
3460 }
34643461
3465 for (values[1..], err_msg.notes) |src_info, *note| {3462 if (messages.items.len == 0) break :compile_log_text "";
3466 note.* = .{3463
3467 .src_loc = src_info.src(),3464 // Okay, there *are* referenced compile logs. Sort them into a consistent order.
3468 .msg = "also here",3465
3466 const SortContext = struct {
3467 err: *?Error,
3468 zcu: *Zcu,
3469 const Error = @typeInfo(
3470 @typeInfo(@TypeOf(Zcu.LazySrcLoc.lessThan)).@"fn".return_type.?,
3471 ).error_union.error_set;
3472 fn lessThan(ctx: @This(), lhs: Zcu.ErrorMsg, rhs: Zcu.ErrorMsg) bool {
3473 if (ctx.err.* != null) return false;
3474 return lhs.src_loc.lessThan(rhs.src_loc, ctx.zcu) catch |e| {
3475 ctx.err.* = e;
3476 return false;
3469 };3477 };
3470 }3478 }
3479 };
3480 var sort_err: ?SortContext.Error = null;
3481 std.mem.sort(Zcu.ErrorMsg, messages.items, @as(SortContext, .{ .err = &sort_err, .zcu = zcu }), SortContext.lessThan);
3482 if (sort_err) |e| return e;
3483
3484 var log_text: std.ArrayListUnmanaged(u8) = .empty;
3485 defer log_text.deinit(gpa);
3486
3487 // Index 0 will be the root message; the rest will be notes.
3488 // Only the actual message, i.e. index 0, will retain its reference trace.
3489 try appendCompileLogLines(&log_text, zcu, messages.items[0].reference_trace_root.unwrap().?);
3490 messages.items[0].notes = messages.items[1..];
3491 messages.items[0].msg = "found compile log statement";
3492 for (messages.items[1..]) |*note| {
3493 try appendCompileLogLines(&log_text, zcu, note.reference_trace_root.unwrap().?);
3494 note.reference_trace_root = .none; // notes don't have reference traces
3495 note.msg = "also here";
3496 }
34713497
3472 try addModuleErrorMsg(zcu, &bundle, err_msg);3498 // We don't actually include the error here if `!include_compile_log_sources`.
3499 // The sorting above was still necessary, though, to get `log_text` in the right order.
3500 if (include_compile_log_sources) {
3501 try addModuleErrorMsg(zcu, &bundle, messages.items[0]);
3473 }3502 }
3474 }3503
3504 break :compile_log_text try log_text.toOwnedSlice(gpa);
3505 };
34753506
3476 // TODO: eventually, this should be behind `std.debug.runtime_safety`. But right now, this is a3507 // TODO: eventually, this should be behind `std.debug.runtime_safety`. But right now, this is a
3477 // very common way for incremental compilation bugs to manifest, so let's always check it.3508 // very common way for incremental compilation bugs to manifest, so let's always check it.
...@@ -3497,10 +3528,24 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {...@@ -3497,10 +3528,24 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
3497 }3528 }
3498 };3529 };
34993530
3500 const compile_log_text = if (comp.zcu) |m| m.compile_log_text.items else "";
3501 return bundle.toOwnedBundle(compile_log_text);3531 return bundle.toOwnedBundle(compile_log_text);
3502}3532}
35033533
3534/// Writes all compile log lines belonging to `logging_unit` into `log_text` using `zcu.gpa`.
3535fn appendCompileLogLines(log_text: *std.ArrayListUnmanaged(u8), zcu: *Zcu, logging_unit: InternPool.AnalUnit) Allocator.Error!void {
3536 const gpa = zcu.gpa;
3537 const ip = &zcu.intern_pool;
3538 var opt_line_idx = zcu.compile_logs.get(logging_unit).?.first_line.toOptional();
3539 while (opt_line_idx.unwrap()) |line_idx| {
3540 const line = line_idx.get(zcu).*;
3541 opt_line_idx = line.next;
3542 const line_slice = line.data.toSlice(ip);
3543 try log_text.ensureUnusedCapacity(gpa, line_slice.len + 1);
3544 log_text.appendSliceAssumeCapacity(line_slice);
3545 log_text.appendAssumeCapacity('\n');
3546 }
3547}
3548
3504fn anyErrors(comp: *Compilation) bool {3549fn anyErrors(comp: *Compilation) bool {
3505 return (totalErrorCount(comp) catch return true) != 0;3550 return (totalErrorCount(comp) catch return true) != 0;
3506}3551}
src/Sema.zig+34-8
...@@ -5884,10 +5884,12 @@ fn zirCompileLog(...@@ -5884,10 +5884,12 @@ fn zirCompileLog(
5884) CompileError!Air.Inst.Ref {5884) CompileError!Air.Inst.Ref {
5885 const pt = sema.pt;5885 const pt = sema.pt;
5886 const zcu = pt.zcu;5886 const zcu = pt.zcu;
5887 const gpa = zcu.gpa;
5888
5889 var buf: std.ArrayListUnmanaged(u8) = .empty;
5890 defer buf.deinit(gpa);
58875891
5888 var managed = zcu.compile_log_text.toManaged(sema.gpa);5892 const writer = buf.writer(gpa);
5889 defer pt.zcu.compile_log_text = managed.moveToUnmanaged();
5890 const writer = managed.writer();
58915893
5892 const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand);5894 const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand);
5893 const src_node = extra.data.src_node;5895 const src_node = extra.data.src_node;
...@@ -5906,13 +5908,37 @@ fn zirCompileLog(...@@ -5906,13 +5908,37 @@ fn zirCompileLog(
5906 try writer.print("@as({}, [runtime value])", .{arg_ty.fmt(pt)});5908 try writer.print("@as({}, [runtime value])", .{arg_ty.fmt(pt)});
5907 }5909 }
5908 }5910 }
5909 try writer.print("\n", .{});
59105911
5911 const gop = try zcu.compile_log_sources.getOrPut(sema.gpa, sema.owner);5912 const line_data = try zcu.intern_pool.getOrPutString(gpa, pt.tid, buf.items, .no_embedded_nulls);
5912 if (!gop.found_existing) gop.value_ptr.* = .{5913
5913 .base_node_inst = block.src_base_inst,5914 const line_idx: Zcu.CompileLogLine.Index = if (zcu.free_compile_log_lines.pop()) |idx| idx: {
5914 .node_offset = src_node,5915 zcu.compile_log_lines.items[@intFromEnum(idx)] = .{
5916 .next = .none,
5917 .data = line_data,
5918 };
5919 break :idx idx;
5920 } else idx: {
5921 try zcu.compile_log_lines.append(gpa, .{
5922 .next = .none,
5923 .data = line_data,
5924 });
5925 break :idx @enumFromInt(zcu.compile_log_lines.items.len - 1);
5915 };5926 };
5927
5928 const gop = try zcu.compile_logs.getOrPut(gpa, sema.owner);
5929 if (gop.found_existing) {
5930 const prev_line = gop.value_ptr.last_line.get(zcu);
5931 assert(prev_line.next == .none);
5932 prev_line.next = line_idx.toOptional();
5933 gop.value_ptr.last_line = line_idx;
5934 } else {
5935 gop.value_ptr.* = .{
5936 .base_node_inst = block.src_base_inst,
5937 .node_offset = src_node,
5938 .first_line = line_idx,
5939 .last_line = line_idx,
5940 };
5941 }
5916 return .void_value;5942 return .void_value;
5917}5943}
59185944
src/Zcu.zig+77-9
...@@ -130,18 +130,23 @@ transitive_failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .emp...@@ -130,18 +130,23 @@ transitive_failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .emp
130/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator.130/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator.
131failed_codegen: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, *ErrorMsg) = .empty,131failed_codegen: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, *ErrorMsg) = .empty,
132failed_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, *ErrorMsg) = .empty,132failed_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, *ErrorMsg) = .empty,
133/// Keep track of one `@compileLog` callsite per `AnalUnit`.133/// Keep track of `@compileLog`s per `AnalUnit`.
134/// The value is the source location of the `@compileLog` call, convertible to a `LazySrcLoc`.134/// We track the source location of the first `@compileLog` call, and all logged lines as a linked list.
135compile_log_sources: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {135/// The list is singly linked, but we do track its tail for fast appends (optimizing many logs in one unit).
136compile_logs: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
136 base_node_inst: InternPool.TrackedInst.Index,137 base_node_inst: InternPool.TrackedInst.Index,
137 node_offset: Ast.Node.Offset,138 node_offset: Ast.Node.Offset,
139 first_line: CompileLogLine.Index,
140 last_line: CompileLogLine.Index,
138 pub fn src(self: @This()) LazySrcLoc {141 pub fn src(self: @This()) LazySrcLoc {
139 return .{142 return .{
140 .base_node_inst = self.base_node_inst,143 .base_node_inst = self.base_node_inst,
141 .offset = LazySrcLoc.Offset.nodeOffset(self.node_offset),144 .offset = LazySrcLoc.Offset.nodeOffset(self.node_offset),
142 };145 };
143 }146 }
144}) = .{},147}) = .empty,
148compile_log_lines: std.ArrayListUnmanaged(CompileLogLine) = .empty,
149free_compile_log_lines: std.ArrayListUnmanaged(CompileLogLine.Index) = .empty,
145/// Using a map here for consistency with the other fields here.150/// Using a map here for consistency with the other fields here.
146/// The ErrorMsg memory is owned by the `File`, using Module's general purpose allocator.151/// The ErrorMsg memory is owned by the `File`, using Module's general purpose allocator.
147failed_files: std.AutoArrayHashMapUnmanaged(*File, ?*ErrorMsg) = .empty,152failed_files: std.AutoArrayHashMapUnmanaged(*File, ?*ErrorMsg) = .empty,
...@@ -196,8 +201,6 @@ stage1_flags: packed struct {...@@ -196,8 +201,6 @@ stage1_flags: packed struct {
196 reserved: u2 = 0,201 reserved: u2 = 0,
197} = .{},202} = .{},
198203
199compile_log_text: std.ArrayListUnmanaged(u8) = .empty,
200
201test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty,204test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty,
202205
203global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty,206global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty,
...@@ -547,6 +550,31 @@ pub const Export = struct {...@@ -547,6 +550,31 @@ pub const Export = struct {
547 };550 };
548};551};
549552
553pub const CompileLogLine = struct {
554 next: Index.Optional,
555 /// Does *not* include the trailing newline.
556 data: InternPool.NullTerminatedString,
557 pub const Index = enum(u32) {
558 _,
559 pub fn get(idx: Index, zcu: *Zcu) *CompileLogLine {
560 return &zcu.compile_log_lines.items[@intFromEnum(idx)];
561 }
562 pub fn toOptional(idx: Index) Optional {
563 return @enumFromInt(@intFromEnum(idx));
564 }
565 pub const Optional = enum(u32) {
566 none = std.math.maxInt(u32),
567 _,
568 pub fn unwrap(opt: Optional) ?Index {
569 return switch (opt) {
570 .none => null,
571 _ => @enumFromInt(@intFromEnum(opt)),
572 };
573 }
574 };
575 };
576};
577
550pub const Reference = struct {578pub const Reference = struct {
551 /// The `AnalUnit` whose semantic analysis was triggered by this reference.579 /// The `AnalUnit` whose semantic analysis was triggered by this reference.
552 referenced: AnalUnit,580 referenced: AnalUnit,
...@@ -2464,6 +2492,30 @@ pub const LazySrcLoc = struct {...@@ -2464,6 +2492,30 @@ pub const LazySrcLoc = struct {
2464 .lazy = lazy.offset,2492 .lazy = lazy.offset,
2465 };2493 };
2466 }2494 }
2495
2496 /// Used to sort error messages, so that they're printed in a consistent order.
2497 /// If an error is returned, that error makes sorting impossible.
2498 pub fn lessThan(lhs_lazy: LazySrcLoc, rhs_lazy: LazySrcLoc, zcu: *Zcu) !bool {
2499 const lhs_src = lhs_lazy.upgradeOrLost(zcu) orelse {
2500 // LHS source location lost, so should never be referenced. Just sort it to the end.
2501 return false;
2502 };
2503 const rhs_src = rhs_lazy.upgradeOrLost(zcu) orelse {
2504 // RHS source location lost, so should never be referenced. Just sort it to the end.
2505 return true;
2506 };
2507 if (lhs_src.file_scope != rhs_src.file_scope) {
2508 return std.mem.order(
2509 u8,
2510 lhs_src.file_scope.sub_file_path,
2511 rhs_src.file_scope.sub_file_path,
2512 ).compare(.lt);
2513 }
2514
2515 const lhs_span = try lhs_src.span(zcu.gpa);
2516 const rhs_span = try rhs_src.span(zcu.gpa);
2517 return lhs_span.main < rhs_span.main;
2518 }
2467};2519};
24682520
2469pub const SemaError = error{ OutOfMemory, AnalysisFail };2521pub const SemaError = error{ OutOfMemory, AnalysisFail };
...@@ -2506,8 +2558,6 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2506,8 +2558,6 @@ pub fn deinit(zcu: *Zcu) void {
2506 }2558 }
2507 zcu.embed_table.deinit(gpa);2559 zcu.embed_table.deinit(gpa);
25082560
2509 zcu.compile_log_text.deinit(gpa);
2510
2511 zcu.local_zir_cache.handle.close();2561 zcu.local_zir_cache.handle.close();
2512 zcu.global_zir_cache.handle.close();2562 zcu.global_zir_cache.handle.close();
25132563
...@@ -2535,7 +2585,9 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2535,7 +2585,9 @@ pub fn deinit(zcu: *Zcu) void {
2535 }2585 }
2536 zcu.cimport_errors.deinit(gpa);2586 zcu.cimport_errors.deinit(gpa);
25372587
2538 zcu.compile_log_sources.deinit(gpa);2588 zcu.compile_logs.deinit(gpa);
2589 zcu.compile_log_lines.deinit(gpa);
2590 zcu.free_compile_log_lines.deinit(gpa);
25392591
2540 zcu.all_exports.deinit(gpa);2592 zcu.all_exports.deinit(gpa);
2541 zcu.free_exports.deinit(gpa);2593 zcu.free_exports.deinit(gpa);
...@@ -3412,6 +3464,22 @@ pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {...@@ -3412,6 +3464,22 @@ pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {
3412 }3464 }
3413}3465}
34143466
3467/// Delete all compile logs performed by this `AnalUnit`.
3468/// Re-analysis of the `AnalUnit` will cause logs to be rediscovered.
3469pub fn deleteUnitCompileLogs(zcu: *Zcu, anal_unit: AnalUnit) void {
3470 const kv = zcu.compile_logs.fetchSwapRemove(anal_unit) orelse return;
3471 const gpa = zcu.gpa;
3472 var opt_line_idx = kv.value.first_line.toOptional();
3473 while (opt_line_idx.unwrap()) |line_idx| {
3474 zcu.free_compile_log_lines.append(gpa, line_idx) catch {
3475 // This space will be reused eventually, so we need not propagate this error.
3476 // Just leak it for now, and let GC reclaim it later on.
3477 return;
3478 };
3479 opt_line_idx = line_idx.get(zcu).next;
3480 }
3481}
3482
3415pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit, ref_src: LazySrcLoc) Allocator.Error!void {3483pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit, ref_src: LazySrcLoc) Allocator.Error!void {
3416 const gpa = zcu.gpa;3484 const gpa = zcu.gpa;
34173485
src/Zcu/PerThread.zig+35-3
...@@ -599,6 +599,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized...@@ -599,6 +599,7 @@ pub fn ensureMemoizedStateUpToDate(pt: Zcu.PerThread, stage: InternPool.Memoized
599 _ = zcu.outdated_ready.swapRemove(unit);599 _ = zcu.outdated_ready.swapRemove(unit);
600 // No need for `deleteUnitExports` because we never export anything.600 // No need for `deleteUnitExports` because we never export anything.
601 zcu.deleteUnitReferences(unit);601 zcu.deleteUnitReferences(unit);
602 zcu.deleteUnitCompileLogs(unit);
602 if (zcu.failed_analysis.fetchSwapRemove(unit)) |kv| {603 if (zcu.failed_analysis.fetchSwapRemove(unit)) |kv| {
603 kv.value.destroy(gpa);604 kv.value.destroy(gpa);
604 }605 }
...@@ -749,6 +750,7 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU...@@ -749,6 +750,7 @@ pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeU
749 if (dev.env.supports(.incremental)) {750 if (dev.env.supports(.incremental)) {
750 zcu.deleteUnitExports(anal_unit);751 zcu.deleteUnitExports(anal_unit);
751 zcu.deleteUnitReferences(anal_unit);752 zcu.deleteUnitReferences(anal_unit);
753 zcu.deleteUnitCompileLogs(anal_unit);
752 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {754 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
753 kv.value.destroy(gpa);755 kv.value.destroy(gpa);
754 }756 }
...@@ -921,6 +923,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu...@@ -921,6 +923,7 @@ pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu
921 _ = zcu.outdated_ready.swapRemove(anal_unit);923 _ = zcu.outdated_ready.swapRemove(anal_unit);
922 zcu.deleteUnitExports(anal_unit);924 zcu.deleteUnitExports(anal_unit);
923 zcu.deleteUnitReferences(anal_unit);925 zcu.deleteUnitReferences(anal_unit);
926 zcu.deleteUnitCompileLogs(anal_unit);
924 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {927 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
925 kv.value.destroy(gpa);928 kv.value.destroy(gpa);
926 }929 }
...@@ -1293,6 +1296,7 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc...@@ -1293,6 +1296,7 @@ pub fn ensureNavTypeUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zc
1293 _ = zcu.outdated_ready.swapRemove(anal_unit);1296 _ = zcu.outdated_ready.swapRemove(anal_unit);
1294 zcu.deleteUnitExports(anal_unit);1297 zcu.deleteUnitExports(anal_unit);
1295 zcu.deleteUnitReferences(anal_unit);1298 zcu.deleteUnitReferences(anal_unit);
1299 zcu.deleteUnitCompileLogs(anal_unit);
1296 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {1300 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
1297 kv.value.destroy(gpa);1301 kv.value.destroy(gpa);
1298 }1302 }
...@@ -1527,6 +1531,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter...@@ -1527,6 +1531,7 @@ pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
1527 _ = zcu.outdated_ready.swapRemove(anal_unit);1531 _ = zcu.outdated_ready.swapRemove(anal_unit);
1528 zcu.deleteUnitExports(anal_unit);1532 zcu.deleteUnitExports(anal_unit);
1529 zcu.deleteUnitReferences(anal_unit);1533 zcu.deleteUnitReferences(anal_unit);
1534 zcu.deleteUnitCompileLogs(anal_unit);
1530 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {1535 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
1531 kv.value.destroy(gpa);1536 kv.value.destroy(gpa);
1532 }1537 }
...@@ -2837,6 +2842,11 @@ pub fn processExports(pt: Zcu.PerThread) !void {...@@ -2837,6 +2842,11 @@ pub fn processExports(pt: Zcu.PerThread) !void {
2837 const zcu = pt.zcu;2842 const zcu = pt.zcu;
2838 const gpa = zcu.gpa;2843 const gpa = zcu.gpa;
28392844
2845 if (zcu.single_exports.count() == 0 and zcu.multi_exports.count() == 0) {
2846 // We can avoid a call to `resolveReferences` in this case.
2847 return;
2848 }
2849
2840 // First, construct a mapping of every exported value and Nav to the indices of all its different exports.2850 // First, construct a mapping of every exported value and Nav to the indices of all its different exports.
2841 var nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;2851 var nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;
2842 var uav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;2852 var uav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;
...@@ -2857,8 +2867,18 @@ pub fn processExports(pt: Zcu.PerThread) !void {...@@ -2857,8 +2867,18 @@ pub fn processExports(pt: Zcu.PerThread) !void {
2857 // So, this ensureTotalCapacity serves as a reasonable (albeit very approximate) optimization.2867 // So, this ensureTotalCapacity serves as a reasonable (albeit very approximate) optimization.
2858 try nav_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count());2868 try nav_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count());
28592869
2860 for (zcu.single_exports.values()) |export_idx| {2870 const unit_references = try zcu.resolveReferences();
2871
2872 for (zcu.single_exports.keys(), zcu.single_exports.values()) |exporter, export_idx| {
2861 const exp = export_idx.ptr(zcu);2873 const exp = export_idx.ptr(zcu);
2874 if (!unit_references.contains(exporter)) {
2875 // This export might already have been sent to the linker on a previous update, in which case we need to delete it.
2876 // The linker export API should be modified to eliminate this call. #23616
2877 if (zcu.comp.bin_file) |lf| {
2878 lf.deleteExport(exp.exported, exp.opts.name);
2879 }
2880 continue;
2881 }
2862 const value_ptr, const found_existing = switch (exp.exported) {2882 const value_ptr, const found_existing = switch (exp.exported) {
2863 .nav => |nav| gop: {2883 .nav => |nav| gop: {
2864 const gop = try nav_exports.getOrPut(gpa, nav);2884 const gop = try nav_exports.getOrPut(gpa, nav);
...@@ -2873,8 +2893,19 @@ pub fn processExports(pt: Zcu.PerThread) !void {...@@ -2873,8 +2893,19 @@ pub fn processExports(pt: Zcu.PerThread) !void {
2873 try value_ptr.append(gpa, export_idx);2893 try value_ptr.append(gpa, export_idx);
2874 }2894 }
28752895
2876 for (zcu.multi_exports.values()) |info| {2896 for (zcu.multi_exports.keys(), zcu.multi_exports.values()) |exporter, info| {
2877 for (zcu.all_exports.items[info.index..][0..info.len], info.index..) |exp, export_idx| {2897 const exports = zcu.all_exports.items[info.index..][0..info.len];
2898 if (!unit_references.contains(exporter)) {
2899 // This export might already have been sent to the linker on a previous update, in which case we need to delete it.
2900 // The linker export API should be modified to eliminate this loop. #23616
2901 if (zcu.comp.bin_file) |lf| {
2902 for (exports) |exp| {
2903 lf.deleteExport(exp.exported, exp.opts.name);
2904 }
2905 }
2906 continue;
2907 }
2908 for (exports, info.index..) |exp, export_idx| {
2878 const value_ptr, const found_existing = switch (exp.exported) {2909 const value_ptr, const found_existing = switch (exp.exported) {
2879 .nav => |nav| gop: {2910 .nav => |nav| gop: {
2880 const gop = try nav_exports.getOrPut(gpa, nav);2911 const gop = try nav_exports.getOrPut(gpa, nav);
...@@ -3738,6 +3769,7 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError...@@ -3738,6 +3769,7 @@ pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError
3738 // reusing the memory which is currently being used to track this state.3769 // reusing the memory which is currently being used to track this state.
3739 zcu.deleteUnitExports(anal_unit);3770 zcu.deleteUnitExports(anal_unit);
3740 zcu.deleteUnitReferences(anal_unit);3771 zcu.deleteUnitReferences(anal_unit);
3772 zcu.deleteUnitCompileLogs(anal_unit);
3741 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {3773 if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
3742 kv.value.destroy(gpa);3774 kv.value.destroy(gpa);
3743 }3775 }
test/incremental/change_exports created+161
...@@ -0,0 +1,161 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
4
5#update=initial version
6#file=main.zig
7export fn foo() void {}
8const bar: u32 = 123;
9const other: u32 = 456;
10comptime {
11 @export(&bar, .{ .name = "bar" });
12}
13pub fn main() !void {
14 const S = struct {
15 extern fn foo() void;
16 extern const bar: u32;
17 };
18 S.foo();
19 try std.io.getStdOut().writer().print("{}\n", .{S.bar});
20}
21const std = @import("std");
22#expect_stdout="123\n"
23
24#update=add conflict
25#file=main.zig
26export fn foo() void {}
27const bar: u32 = 123;
28const other: u32 = 456;
29comptime {
30 @export(&bar, .{ .name = "bar" });
31 @export(&other, .{ .name = "foo" });
32}
33pub fn main() !void {
34 const S = struct {
35 extern fn foo() void;
36 extern const bar: u32;
37 extern const other: u32;
38 };
39 S.foo();
40 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
41}
42const std = @import("std");
43#expect_error=main.zig:6:5: error: exported symbol collision: foo
44#expect_error=main.zig:1:1: note: other symbol here
45
46#update=resolve conflict
47#file=main.zig
48export fn foo() void {}
49const bar: u32 = 123;
50const other: u32 = 456;
51comptime {
52 @export(&bar, .{ .name = "bar" });
53 @export(&other, .{ .name = "other" });
54}
55pub fn main() !void {
56 const S = struct {
57 extern fn foo() void;
58 extern const bar: u32;
59 extern const other: u32;
60 };
61 S.foo();
62 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
63}
64const std = @import("std");
65#expect_stdout="123 456\n"
66
67#update=put exports in decl
68#file=main.zig
69export fn foo() void {}
70const bar: u32 = 123;
71const other: u32 = 456;
72const does_exports = {
73 @export(&bar, .{ .name = "bar" });
74 @export(&other, .{ .name = "other" });
75};
76comptime {
77 _ = does_exports;
78}
79pub fn main() !void {
80 const S = struct {
81 extern fn foo() void;
82 extern const bar: u32;
83 extern const other: u32;
84 };
85 S.foo();
86 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
87}
88const std = @import("std");
89#expect_stdout="123 456\n"
90
91#update=remove reference to exporting decl
92#file=main.zig
93export fn foo() void {}
94const bar: u32 = 123;
95const other: u32 = 456;
96const does_exports = {
97 @export(&bar, .{ .name = "bar" });
98 @export(&other, .{ .name = "other" });
99};
100comptime {
101 //_ = does_exports;
102}
103pub fn main() !void {
104 const S = struct {
105 extern fn foo() void;
106 };
107 S.foo();
108}
109const std = @import("std");
110#expect_stdout=""
111
112#update=mark consts as export
113#file=main.zig
114export fn foo() void {}
115export const bar: u32 = 123;
116export const other: u32 = 456;
117const does_exports = {
118 @export(&bar, .{ .name = "bar" });
119 @export(&other, .{ .name = "other" });
120};
121comptime {
122 //_ = does_exports;
123}
124pub fn main() !void {
125 const S = struct {
126 extern fn foo() void;
127 extern const bar: u32;
128 extern const other: u32;
129 };
130 S.foo();
131 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
132}
133const std = @import("std");
134#expect_stdout="123 456\n"
135
136#update=reintroduce reference to exporting decl, introducing conflict
137#file=main.zig
138export fn foo() void {}
139export const bar: u32 = 123;
140export const other: u32 = 456;
141const does_exports = {
142 @export(&bar, .{ .name = "bar" });
143 @export(&other, .{ .name = "other" });
144};
145comptime {
146 _ = does_exports;
147}
148pub fn main() !void {
149 const S = struct {
150 extern fn foo() void;
151 extern const bar: u32;
152 extern const other: u32;
153 };
154 S.foo();
155 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
156}
157const std = @import("std");
158#expect_error=main.zig:5:5: error: exported symbol collision: bar
159#expect_error=main.zig:2:1: note: other symbol here
160#expect_error=main.zig:6:5: error: exported symbol collision: other
161#expect_error=main.zig:3:1: note: other symbol here
test/incremental/compile_error_then_log+2
...@@ -12,6 +12,7 @@ comptime {...@@ -12,6 +12,7 @@ comptime {
12 @compileLog("this is a log");12 @compileLog("this is a log");
13}13}
14#expect_error=main.zig:3:5: error: this is an error14#expect_error=main.zig:3:5: error: this is an error
15#expect_compile_log=@as(*const [13:0]u8, "this is a log")
1516
16#update=remove the compile error17#update=remove the compile error
17#file=main.zig18#file=main.zig
...@@ -23,3 +24,4 @@ comptime {...@@ -23,3 +24,4 @@ comptime {
23 @compileLog("this is a log");24 @compileLog("this is a log");
24}25}
25#expect_error=main.zig:6:5: error: found compile log statement26#expect_error=main.zig:6:5: error: found compile log statement
27#expect_compile_log=@as(*const [13:0]u8, "this is a log")
test/incremental/compile_log created+30
...@@ -0,0 +1,30 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
4#target=wasm32-wasi-selfhosted
5
6#update=initial version with no compile log
7#file=main.zig
8const std = @import("std");
9pub fn main() !void {
10 try std.io.getStdOut().writeAll("Hello, World!\n");
11}
12#expect_stdout="Hello, World!\n"
13
14#update=add compile log
15#file=main.zig
16const std = @import("std");
17pub fn main() !void {
18 try std.io.getStdOut().writeAll("Hello, World!\n");
19 @compileLog("this is a log");
20}
21#expect_error=main.zig:4:5: error: found compile log statement
22#expect_compile_log=@as(*const [13:0]u8, "this is a log")
23
24#update=remove compile log
25#file=main.zig
26const std = @import("std");
27pub fn main() !void {
28 try std.io.getStdOut().writeAll("Hello, World!\n");
29}
30#expect_stdout="Hello, World!\n"
tools/incr-check.zig+39-8
...@@ -341,9 +341,9 @@ const Eval = struct {...@@ -341,9 +341,9 @@ const Eval = struct {
341 }341 }
342342
343 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {343 fn checkErrorOutcome(eval: *Eval, update: Case.Update, error_bundle: std.zig.ErrorBundle) !void {
344 const expected_errors = switch (update.outcome) {344 const expected = switch (update.outcome) {
345 .unknown => return,345 .unknown => return,
346 .compile_errors => |expected_errors| expected_errors,346 .compile_errors => |ce| ce,
347 .stdout, .exit_code => {347 .stdout, .exit_code => {
348 const color: std.zig.Color = .auto;348 const color: std.zig.Color = .auto;
349 error_bundle.renderToStdErr(color.renderOptions());349 error_bundle.renderToStdErr(color.renderOptions());
...@@ -354,24 +354,30 @@ const Eval = struct {...@@ -354,24 +354,30 @@ const Eval = struct {
354 var expected_idx: usize = 0;354 var expected_idx: usize = 0;
355355
356 for (error_bundle.getMessages()) |err_idx| {356 for (error_bundle.getMessages()) |err_idx| {
357 if (expected_idx == expected_errors.len) {357 if (expected_idx == expected.errors.len) {
358 const color: std.zig.Color = .auto;358 const color: std.zig.Color = .auto;
359 error_bundle.renderToStdErr(color.renderOptions());359 error_bundle.renderToStdErr(color.renderOptions());
360 eval.fatal("update '{s}': more errors than expected", .{update.name});360 eval.fatal("update '{s}': more errors than expected", .{update.name});
361 }361 }
362 eval.checkOneError(update, error_bundle, expected_errors[expected_idx], false, err_idx);362 eval.checkOneError(update, error_bundle, expected.errors[expected_idx], false, err_idx);
363 expected_idx += 1;363 expected_idx += 1;
364364
365 for (error_bundle.getNotes(err_idx)) |note_idx| {365 for (error_bundle.getNotes(err_idx)) |note_idx| {
366 if (expected_idx == expected_errors.len) {366 if (expected_idx == expected.errors.len) {
367 const color: std.zig.Color = .auto;367 const color: std.zig.Color = .auto;
368 error_bundle.renderToStdErr(color.renderOptions());368 error_bundle.renderToStdErr(color.renderOptions());
369 eval.fatal("update '{s}': more error notes than expected", .{update.name});369 eval.fatal("update '{s}': more error notes than expected", .{update.name});
370 }370 }
371 eval.checkOneError(update, error_bundle, expected_errors[expected_idx], true, note_idx);371 eval.checkOneError(update, error_bundle, expected.errors[expected_idx], true, note_idx);
372 expected_idx += 1;372 expected_idx += 1;
373 }373 }
374 }374 }
375
376 if (!std.mem.eql(u8, error_bundle.getCompileLogOutput(), expected.compile_log_output)) {
377 const color: std.zig.Color = .auto;
378 error_bundle.renderToStdErr(color.renderOptions());
379 eval.fatal("update '{s}': unexpected compile log output", .{update.name});
380 }
375 }381 }
376382
377 fn checkOneError(383 fn checkOneError(
...@@ -634,7 +640,10 @@ const Case = struct {...@@ -634,7 +640,10 @@ const Case = struct {
634640
635 const Outcome = union(enum) {641 const Outcome = union(enum) {
636 unknown,642 unknown,
637 compile_errors: []const ExpectedError,643 compile_errors: struct {
644 errors: []const ExpectedError,
645 compile_log_output: []const u8,
646 },
638 stdout: []const u8,647 stdout: []const u8,
639 exit_code: u8,648 exit_code: u8,
640 };649 };
...@@ -759,7 +768,29 @@ const Case = struct {...@@ -759,7 +768,29 @@ const Case = struct {
759 try errors.append(arena, parseExpectedError(new_val, line_n));768 try errors.append(arena, parseExpectedError(new_val, line_n));
760 }769 }
761770
762 last_update.outcome = .{ .compile_errors = errors.items };771 var compile_log_output: std.ArrayListUnmanaged(u8) = .empty;
772 while (true) {
773 const next_line = it.peek() orelse break;
774 if (!std.mem.startsWith(u8, next_line, "#")) break;
775 var new_line_it = std.mem.splitScalar(u8, next_line, '=');
776 const new_key = new_line_it.first()[1..];
777 const new_val = std.mem.trimRight(u8, new_line_it.rest(), "\r");
778 if (new_val.len == 0) break;
779 if (!std.mem.eql(u8, new_key, "expect_compile_log")) break;
780
781 _ = it.next();
782 line_n += 1;
783 try compile_log_output.ensureUnusedCapacity(arena, new_val.len + 1);
784 compile_log_output.appendSliceAssumeCapacity(new_val);
785 compile_log_output.appendAssumeCapacity('\n');
786 }
787
788 last_update.outcome = .{ .compile_errors = .{
789 .errors = errors.items,
790 .compile_log_output = compile_log_output.items,
791 } };
792 } else if (std.mem.eql(u8, key, "expect_compile_log")) {
793 fatal("line {d}: 'expect_compile_log' must immediately follow 'expect_error'", .{line_n});
763 } else {794 } else {
764 fatal("line {d}: unrecognized key '{s}'", .{ line_n, key });795 fatal("line {d}: unrecognized key '{s}'", .{ line_n, key });
765 }796 }