| ... | ... | @@ -60,10 +60,6 @@ pub fn destroyFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void { |
| 60 | 60 | pub fn astGenFile( |
| 61 | 61 | pt: Zcu.PerThread, |
| 62 | 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 | 63 | path_digest: Cache.BinDigest, |
| 68 | 64 | opt_root_decl: Zcu.Decl.OptionalIndex, |
| 69 | 65 | ) !void { |
| ... | ... | @@ -210,13 +206,18 @@ pub fn astGenFile( |
| 210 | 206 | |
| 211 | 207 | pt.lockAndClearFileCompileError(file); |
| 212 | 208 | |
| 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. |
| 220 | 221 | if (file.zir_loaded and !file.zir.hasCompileErrors()) { |
| 221 | 222 | assert(file.prev_zir == null); |
| 222 | 223 | const prev_zir_ptr = try gpa.create(Zir); |
| ... | ... | @@ -320,14 +321,6 @@ pub fn astGenFile( |
| 320 | 321 | return error.AnalysisFail; |
| 321 | 322 | } |
| 322 | 323 | |
| 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 | 324 | if (opt_root_decl.unwrap()) |root_decl| { |
| 332 | 325 | // The root of this file must be re-analyzed, since the file has changed. |
| 333 | 326 | comp.mutex.lock(); |
| ... | ... | @@ -338,137 +331,162 @@ pub fn astGenFile( |
| 338 | 331 | } |
| 339 | 332 | } |
| 340 | 333 | |
| 341 | | /// This is called from the AstGen thread pool, so must acquire |
| 342 | | /// the Compilation mutex when acting on shared state. |
| 343 | | fn updateZirRefs(pt: Zcu.PerThread, file: *Zcu.File, file_index: Zcu.File.Index, old_zir: Zir) !void { |
| 334 | const 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 | |
| 340 | fn 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 | |
| 345 | pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { |
| 346 | assert(pt.tid == .main); |
| 344 | 347 | const zcu = pt.zcu; |
| 345 | 348 | const ip = &zcu.intern_pool; |
| 346 | 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); |
| 351 | 350 | |
| 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 | } |
| 353 | 366 | |
| 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; |
| 356 | 369 | |
| 357 | | // TODO: this should be done after all AstGen workers complete, to avoid |
| 358 | | // iterating over this full set for every updated file. |
| 359 | 370 | for (ip.locals, 0..) |*local, tid| { |
| 360 | | local.mutate.tracked_insts.mutex.lock(); |
| 361 | | defer local.mutate.tracked_insts.mutex.unlock(); |
| 362 | 371 | const tracked_insts_list = local.getMutableTrackedInsts(gpa); |
| 363 | 372 | 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 | }; |
| 378 | 395 | |
| 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 | }); |
| 383 | 407 | } |
| 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 }); |
| 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 | | } |
| 396 | 411 | |
| 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 | }, |
| 401 | 418 | 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, {}); |
| 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 | | } |
| 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 | } }); |
| 435 | 465 | } |
| 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| { |
| 445 | 469 | any_change = true; |
| 446 | | zcu.comp.mutex.lock(); |
| 447 | | defer zcu.comp.mutex.unlock(); |
| 448 | 470 | try zcu.markDependeeOutdated(.{ .namespace_name = .{ |
| 449 | 471 | .namespace = tracked_inst_index, |
| 450 | 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 | | } |
| 464 | 475 | |
| 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 | } |
| 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 | } |
| 473 | 491 | |
| 474 | 492 | /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. |