authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-11 14:27:21+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-06-12 17:51:30+01:00
logd7afd797ccdeeab74946f047c3e755f33b5ea9b9
tree5c203e55c9c4ffaa18da83cf74f3bb19f79fac5d
parent4d2b216121e5fbdb019ab8e727df654e7b08e984
signaturelock-open Commit is signed but in an unrecognized format.

Zcu: handle unreferenced `test_functions` correctly

Previously, `PerThread.populateTestFunctions` was analyzing the `test_functions` declaration if it hadn't already been analyzed, so that it could then populate it. However, the logic for doing this wasn't actually correct, because it didn't trigger the necessary type resolution. I could have tried to fix this, but there's actually a simpler solution! If the `test_functions` declaration isn't referenced or has a compile error, then we simply don't need to update it; either it's unreferenced so its value doesn't matter, or we're going to get a compile error anyway. Either way, we can just give up early. This avoids doing semantic analysis after `performAllTheWork` finishes. Also, get rid of the "Code Generation" progress node while updating the test decl: this is a linking task.

2 files changed, 27 insertions(+), 33 deletions(-)

src/Compilation.zig+1-1
...@@ -2817,7 +2817,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {...@@ -2817,7 +2817,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
2817 // The `test_functions` decl has been intentionally postponed until now,2817 // The `test_functions` decl has been intentionally postponed until now,
2818 // at which point we must populate it with the list of test functions that2818 // at which point we must populate it with the list of test functions that
2819 // have been discovered and not filtered out.2819 // have been discovered and not filtered out.
2820 try pt.populateTestFunctions(main_progress_node);2820 try pt.populateTestFunctions();
2821 }2821 }
28222822
2823 try pt.processExports();2823 try pt.processExports();
src/Zcu/PerThread.zig+26-32
...@@ -3229,39 +3229,42 @@ fn processExportsInner(...@@ -3229,39 +3229,42 @@ fn processExportsInner(
3229 }3229 }
3230}3230}
32313231
3232pub fn populateTestFunctions(3232pub fn populateTestFunctions(pt: Zcu.PerThread) Allocator.Error!void {
3233 pt: Zcu.PerThread,
3234 main_progress_node: std.Progress.Node,
3235) Allocator.Error!void {
3236 const zcu = pt.zcu;3233 const zcu = pt.zcu;
3237 const gpa = zcu.gpa;3234 const gpa = zcu.gpa;
3238 const ip = &zcu.intern_pool;3235 const ip = &zcu.intern_pool;
3236
3237 // Our job is to correctly set the value of the `test_functions` declaration if it has been
3238 // analyzed and sent to codegen, It usually will have been, because the test runner will
3239 // reference it, and `std.builtin` shouldn't have type errors. However, if it hasn't been
3240 // analyzed, we will just terminate early, since clearly the test runner hasn't referenced
3241 // `test_functions` so there's no point populating it. More to the the point, we potentially
3242 // *can't* populate it without doing some type resolution, and... let's try to leave Sema in
3243 // the past here.
3244
3239 const builtin_mod = zcu.builtin_modules.get(zcu.root_mod.getBuiltinOptions(zcu.comp.config).hash()).?;3245 const builtin_mod = zcu.builtin_modules.get(zcu.root_mod.getBuiltinOptions(zcu.comp.config).hash()).?;
3240 const builtin_file_index = zcu.module_roots.get(builtin_mod).?.unwrap().?;3246 const builtin_file_index = zcu.module_roots.get(builtin_mod).?.unwrap().?;
3241 pt.ensureFileAnalyzed(builtin_file_index) catch |err| switch (err) {3247 const builtin_root_type = zcu.fileRootType(builtin_file_index);
3242 error.AnalysisFail => unreachable, // builtin module is generated so cannot be corrupt3248 if (builtin_root_type == .none) return; // `@import("builtin")` never analyzed
3243 error.OutOfMemory => |e| return e,3249 const builtin_namespace = Type.fromInterned(builtin_root_type).getNamespace(zcu).unwrap().?;
3244 };3250 // We know that the namespace has a `test_functions`...
3245 const builtin_root_type = Type.fromInterned(zcu.fileRootType(builtin_file_index));
3246 const builtin_namespace = builtin_root_type.getNamespace(zcu).unwrap().?;
3247 const nav_index = zcu.namespacePtr(builtin_namespace).pub_decls.getKeyAdapted(3251 const nav_index = zcu.namespacePtr(builtin_namespace).pub_decls.getKeyAdapted(
3248 try ip.getOrPutString(gpa, pt.tid, "test_functions", .no_embedded_nulls),3252 try ip.getOrPutString(gpa, pt.tid, "test_functions", .no_embedded_nulls),
3249 Zcu.Namespace.NameAdapter{ .zcu = zcu },3253 Zcu.Namespace.NameAdapter{ .zcu = zcu },
3250 ).?;3254 ).?;
3255 // ...but it might not be populated, so let's check that!
3256 if (zcu.failed_analysis.contains(.wrap(.{ .nav_val = nav_index })) or
3257 zcu.transitive_failed_analysis.contains(.wrap(.{ .nav_val = nav_index })) or
3258 ip.getNav(nav_index).status != .fully_resolved)
3251 {3259 {
3252 // We have to call `ensureNavValUpToDate` here in case `builtin.test_functions`3260 // The value of `builtin.test_functions` was either never referenced, or failed analysis.
3253 // was not referenced by start code.3261 // Either way, we don't need to do anything.
3254 zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);3262 return;
3255 defer {
3256 zcu.sema_prog_node.end();
3257 zcu.sema_prog_node = std.Progress.Node.none;
3258 }
3259 pt.ensureNavValUpToDate(nav_index) catch |err| switch (err) {
3260 error.AnalysisFail => return,
3261 error.OutOfMemory => return error.OutOfMemory,
3262 };
3263 }3263 }
32643264
3265 // Okay, `builtin.test_functions` is (potentially) referenced and valid. Our job now is to swap
3266 // its placeholder `&.{}` value for the actual list of all test functions.
3267
3265 const test_fns_val = zcu.navValue(nav_index);3268 const test_fns_val = zcu.navValue(nav_index);
3266 const test_fn_ty = test_fns_val.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu);3269 const test_fn_ty = test_fns_val.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu);
32673270
...@@ -3363,17 +3366,8 @@ pub fn populateTestFunctions(...@@ -3363,17 +3366,8 @@ pub fn populateTestFunctions(
3363 } });3366 } });
3364 ip.mutateVarInit(test_fns_val.toIntern(), new_init);3367 ip.mutateVarInit(test_fns_val.toIntern(), new_init);
3365 }3368 }
3366 {3369 // The linker thread is not running, so we actually need to dispatch this task directly.
3367 assert(zcu.codegen_prog_node.index == .none);3370 @import("../link.zig").linkTestFunctionsNav(pt, nav_index);
3368 zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
3369 defer {
3370 zcu.codegen_prog_node.end();
3371 zcu.codegen_prog_node = std.Progress.Node.none;
3372 }
3373
3374 // The linker thread is not running, so we actually need to dispatch this task directly.
3375 @import("../link.zig").linkTestFunctionsNav(pt, nav_index);
3376 }
3377}3371}
33783372
3379/// Stores an error in `pt.zcu.failed_files` for this file, and sets the file3373/// Stores an error in `pt.zcu.failed_files` for this file, and sets the file