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(...@@ -3596,6 +3596,11 @@ fn performAllTheWorkInner(
35963596
3597 if (comp.module) |zcu| {3597 if (comp.module) |zcu| {
3598 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = .main };3598 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 }
3599 try reportMultiModuleErrors(pt);3604 try reportMultiModuleErrors(pt);
3600 try zcu.flushRetryableFailures();3605 try zcu.flushRetryableFailures();
3601 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);3606 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
...@@ -4306,7 +4311,7 @@ fn workerAstGenFile(...@@ -4306,7 +4311,7 @@ fn workerAstGenFile(
4306 defer child_prog_node.end();4311 defer child_prog_node.end();
43074312
4308 const pt: Zcu.PerThread = .{ .zcu = comp.module.?, .tid = @enumFromInt(tid) };4313 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) {
4310 error.AnalysisFail => return,4315 error.AnalysisFail => return,
4311 else => {4316 else => {
4312 file.status = .retryable_failure;4317 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...@@ -6065,7 +6065,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
60656065
6066 const path_digest = zcu.filePathDigest(result.file_index);6066 const path_digest = zcu.filePathDigest(result.file_index);
6067 const root_decl = zcu.fileRootDecl(result.file_index);6067 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|
6069 return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});6069 return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
60706070
6071 try pt.ensureFileAnalyzed(result.file_index);6071 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 {...@@ -60,10 +60,6 @@ pub fn destroyFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void {
60pub fn astGenFile(60pub fn astGenFile(
61 pt: Zcu.PerThread,61 pt: Zcu.PerThread,
62 file: *Zcu.File,62 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,
67 path_digest: Cache.BinDigest,63 path_digest: Cache.BinDigest,
68 opt_root_decl: Zcu.Decl.OptionalIndex,64 opt_root_decl: Zcu.Decl.OptionalIndex,
69) !void {65) !void {
...@@ -210,13 +206,18 @@ pub fn astGenFile(...@@ -210,13 +206,18 @@ pub fn astGenFile(
210206
211 pt.lockAndClearFileCompileError(file);207 pt.lockAndClearFileCompileError(file);
212208
213 // If the previous ZIR does not have compile errors, keep it around209 // Previous ZIR is kept for two reasons:
214 // in case parsing or new ZIR fails. In case of successful ZIR update210 //
215 // at the end of this function we will free it.211 // 1. In case an update to the file causes a Parse or AstGen failure, we
216 // We keep the previous ZIR loaded so that we can use it212 // need to compare two successful ZIR files in order to proceed with an
217 // for the update next time it does not have any compile errors. This avoids213 // incremental update. This avoids needlessly tossing out semantic
218 // needlessly tossing out semantic analysis work when an error is214 // analysis work when an error is temporarily introduced.
219 // 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.
220 if (file.zir_loaded and !file.zir.hasCompileErrors()) {221 if (file.zir_loaded and !file.zir.hasCompileErrors()) {
221 assert(file.prev_zir == null);222 assert(file.prev_zir == null);
222 const prev_zir_ptr = try gpa.create(Zir);223 const prev_zir_ptr = try gpa.create(Zir);
...@@ -320,14 +321,6 @@ pub fn astGenFile(...@@ -320,14 +321,6 @@ pub fn astGenFile(
320 return error.AnalysisFail;321 return error.AnalysisFail;
321 }322 }
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
331 if (opt_root_decl.unwrap()) |root_decl| {324 if (opt_root_decl.unwrap()) |root_decl| {
332 // The root of this file must be re-analyzed, since the file has changed.325 // The root of this file must be re-analyzed, since the file has changed.
333 comp.mutex.lock();326 comp.mutex.lock();
...@@ -338,137 +331,162 @@ pub fn astGenFile(...@@ -338,137 +331,162 @@ pub fn astGenFile(
338 }331 }
339}332}
340333
341/// This is called from the AstGen thread pool, so must acquire334const UpdatedFile = struct {
342/// the Compilation mutex when acting on shared state.335 file_index: Zcu.File.Index,
343fn updateZirRefs(pt: Zcu.PerThread, file: *Zcu.File, file_index: Zcu.File.Index, old_zir: Zir) !void {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);
344 const zcu = pt.zcu;347 const zcu = pt.zcu;
345 const ip = &zcu.intern_pool;348 const ip = &zcu.intern_pool;
346 const gpa = zcu.gpa;349 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);367 if (updated_files.items.len == 0)
355 const old_data = old_zir.instructions.items(.data);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.
359 for (ip.locals, 0..) |*local, tid| {370 for (ip.locals, 0..) |*local, tid| {
360 local.mutate.tracked_insts.mutex.lock();
361 defer local.mutate.tracked_insts.mutex.unlock();
362 const tracked_insts_list = local.getMutableTrackedInsts(gpa);371 const tracked_insts_list = local.getMutableTrackedInsts(gpa);
363 for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| {372 for (tracked_insts_list.view().items(.@"0"), 0..) |*tracked_inst, tracked_inst_unwrapped_index| {
364 if (tracked_inst.file != file_index) continue;373 for (updated_files.items) |updated_file| {
365 const old_inst = tracked_inst.inst;374 const file_index = updated_file.file_index;
366 const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{375 if (tracked_inst.file != file_index) continue;
367 .tid = @enumFromInt(tid),376
368 .index = @intCast(tracked_inst_unwrapped_index),377 const file = updated_file.file;
369 }).wrap(ip);378 const old_zir = file.prev_zir.?.*;
370 tracked_inst.inst = inst_map.get(old_inst) orelse {379 const new_zir = file.zir;
371 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.380 const old_tag = old_zir.instructions.items(.tag);
372 zcu.comp.mutex.lock();381 const old_data = old_zir.instructions.items(.data);
373 defer zcu.comp.mutex.unlock();382 const inst_map = &updated_file.inst_map;
374 log.debug("tracking failed for %{d}", .{old_inst});383
375 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });384 const old_inst = tracked_inst.inst;
376 continue;385 const tracked_inst_index = (InternPool.TrackedInst.Index.Unwrapped{
377 };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: {396 if (old_zir.getAssociatedSrcHash(old_inst)) |old_hash| hash_changed: {
380 if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| {397 if (new_zir.getAssociatedSrcHash(tracked_inst.inst)) |new_hash| {
381 if (std.zig.srcHashEql(old_hash, new_hash)) {398 if (std.zig.srcHashEql(old_hash, new_hash)) {
382 break :hash_changed;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 });
383 }407 }
384 log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{408 // The source hash associated with this instruction changed - invalidate relevant dependencies.
385 old_inst,409 try zcu.markDependeeOutdated(.{ .src_hash = tracked_inst_index });
386 tracked_inst.inst,
387 std.fmt.fmtSliceHexLower(&old_hash),
388 std.fmt.fmtSliceHexLower(&new_hash),
389 });
390 }410 }
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.412 // If this is a `struct_decl` etc, we must invalidate any outdated namespace dependencies.
398 const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {413 const has_namespace = switch (old_tag[@intFromEnum(old_inst)]) {
399 .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {414 .extended => switch (old_data[@intFromEnum(old_inst)].extended.opcode) {
400 .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,415 .struct_decl, .union_decl, .opaque_decl, .enum_decl => true,
416 else => false,
417 },
401 else => false,418 else => false,
402 },419 };
403 else => false,420 if (!has_namespace) continue;
404 };421
405 if (!has_namespace) continue;422 var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
406423 defer old_names.deinit(zcu.gpa);
407 var old_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};424 {
408 defer old_names.deinit(zcu.gpa);425 var it = old_zir.declIterator(old_inst);
409 {426 while (it.next()) |decl_inst| {
410 var it = old_zir.declIterator(old_inst);427 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
411 while (it.next()) |decl_inst| {428 switch (decl_name) {
412 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;429 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
413 switch (decl_name) {430 _ => if (decl_name.isNamedTest(old_zir)) continue,
414 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,431 }
415 _ => if (decl_name.isNamedTest(old_zir)) continue,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, {});
416 }440 }
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, {});
425 }441 }
426 }442 var any_change = false;
427 var any_change = false;443 {
428 {444 var it = new_zir.declIterator(tracked_inst.inst);
429 var it = new_zir.declIterator(tracked_inst.inst);445 while (it.next()) |decl_inst| {
430 while (it.next()) |decl_inst| {446 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
431 const decl_name = old_zir.getDeclaration(decl_inst)[0].name;447 switch (decl_name) {
432 switch (decl_name) {448 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,
433 .@"comptime", .@"usingnamespace", .unnamed_test, .decltest => continue,449 _ => if (decl_name.isNamedTest(old_zir)) continue,
434 _ => 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 } });
435 }465 }
436 const name_zir = decl_name.toString(old_zir).?;466 }
437 const name_ip = try zcu.intern_pool.getOrPutString(467 // The only elements remaining in `old_names` now are any names which were removed.
438 zcu.gpa,468 for (old_names.keys()) |name_ip| {
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
445 any_change = true;469 any_change = true;
446 zcu.comp.mutex.lock();
447 defer zcu.comp.mutex.unlock();
448 try zcu.markDependeeOutdated(.{ .namespace_name = .{470 try zcu.markDependeeOutdated(.{ .namespace_name = .{
449 .namespace = tracked_inst_index,471 .namespace = tracked_inst_index,
450 .name = name_ip,472 .name = name_ip,
451 } });473 } });
452 }474 }
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) {476 if (any_change) {
466 zcu.comp.mutex.lock();477 try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index });
467 defer zcu.comp.mutex.unlock();478 }
468 try zcu.markDependeeOutdated(.{ .namespace = tracked_inst_index });
469 }479 }
470 }480 }
471 }481 }
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 }
472}490}
473491
474/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.492/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.