authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-02-03 03:03:17+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-02-04 18:38:39+00:00
loga0004cebc255405764e889effb25a42fe07d8463
tree9579c85d052b4520c0b40602add0bc797d9ceec8
parent1e91ee1e05f08013e3a4edec5d9f0aef978f3f0b
signaturelock-open Commit is signed but in an unrecognized format.

Zcu: more dependency tracking logic

* Invalidate `decl_val` dependencies * Recursively mark and un-mark all dependencies correctly * Queue analysis of outdated dependers in `Compilation.performAllTheWork` Introduces logic to invalidate `decl_val` dependencies after `Zcu.semaDecl` completes. Also, recursively un-mark dependencies as PO where needed. With this, all dependency invalidation logic is in place. The next step is analyzing outdated dependencies and triggering appropriate re-analysis.

2 files changed, 279 insertions(+), 36 deletions(-)

src/Compilation.zig+11
...@@ -3514,6 +3514,17 @@ pub fn performAllTheWork(...@@ -3514,6 +3514,17 @@ pub fn performAllTheWork(
3514 try processOneJob(comp, work_item, main_progress_node);3514 try processOneJob(comp, work_item, main_progress_node);
3515 continue;3515 continue;
3516 }3516 }
3517 if (comp.module) |zcu| {
3518 // If there's no work queued, check if there's anything outdated
3519 // which we need to work on, and queue it if so.
3520 if (try zcu.findOutdatedToAnalyze()) |outdated| {
3521 switch (outdated.unwrap()) {
3522 .decl => |decl| try comp.work_queue.writeItem(.{ .analyze_decl = decl }),
3523 .func => |func| try comp.work_queue.writeItem(.{ .codegen_func = func }),
3524 }
3525 continue;
3526 }
3527 }
3517 break;3528 break;
3518 }3529 }
35193530
src/Module.zig+268-36
...@@ -149,9 +149,14 @@ error_limit: ErrorInt,...@@ -149,9 +149,14 @@ error_limit: ErrorInt,
149/// previous analysis.149/// previous analysis.
150generation: u32 = 0,150generation: u32 = 0,
151151
152/// Value is the number of PO dependencies of this Depender.152/// Value is the number of PO or outdated Decls which this Depender depends on.
153potentially_outdated: std.AutoArrayHashMapUnmanaged(InternPool.Depender, u32) = .{},153potentially_outdated: std.AutoArrayHashMapUnmanaged(InternPool.Depender, u32) = .{},
154outdated: std.AutoArrayHashMapUnmanaged(InternPool.Depender, void) = .{},154/// Value is the number of PO or outdated Decls which this Depender depends on.
155/// Once this value drops to 0, the Depender is a candidate for re-analysis.
156outdated: std.AutoArrayHashMapUnmanaged(InternPool.Depender, u32) = .{},
157/// This contains all `Depender`s in `outdated` whose PO dependency count is 0.
158/// Such `Depender`s are ready for immediate re-analysis.
159outdated_ready: std.AutoArrayHashMapUnmanaged(InternPool.Depender, void) = .{},
155160
156stage1_flags: packed struct {161stage1_flags: packed struct {
157 have_winmain: bool = false,162 have_winmain: bool = false,
...@@ -2485,6 +2490,8 @@ pub fn deinit(zcu: *Zcu) void {...@@ -2485,6 +2490,8 @@ pub fn deinit(zcu: *Zcu) void {
2485 zcu.global_error_set.deinit(gpa);2490 zcu.global_error_set.deinit(gpa);
24862491
2487 zcu.potentially_outdated.deinit(gpa);2492 zcu.potentially_outdated.deinit(gpa);
2493 zcu.outdated.deinit(gpa);
2494 zcu.outdated_ready.deinit(gpa);
24882495
2489 zcu.test_functions.deinit(gpa);2496 zcu.test_functions.deinit(gpa);
24902497
...@@ -2851,11 +2858,27 @@ pub fn astGenFile(mod: *Module, file: *File) !void {...@@ -2851,11 +2858,27 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
2851 file.prev_zir = null;2858 file.prev_zir = null;
2852 }2859 }
28532860
2854 if (file.root_decl.unwrap()) |root_decl| {2861 if (file.root_decl.unwrap()) |root_decl| mark_outdated: {
2855 // The root of this file must be re-analyzed, since the file has changed.2862 // The root of this file must be re-analyzed, since the file has changed.
2856 comp.mutex.lock();2863 comp.mutex.lock();
2857 defer comp.mutex.unlock();2864 defer comp.mutex.unlock();
2858 try mod.outdated.put(gpa, InternPool.Depender.wrap(.{ .decl = root_decl }), {});2865
2866 const root_decl_depender = InternPool.Depender.wrap(.{ .decl = root_decl });
2867
2868 const gop = try mod.outdated.getOrPut(gpa, root_decl_depender);
2869 // If this Decl is already marked as outdated, nothing needs to be done.
2870 if (gop.found_existing) break :mark_outdated;
2871
2872 log.debug("outdated: {} (root Decl)", .{root_decl});
2873
2874 // If it's already PO, forward its existing PO dependency count.
2875 // Otherwise, it has no PO dependencies yet.
2876 if (mod.potentially_outdated.fetchSwapRemove(root_decl_depender)) |kv| {
2877 gop.value_ptr.* = kv.value;
2878 } else {
2879 gop.value_ptr.* = 0;
2880 try mod.outdated_ready.put(mod.gpa, root_decl_depender, {});
2881 }
2859 }2882 }
2860}2883}
28612884
...@@ -2953,6 +2976,7 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {...@@ -2953,6 +2976,7 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {
2953 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.2976 // Tracking failed for this instruction. Invalidate associated `src_hash` deps.
2954 zcu.comp.mutex.lock();2977 zcu.comp.mutex.lock();
2955 defer zcu.comp.mutex.unlock();2978 defer zcu.comp.mutex.unlock();
2979 log.debug("tracking failed for %{d}", .{old_inst});
2956 try zcu.markDependeeOutdated(.{ .src_hash = ti_idx });2980 try zcu.markDependeeOutdated(.{ .src_hash = ti_idx });
2957 continue;2981 continue;
2958 };2982 };
...@@ -2962,6 +2986,12 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {...@@ -2962,6 +2986,12 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {
2962 if (std.zig.srcHashEql(old_hash, new_hash)) {2986 if (std.zig.srcHashEql(old_hash, new_hash)) {
2963 break :hash_changed;2987 break :hash_changed;
2964 }2988 }
2989 log.debug("hash for (%{d} -> %{d}) changed: {} -> {}", .{
2990 old_inst,
2991 ti.inst,
2992 std.fmt.fmtSliceHexLower(&old_hash),
2993 std.fmt.fmtSliceHexLower(&new_hash),
2994 });
2965 }2995 }
2966 // The source hash associated with this instruction changed - invalidate relevant dependencies.2996 // The source hash associated with this instruction changed - invalidate relevant dependencies.
2967 zcu.comp.mutex.lock();2997 zcu.comp.mutex.lock();
...@@ -3042,25 +3072,80 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {...@@ -3042,25 +3072,80 @@ fn updateZirRefs(zcu: *Module, file: *File, old_zir: Zir) !void {
3042}3072}
30433073
3044pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void {3074pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3075 log.debug("outdated dependee: {}", .{dependee});
3045 var it = zcu.intern_pool.dependencyIterator(dependee);3076 var it = zcu.intern_pool.dependencyIterator(dependee);
3046 while (it.next()) |depender| {3077 while (it.next()) |depender| {
3047 if (zcu.outdated.contains(depender)) continue;3078 if (zcu.outdated.contains(depender)) {
3048 const was_po = zcu.potentially_outdated.swapRemove(depender);3079 // We do not need to increment the PO dep count, as if the outdated
3049 try zcu.outdated.putNoClobber(zcu.gpa, depender, {});3080 // dependee is a Decl, we had already marked this as PO.
3081 continue;
3082 }
3083 const opt_po_entry = zcu.potentially_outdated.fetchSwapRemove(depender);
3084 try zcu.outdated.putNoClobber(
3085 zcu.gpa,
3086 depender,
3087 // We do not need to increment this count for the same reason as above.
3088 if (opt_po_entry) |e| e.value else 0,
3089 );
3090 log.debug("outdated: {}", .{depender});
3091 if (opt_po_entry != null) {
3092 // This is a new entry with no PO dependencies.
3093 try zcu.outdated_ready.put(zcu.gpa, depender, {});
3094 }
3050 // If this is a Decl and was not previously PO, we must recursively3095 // If this is a Decl and was not previously PO, we must recursively
3051 // mark dependencies on its tyval as PO.3096 // mark dependencies on its tyval as PO.
3052 if (was_po) switch (depender.unwrap()) {3097 if (opt_po_entry == null) switch (depender.unwrap()) {
3053 .decl => |decl_index| try zcu.markDeclDependenciesPotentiallyOutdated(decl_index),3098 .decl => |decl_index| try zcu.markDeclDependenciesPotentiallyOutdated(decl_index),
3054 .func => {},3099 .func => {},
3055 };3100 };
3056 }3101 }
3057}3102}
30583103
3104fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
3105 var it = zcu.intern_pool.dependencyIterator(dependee);
3106 while (it.next()) |depender| {
3107 if (zcu.outdated.getPtr(depender)) |po_dep_count| {
3108 // This depender is already outdated, but it now has one
3109 // less PO dependency!
3110 po_dep_count.* -= 1;
3111 if (po_dep_count.* == 0) {
3112 try zcu.outdated_ready.put(zcu.gpa, depender, {});
3113 }
3114 continue;
3115 }
3116 // This depender is definitely at least PO, because this Decl was just analyzed
3117 // due to being outdated.
3118 const ptr = zcu.potentially_outdated.getPtr(depender).?;
3119 if (ptr.* > 1) {
3120 ptr.* -= 1;
3121 continue;
3122 }
3123
3124 // This dependency is no longer PO, i.e. is known to be up-to-date.
3125 assert(zcu.potentially_outdated.swapRemove(depender));
3126 // If this is a Decl, we must recursively mark dependencies on its tyval
3127 // as no longer PO.
3128 switch (depender.unwrap()) {
3129 .decl => |decl_index| try zcu.markPoDependeeUpToDate(.{ .decl_val = decl_index }),
3130 .func => {},
3131 }
3132 }
3133}
3134
3059/// Given a Decl which is newly outdated or PO, mark all dependers which depend3135/// Given a Decl which is newly outdated or PO, mark all dependers which depend
3060/// on its tyval as PO.3136/// on its tyval as PO.
3061fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !void {3137fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !void {
3062 var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });3138 var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });
3063 while (it.next()) |po| {3139 while (it.next()) |po| {
3140 if (zcu.outdated.getPtr(po)) |po_dep_count| {
3141 // This dependency is already outdated, but it now has one more PO
3142 // dependency.
3143 if (po_dep_count.* == 0) {
3144 _ = zcu.outdated_ready.swapRemove(po);
3145 }
3146 po_dep_count.* += 1;
3147 continue;
3148 }
3064 if (zcu.potentially_outdated.getPtr(po)) |n| {3149 if (zcu.potentially_outdated.getPtr(po)) |n| {
3065 // There is now one more PO dependency.3150 // There is now one more PO dependency.
3066 n.* += 1;3151 n.* += 1;
...@@ -3077,6 +3162,92 @@ fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !v...@@ -3077,6 +3162,92 @@ fn markDeclDependenciesPotentiallyOutdated(zcu: *Zcu, decl_index: Decl.Index) !v
3077 // TODO: repeat the above for `decl_ty` dependencies when they are introduced3162 // TODO: repeat the above for `decl_ty` dependencies when they are introduced
3078}3163}
30793164
3165pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.Depender {
3166 if (zcu.outdated.count() == 0 and zcu.potentially_outdated.count() == 0) {
3167 log.debug("findOutdatedToAnalyze: no outdated depender", .{});
3168 return null;
3169 }
3170
3171 // Our goal is to find an outdated Depender which itself has no outdated or
3172 // PO dependencies. Most of the time, such a Depender will exist - we track
3173 // them in the `outdated_ready` set for efficiency. However, this is not
3174 // necessarily the case, since the Decl dependency graph may contain loops
3175 // via mutually recursive definitions:
3176 // pub const A = struct { b: *B };
3177 // pub const B = struct { b: *A };
3178 // In this case, we must defer to more complex logic below.
3179
3180 if (zcu.outdated_ready.count() > 0) {
3181 log.debug("findOutdatedToAnalyze: trivial '{s} {d}'", .{
3182 @tagName(zcu.outdated_ready.keys()[0].unwrap()),
3183 switch (zcu.outdated_ready.keys()[0].unwrap()) {
3184 inline else => |x| @intFromEnum(x),
3185 },
3186 });
3187 return zcu.outdated_ready.keys()[0];
3188 }
3189
3190 // There is no single Depender which is ready for re-analysis. Instead, we
3191 // must assume that some Decl with PO dependencies is outdated - e.g. in the
3192 // above example we arbitrarily pick one of A or B. We should select a Decl,
3193 // since a Decl is definitely responsible for the loop in the dependency
3194 // graph (since you can't depend on a runtime function analysis!).
3195
3196 // The choice of this Decl could have a big impact on how much total
3197 // analysis we perform, since if analysis concludes its tyval is unchanged,
3198 // then other PO Dependers may be resolved as up-to-date. To hopefully avoid
3199 // doing too much work, let's find a Decl which the most things depend on -
3200 // the idea is that this will resolve a lot of loops (but this is only a
3201 // heuristic).
3202
3203 log.debug("findOutdatedToAnalyze: no trivial ready, using heuristic; {d} outdated, {d} PO", .{
3204 zcu.outdated.count(),
3205 zcu.potentially_outdated.count(),
3206 });
3207
3208 var chosen_decl_idx: ?Decl.Index = null;
3209 var chosen_decl_dependers: u32 = undefined;
3210
3211 for (zcu.outdated.keys()) |depender| {
3212 const decl_index = switch (depender.unwrap()) {
3213 .decl => |d| d,
3214 .func => continue,
3215 };
3216
3217 var n: u32 = 0;
3218 var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });
3219 while (it.next()) |_| n += 1;
3220
3221 if (chosen_decl_idx == null or n > chosen_decl_dependers) {
3222 chosen_decl_idx = decl_index;
3223 chosen_decl_dependers = n;
3224 }
3225 }
3226
3227 for (zcu.potentially_outdated.keys()) |depender| {
3228 const decl_index = switch (depender.unwrap()) {
3229 .decl => |d| d,
3230 .func => continue,
3231 };
3232
3233 var n: u32 = 0;
3234 var it = zcu.intern_pool.dependencyIterator(.{ .decl_val = decl_index });
3235 while (it.next()) |_| n += 1;
3236
3237 if (chosen_decl_idx == null or n > chosen_decl_dependers) {
3238 chosen_decl_idx = decl_index;
3239 chosen_decl_dependers = n;
3240 }
3241 }
3242
3243 log.debug("findOutdatedToAnalyze: heuristic returned Decl {d} ({d} dependers)", .{
3244 chosen_decl_idx.?,
3245 chosen_decl_dependers,
3246 });
3247
3248 return InternPool.Depender.wrap(.{ .decl = chosen_decl_idx.? });
3249}
3250
3080pub fn mapOldZirToNew(3251pub fn mapOldZirToNew(
3081 gpa: Allocator,3252 gpa: Allocator,
3082 old_zir: Zir,3253 old_zir: Zir,
...@@ -3204,7 +3375,7 @@ pub fn mapOldZirToNew(...@@ -3204,7 +3375,7 @@ pub fn mapOldZirToNew(
3204 }3375 }
3205}3376}
32063377
3207/// This ensures that the Decl will have a Type and Value populated.3378/// This ensures that the Decl will have an up-to-date Type and Value populated.
3208/// However the resolution status of the Type may not be fully resolved.3379/// However the resolution status of the Type may not be fully resolved.
3209/// For example an inferred error set is not resolved until after `analyzeFnBody`.3380/// For example an inferred error set is not resolved until after `analyzeFnBody`.
3210/// is called.3381/// is called.
...@@ -3214,7 +3385,25 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {...@@ -3214,7 +3385,25 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
32143385
3215 const decl = mod.declPtr(decl_index);3386 const decl = mod.declPtr(decl_index);
32163387
3217 const subsequent_analysis = switch (decl.analysis) {3388 // Determine whether or not this Decl is outdated, i.e. requires re-analysis
3389 // even if `complete`. If a Decl is PO, we pessismistically assume that it
3390 // *does* require re-analysis, to ensure that the Decl is definitely
3391 // up-to-date when this function returns.
3392
3393 // If analysis occurs in a poor order, this could result in over-analysis.
3394 // We do our best to avoid this by the other dependency logic in this file
3395 // which tries to limit re-analysis to Decls whose previously listed
3396 // dependencies are all up-to-date.
3397
3398 const decl_as_depender = InternPool.Depender.wrap(.{ .decl = decl_index });
3399 const was_outdated = mod.outdated.swapRemove(decl_as_depender) or
3400 mod.potentially_outdated.swapRemove(decl_as_depender);
3401
3402 if (was_outdated) {
3403 _ = mod.outdated_ready.swapRemove(decl_as_depender);
3404 }
3405
3406 switch (decl.analysis) {
3218 .in_progress => unreachable,3407 .in_progress => unreachable,
32193408
3220 .file_failure,3409 .file_failure,
...@@ -3226,28 +3415,29 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {...@@ -3226,28 +3415,29 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
3226 .codegen_failure_retryable,3415 .codegen_failure_retryable,
3227 => return error.AnalysisFail,3416 => return error.AnalysisFail,
32283417
3229 .complete => return,3418 .complete => if (was_outdated) {
3230
3231 .outdated => blk: {
3232 if (build_options.only_c) unreachable;3419 if (build_options.only_c) unreachable;
3233 // The exports this Decl performs will be re-discovered, so we remove them here3420 // The exports this Decl performs will be re-discovered, so we remove them here
3234 // prior to re-analysis.3421 // prior to re-analysis.
3235 try mod.deleteDeclExports(decl_index);3422 try mod.deleteDeclExports(decl_index);
3423 } else return,
32363424
3237 break :blk true;3425 .outdated => unreachable, // TODO: remove this field
3238 },
32393426
3240 .unreferenced => false,3427 .unreferenced => {},
3241 };3428 }
32423429
3243 var decl_prog_node = mod.sema_prog_node.start("", 0);3430 var decl_prog_node = mod.sema_prog_node.start("", 0);
3244 decl_prog_node.activate();3431 decl_prog_node.activate();
3245 defer decl_prog_node.end();3432 defer decl_prog_node.end();
32463433
3247 const type_changed = blk: {3434 const sema_result: SemaDeclResult = blk: {
3248 if (decl.zir_decl_index == .none and !mod.declIsRoot(decl_index)) {3435 if (decl.zir_decl_index == .none and !mod.declIsRoot(decl_index)) {
3249 // Anonymous decl. We don't semantically analyze these.3436 // Anonymous decl. We don't semantically analyze these.
3250 break :blk false; // tv unchanged3437 break :blk .{
3438 .invalidate_decl_val = false,
3439 .invalidate_decl_ref = false,
3440 };
3251 }3441 }
32523442
3253 break :blk mod.semaDecl(decl_index) catch |err| switch (err) {3443 break :blk mod.semaDecl(decl_index) catch |err| switch (err) {
...@@ -3276,9 +3466,18 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {...@@ -3276,9 +3466,18 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
3276 };3466 };
3277 };3467 };
32783468
3279 if (subsequent_analysis) {3469 // TODO: we do not yet have separate dependencies for decl values vs types.
3280 _ = type_changed;3470 if (was_outdated) {
3281 @panic("TODO re-implement incremental compilation");3471 if (sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref) {
3472 // This dependency was marked as PO, meaning dependees were waiting
3473 // on its analysis result, and it has turned out to be outdated.
3474 // Update dependees accordingly.
3475 try mod.markDependeeOutdated(.{ .decl_val = decl_index });
3476 } else {
3477 // This dependency was previously PO, but turned out to be up-to-date.
3478 // We do not need to queue successive analysis.
3479 try mod.markPoDependeeUpToDate(.{ .decl_val = decl_index });
3480 }
3282 }3481 }
3283}3482}
32843483
...@@ -3591,12 +3790,19 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {...@@ -3591,12 +3790,19 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
3591 },3790 },
3592 .incremental => {},3791 .incremental => {},
3593 }3792 }
3793
3794 // Since this is our first time analyzing this file, there can be no dependencies on
3795 // its root Decl. Thus, we do not need to invalidate any dependencies.
3594}3796}
35953797
3596/// Returns `true` if the Decl type changed.3798const SemaDeclResult = packed struct {
3597/// Returns `true` if this is the first time analyzing the Decl.3799 /// Whether the value of a `decl_val` of this Decl changed.
3598/// Returns `false` otherwise.3800 invalidate_decl_val: bool,
3599fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {3801 /// Whether the type of a `decl_ref` of this Decl changed.
3802 invalidate_decl_ref: bool,
3803};
3804
3805fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult {
3600 const tracy = trace(@src());3806 const tracy = trace(@src());
3601 defer tracy.end();3807 defer tracy.end();
36023808
...@@ -3674,8 +3880,10 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3674,8 +3880,10 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
3674 };3880 };
3675 defer sema.deinit();3881 defer sema.deinit();
36763882
3677 // Every Decl has a dependency on its own source.3883 // Every Decl other (than file root Decls, which do not have a ZIR index) has a dependency on its own source.
3678 try sema.declareDependency(.{ .src_hash = try ip.trackZir(sema.gpa, decl.getFileScope(mod), decl.zir_decl_index.unwrap().?) });3884 if (decl.zir_decl_index.unwrap()) |zir_decl_index| {
3885 try sema.declareDependency(.{ .src_hash = try ip.trackZir(sema.gpa, decl.getFileScope(mod), zir_decl_index) });
3886 }
36793887
3680 assert(!mod.declIsRoot(decl_index));3888 assert(!mod.declIsRoot(decl_index));
36813889
...@@ -3735,7 +3943,11 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3735,7 +3943,11 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
3735 decl.analysis = .complete;3943 decl.analysis = .complete;
3736 decl.generation = mod.generation;3944 decl.generation = mod.generation;
37373945
3738 return true;3946 // TODO: usingnamespace cannot currently participate in incremental compilation
3947 return .{
3948 .invalidate_decl_val = true,
3949 .invalidate_decl_ref = true,
3950 };
3739 }3951 }
37403952
3741 switch (ip.indexToKey(decl_tv.val.toIntern())) {3953 switch (ip.indexToKey(decl_tv.val.toIntern())) {
...@@ -3771,15 +3983,16 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3771,15 +3983,16 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
3771 // The scope needs to have the decl in it.3983 // The scope needs to have the decl in it.
3772 try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index);3984 try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index);
3773 }3985 }
3774 return type_changed or is_inline != prev_is_inline;3986 // TODO: align, linksection, addrspace?
3987 const changed = type_changed or is_inline != prev_is_inline;
3988 return .{
3989 .invalidate_decl_val = changed,
3990 .invalidate_decl_ref = changed,
3991 };
3775 }3992 }
3776 },3993 },
3777 else => {},3994 else => {},
3778 }3995 }
3779 var type_changed = true;
3780 if (decl.has_tv) {
3781 type_changed = !decl.ty.eql(decl_tv.ty, mod);
3782 }
37833996
3784 decl.owns_tv = false;3997 decl.owns_tv = false;
3785 var queue_linker_work = false;3998 var queue_linker_work = false;
...@@ -3807,6 +4020,14 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3807,6 +4020,14 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
3807 },4020 },
3808 }4021 }
38094022
4023 const old_has_tv = decl.has_tv;
4024 // The following values are ignored if `!old_has_tv`
4025 const old_ty = decl.ty;
4026 const old_val = decl.val;
4027 const old_align = decl.alignment;
4028 const old_linksection = decl.@"linksection";
4029 const old_addrspace = decl.@"addrspace";
4030
3810 decl.ty = decl_tv.ty;4031 decl.ty = decl_tv.ty;
3811 decl.val = Value.fromInterned((try decl_tv.val.intern(decl_tv.ty, mod)));4032 decl.val = Value.fromInterned((try decl_tv.val.intern(decl_tv.ty, mod)));
3812 decl.alignment = blk: {4033 decl.alignment = blk: {
...@@ -3850,6 +4071,17 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3850,6 +4071,17 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
3850 decl.analysis = .complete;4071 decl.analysis = .complete;
3851 decl.generation = mod.generation;4072 decl.generation = mod.generation;
38524073
4074 const result: SemaDeclResult = if (old_has_tv) .{
4075 .invalidate_decl_val = !decl.ty.eql(old_ty, mod) or !decl.val.eql(old_val, decl.ty, mod),
4076 .invalidate_decl_ref = !decl.ty.eql(old_ty, mod) or
4077 decl.alignment != old_align or
4078 decl.@"linksection" != old_linksection or
4079 decl.@"addrspace" != old_addrspace,
4080 } else .{
4081 .invalidate_decl_val = true,
4082 .invalidate_decl_ref = true,
4083 };
4084
3853 const has_runtime_bits = is_extern or4085 const has_runtime_bits = is_extern or
3854 (queue_linker_work and try sema.typeHasRuntimeBits(decl.ty));4086 (queue_linker_work and try sema.typeHasRuntimeBits(decl.ty));
38554087
...@@ -3861,7 +4093,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3861,7 +4093,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
38614093
3862 try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });4094 try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl_index });
38634095
3864 if (type_changed and mod.emit_h != null) {4096 if (result.invalidate_decl_ref and mod.emit_h != null) {
3865 try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });4097 try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
3866 }4098 }
3867 }4099 }
...@@ -3872,7 +4104,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {...@@ -3872,7 +4104,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
3872 try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index);4104 try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index);
3873 }4105 }
38744106
3875 return type_changed;4107 return result;
3876}4108}
38774109
3878pub const ImportFileResult = struct {4110pub const ImportFileResult = struct {