| ... | @@ -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 { |
| 60 | pub fn astGenFile( | 60 | pub 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( |
| 210 | | 206 | |
| 211 | pt.lockAndClearFileCompileError(file); | 207 | pt.lockAndClearFileCompileError(file); |
| 212 | | 208 | |
| 213 | // If the previous ZIR does not have compile errors, keep it around | 209 | // Previous ZIR is kept for two reasons: |
| 214 | // in case parsing or new ZIR fails. In case of successful ZIR update | 210 | // |
| 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 it | 212 | // 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 avoids | 213 | // incremental update. This avoids needlessly tossing out semantic |
| 218 | // needlessly tossing out semantic analysis work when an error is | 214 | // 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 | } |
| 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 | 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 | } |
| 340 | | 333 | |
| 341 | /// This is called from the AstGen thread pool, so must acquire | 334 | const UpdatedFile = struct { |
| 342 | /// the Compilation mutex when acting on shared state. | 335 | file_index: Zcu.File.Index, |
| 343 | fn 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 | |
| | 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 | 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); | | |
| 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); | 367 | if (updated_files.items.len == 0) |
| 355 | const old_data = old_zir.instructions.items(.data); | 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 | 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 | }; |
| 378 | | 395 | |
| 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 | } | | |
| 396 | | 411 | |
| 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) = .{}; |
| 406 | | 423 | 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 | } | | |
| 464 | | 475 | |
| 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 | } |
| 473 | | 491 | |
| 474 | /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. | 492 | /// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. |