authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-15 18:54:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-07-15 18:54:41-07:00
log60318a1e39897a0535cf7699c2e4ab6366d0687f
tree1e8945279c48013c6f1717946945437322e62002
parent888708ec8af9b60681ef14fb0a5c265f2a30b41f

frontend: move updateZirRefs to be single-threaded

for simplicity's sake. This makes it O(M) instead of O(N*M) where N is tracked insts and M is number of changed source files.

3 files changed, 148 insertions(+), 125 deletions(-)

src/Compilation.zig+6-1
......@@ -3596,6 +3596,11 @@ fn performAllTheWorkInner(
35963596
35973597 if (comp.module) |zcu| {
35983598 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = .main };
3599 if (comp.incremental) {
3600 const update_zir_refs_node = main_progress_node.start("Update ZIR References", 0);
3601 defer update_zir_refs_node.end();
3602 try pt.updateZirRefs();
3603 }
35993604 try reportMultiModuleErrors(pt);
36003605 try zcu.flushRetryableFailures();
36013606 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
......@@ -4306,7 +4311,7 @@ fn workerAstGenFile(
43064311 defer child_prog_node.end();
43074312
43084313 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };
4309 pt.astGenFile(file, file_index, path_digest, root_decl) catch |err| switch (err) {
4314 pt.astGenFile(file, path_digest, root_decl) catch |err| switch (err) {
43104315 error.AnalysisFail => return,
43114316 else => {
43124317 file.status = .retryable_failure;
src/Sema.zig+1-1
......@@ -6065,7 +6065,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
60656065
60666066 const path_digest = zcu.filePathDigest(result.file_index);
60676067 const root_decl = zcu.fileRootDecl(result.file_index);
6068 pt.astGenFile(result.file, result.file_index, path_digest, root_decl) catch |err|
6068 pt.astGenFile(result.file, path_digest, root_decl) catch |err|
60696069 return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
60706070
60716071 try pt.ensureFileAnalyzed(result.file_index);
src/Zcu/PerThread.zig+141-123
......@@ -60,10 +60,6 @@ pub fn destroyFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void {
6060pub fn astGenFile(
6161 pt: Zcu.PerThread,
6262 file: *Zcu.File,
63 /// This parameter is provided separately from `file` because it is not
64 /// safe to access `import_table` without a lock, and this index is needed
65 /// in the call to `updateZirRefs`.
66 file_index: Zcu.File.Index,
6763 path_digest: Cache.BinDigest,
6864 opt_root_decl: Zcu.Decl.OptionalIndex,
6965) !void {
......@@ -210,13 +206,18 @@ pub fn astGenFile(
210206
211207 pt.lockAndClearFileCompileError(file);
212208
213 // If the previous ZIR does not have compile errors, keep it around
214 // in case parsing or new ZIR fails. In case of successful ZIR update
215 // at the end of this function we will free it.
216 // We keep the previous ZIR loaded so that we can use it
217 // for the update next time it does not have any compile errors. This avoids
218 // needlessly tossing out semantic analysis work when an error is
219 // temporarily introduced.
209 // Previous ZIR is kept for two reasons:
210 //
211 // 1. In case an update to the file causes a Parse or AstGen failure, we
212 // need to compare two successful ZIR files in order to proceed with an
213 // incremental update. This avoids needlessly tossing out semantic
214 // analysis work when an error is temporarily introduced.
215 //
216 // 2. In order to detect updates, we need to iterate over the intern pool
217 // values while comparing old ZIR to new ZIR. This is better done in a
218 // single-threaded context, so we need to keep both versions around
219 // until that point in the pipeline. Previous ZIR data is freed after
220 // that.
220221 if (file.zir_loaded and !file.zir.hasCompileErrors()) {
221222 assert(file.prev_zir == null);
222223 const prev_zir_ptr = try gpa.create(Zir);
......@@ -320,14 +321,6 @@ pub fn astGenFile(
320321 return error.AnalysisFail;
321322 }
322323
323 if (file.prev_zir) |prev_zir| {
324 try pt.updateZirRefs(file, file_index, prev_zir.*);
325 // No need to keep previous ZIR.
326 prev_zir.deinit(gpa);
327 gpa.destroy(prev_zir);
328 file.prev_zir = null;
329 }
330
331324 if (opt_root_decl.unwrap()) |root_decl| {
332325 // The root of this file must be re-analyzed, since the file has changed.
333326 comp.mutex.lock();
......@@ -338,137 +331,162 @@ pub fn astGenFile(
338331 }
339332}
340333
341/// This is called from the AstGen thread pool, so must acquire
342/// the Compilation mutex when acting on shared state.
343fn updateZirRefs(pt: Zcu.PerThread, file: *Zcu.File, file_index: Zcu.File.Index, old_zir: Zir) !void {
334const UpdatedFile = struct {
335 file_index: Zcu.File.Index,
336 file: *Zcu.File,
337 inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index),
338};
339
340fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.ArrayListUnmanaged(UpdatedFile)) void {
341 for (updated_files.items) |*elem| elem.inst_map.deinit(gpa);
342 updated_files.deinit(gpa);
343}
344
345pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
346 assert(pt.tid == .main);
344347 const zcu = pt.zcu;
345348 const ip = &zcu.intern_pool;
346349 const gpa = zcu.gpa;
347 const new_zir = file.zir;
348
349 var inst_map: std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index) = .{};
350 defer inst_map.deinit(gpa);
351350
352 try Zcu.mapOldZirToNew(gpa, old_zir, new_zir, &inst_map);
351 // We need to visit every updated File for every TrackedInst in InternPool.
352 var updated_files: std.ArrayListUnmanaged(UpdatedFile) = .{};
353 defer cleanupUpdatedFiles(gpa, &updated_files);
354 for (zcu.import_table.values()) |file_index| {
355 const file = zcu.fileByIndex(file_index);
356 const old_zir = file.prev_zir orelse continue;
357 const new_zir = file.zir;
358 try updated_files.append(gpa, .{
359 .file_index = file_index,
360 .file = file,
361 .inst_map = .{},
362 });
363 const inst_map = &updated_files.items[updated_files.items.len - 1].inst_map;
364 try Zcu.mapOldZirToNew(gpa, old_zir.*, new_zir, inst_map);
365 }
353366
354 const old_tag = old_zir.instructions.items(.tag);
355 const old_data = old_zir.instructions.items(.data);
367 if (updated_files.items.len == 0)
368 return;
356369
357 // TODO: this should be done after all AstGen workers complete, to avoid
358 // iterating over this full set for every updated file.
359370 for (ip.locals, 0..) |*local, tid| {
360 local.mutate.tracked_insts.mutex.lock();
361 defer local.mutate.tracked_insts.mutex.unlock();
362371 const tracked_insts_list = local.getMutableTrackedInsts(gpa);
363372 for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| {
364 if (tracked_inst.file != file_index) continue;
365 const old_inst = tracked_inst.inst;
366 const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{
367 .tid = @enumFromInt(tid),
368 .index = @intCast(tracked_inst_unwrapped_index),
369 }).wrap(ip);
370 tracked_inst.inst = inst_map.get(old_inst) orelse {
371 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
372 zcu.comp.mutex.lock();
373 defer zcu.comp.mutex.unlock();
374 log.debug("tracking failed for %{d}", .{old_inst});
375 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
376 continue;
377 };
373 for (updated_files.items) |updated_file| {
374 const file_index = updated_file.file_index;
375 if (tracked_inst.file != file_index) continue;
376
377 const file = updated_file.file;
378 const old_zir = file.prev_zir.?.*;
379 const new_zir = file.zir;
380 const old_tag = old_zir.instructions.items(.tag);
381 const old_data = old_zir.instructions.items(.data);
382 const inst_map = &updated_file.inst_map;
383
384 const old_inst = tracked_inst.inst;
385 const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{
386 .tid = @enumFromInt(tid),
387 .index = @intCast(tracked_inst_unwrapped_index),
388 }).wrap(ip);
389 tracked_inst.inst = inst_map.get(old_inst) orelse {
390 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
391 log.debug("tracking failed for %{d}", .{old_inst});
392 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
393 continue;
394 };
378395
379 if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
380 if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| {
381 if (std.zig.srcHashEql(old_hash, new_hash)) {
382 break :hash_changed;
396 if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
397 if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| {
398 if (std.zig.srcHashEql(old_hash, new_hash)) {
399 break :hash_changed;
400 }
401 log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
402 old_inst,
403 tracked_inst.inst,
404 std.fmt.fmtSliceHexLower(&old_hash),
405 std.fmt.fmtSliceHexLower(&new_hash),
406 });
383407 }
384 log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
385 old_inst,
386 tracked_inst.inst,
387 std.fmt.fmtSliceHexLower(&old_hash),
388 std.fmt.fmtSliceHexLower(&new_hash),
389 });
408 // The source hash associated with this instruction changed - invalidate relevant dependencies.
409 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
390410 }
391 // The source hash associated with this instruction changed - invalidate relevant dependencies.
392 zcu.comp.mutex.lock();
393 defer zcu.comp.mutex.unlock();
394 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
395 }
396411
397 // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
398 const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
399 .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
400 .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
412 // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
413 const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
414 .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
415 .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
416 else => false,
417 },
401418 else => false,
402 },
403 else => false,
404 };
405 if (!has_namespace) continue;
406
407 var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
408 defer old_names.deinit(zcu.gpa);
409 {
410 var it = old_zir.declIterator(old_inst);
411 while (it.next()) |decl_inst| {
412 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
413 switch (decl_name) {
414 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
415 _ => if (decl_name.isNamedTest(old_zir)) continue,
419 };
420 if (!has_namespace) continue;
421
422 var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
423 defer old_names.deinit(zcu.gpa);
424 {
425 var it = old_zir.declIterator(old_inst);
426 while (it.next()) |decl_inst| {
427 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
428 switch (decl_name) {
429 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
430 _ => if (decl_name.isNamedTest(old_zir)) continue,
431 }
432 const name_zir = decl_name.toString(old_zir).?;
433 const name_ip = try zcu.intern_pool.getOrPutString(
434 zcu.gpa,
435 pt.tid,
436 old_zir.nullTerminatedString(name_zir),
437 .no_embedded_nulls,
438 );
439 try old_names.put(zcu.gpa, name_ip, {});
416440 }
417 const name_zir = decl_name.toString(old_zir).?;
418 const name_ip = try zcu.intern_pool.getOrPutString(
419 zcu.gpa,
420 pt.tid,
421 old_zir.nullTerminatedString(name_zir),
422 .no_embedded_nulls,
423 );
424 try old_names.put(zcu.gpa, name_ip, {});
425441 }
426 }
427 var any_change = false;
428 {
429 var it = new_zir.declIterator(tracked_inst.inst);
430 while (it.next()) |decl_inst| {
431 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
432 switch (decl_name) {
433 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
434 _ => if (decl_name.isNamedTest(old_zir)) continue,
442 var any_change = false;
443 {
444 var it = new_zir.declIterator(tracked_inst.inst);
445 while (it.next()) |decl_inst| {
446 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
447 switch (decl_name) {
448 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
449 _ => if (decl_name.isNamedTest(old_zir)) continue,
450 }
451 const name_zir = decl_name.toString(old_zir).?;
452 const name_ip = try zcu.intern_pool.getOrPutString(
453 zcu.gpa,
454 pt.tid,
455 old_zir.nullTerminatedString(name_zir),
456 .no_embedded_nulls,
457 );
458 if (!old_names.swapRemove(name_ip)) continue;
459 // Name added
460 any_change = true;
461 try zcu.markDependeeOutdated(.{ .namespace_name = .{
462 .namespace = tracked_inst_index,
463 .name = name_ip,
464 } });
435465 }
436 const name_zir = decl_name.toString(old_zir).?;
437 const name_ip = try zcu.intern_pool.getOrPutString(
438 zcu.gpa,
439 pt.tid,
440 old_zir.nullTerminatedString(name_zir),
441 .no_embedded_nulls,
442 );
443 if (!old_names.swapRemove(name_ip)) continue;
444 // Name added
466 }
467 // The only elements remaining in `old_names` now are any names which were removed.
468 for (old_names.keys()) |name_ip| {
445469 any_change = true;
446 zcu.comp.mutex.lock();
447 defer zcu.comp.mutex.unlock();
448470 try zcu.markDependeeOutdated(.{ .namespace_name = .{
449471 .namespace = tracked_inst_index,
450472 .name = name_ip,
451473 } });
452474 }
453 }
454 // The only elements remaining in `old_names` now are any names which were removed.
455 for (old_names.keys()) |name_ip| {
456 any_change = true;
457 zcu.comp.mutex.lock();
458 defer zcu.comp.mutex.unlock();
459 try zcu.markDependeeOutdated(.{ .namespace_name = .{
460 .namespace = tracked_inst_index,
461 .name = name_ip,
462 } });
463 }
464475
465 if (any_change) {
466 zcu.comp.mutex.lock();
467 defer zcu.comp.mutex.unlock();
468 try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index });
476 if (any_change) {
477 try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index });
478 }
469479 }
470480 }
471481 }
482
483 for (updated_files.items) |updated_file| {
484 const file = updated_file.file;
485 const prev_zir = file.prev_zir.?;
486 file.prev_zir = null;
487 prev_zir.deinit(gpa);
488 gpa.destroy(prev_zir);
489 }
472490}
473491
474492/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.