authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-03-08 12:49:15+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2024-03-14 07:40:06+00:00
log42fedb3f6c8f9c54da8faea35f424cbd3f72c386
treea3dcfd0af44e9e25c8509d3e5db12040ea36a5c8
parentd4ce1ec8dd2bb63bafda7e6fe23315d167492a1b
signaturelock-open Commit is signed but in an unrecognized format.

Zcu: handle incremental updates (more) correctly when scanning namespaces


1 files changed, 83 insertions(+), 52 deletions(-)

src/Module.zig+83-52
...@@ -4076,17 +4076,34 @@ pub fn scanNamespace(...@@ -4076,17 +4076,34 @@ pub fn scanNamespace(
4076 const gpa = zcu.gpa;4076 const gpa = zcu.gpa;
4077 const namespace = zcu.namespacePtr(namespace_index);4077 const namespace = zcu.namespacePtr(namespace_index);
40784078
4079 // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather
4080 // than their name. We'll build an efficient mapping now, then discard the current `decls`.
4081 var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index) = .{};
4082 defer existing_by_inst.deinit(gpa);
4083
4084 try existing_by_inst.ensureTotalCapacity(namespace.decls.count());
4085
4086 for (namespace.decls.keys()) |decl_index| {
4087 const decl = zcu.declPtr(decl_index);
4088 existing_by_inst.putAssumeCapacityNoClobber(decl.zir_decl_index.unwrap().?, decl_index);
4089 }
4090
4079 var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};4091 var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
4080 defer seen_decls.deinit(gpa);4092 defer seen_decls.deinit(gpa);
40814093
4082 try zcu.comp.work_queue.ensureUnusedCapacity(decls.len);4094 try zcu.comp.work_queue.ensureUnusedCapacity(decls.len);
4095
4096 namespace.decls.clearRetainingCapacity();
4083 try namespace.decls.ensureTotalCapacity(gpa, decls.len);4097 try namespace.decls.ensureTotalCapacity(gpa, decls.len);
40844098
4099 namespace.usingnamespace_set.clearRetainingCapacity();
4100
4085 var scan_decl_iter: ScanDeclIter = .{4101 var scan_decl_iter: ScanDeclIter = .{
4086 .zcu = zcu,4102 .zcu = zcu,
4087 .namespace_index = namespace_index,4103 .namespace_index = namespace_index,
4088 .parent_decl = parent_decl,4104 .parent_decl = parent_decl,
4089 .seen_decls = &seen_decls,4105 .seen_decls = &seen_decls,
4106 .existing_by_inst = &existing_by_inst,
4090 .pass = .named,4107 .pass = .named,
4091 };4108 };
4092 for (decls) |decl_inst| {4109 for (decls) |decl_inst| {
...@@ -4118,6 +4135,7 @@ const ScanDeclIter = struct {...@@ -4118,6 +4135,7 @@ const ScanDeclIter = struct {
4118 namespace_index: Namespace.Index,4135 namespace_index: Namespace.Index,
4119 parent_decl: *Decl,4136 parent_decl: *Decl,
4120 seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),4137 seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
4138 existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Decl.Index),
4121 /// Decl scanning is run in two passes, so that we can detect when a generated4139 /// Decl scanning is run in two passes, so that we can detect when a generated
4122 /// name would clash with an explicit name and use a different one.4140 /// name would clash with an explicit name and use a different one.
4123 pass: enum { named, unnamed },4141 pass: enum { named, unnamed },
...@@ -4222,72 +4240,85 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void...@@ -4222,72 +4240,85 @@ fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void
4222 },4240 },
4223 };4241 };
42244242
4225 if (kind == .@"usingnamespace") try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1);4243 switch (kind) {
4244 .@"usingnamespace" => try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1),
4245 .@"test" => try zcu.test_functions.ensureUnusedCapacity(gpa, 1),
4246 else => {},
4247 }
42264248
4227 const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst);4249 const tracked_inst = try ip.trackZir(gpa, iter.parent_decl.getFileScope(zcu), decl_inst);
42284250
4229 // We create a Decl for it regardless of analysis status.4251 // We create a Decl for it regardless of analysis status.
4230 const gop = try namespace.decls.getOrPutContextAdapted(4252
4231 gpa,4253 const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: {
4232 decl_name,4254 // We need only update this existing Decl.
4233 DeclAdapter{ .zcu = zcu },4255 const decl = zcu.declPtr(decl_index);
4234 Namespace.DeclContext{ .zcu = zcu },4256 const was_exported = decl.is_exported;
4235 );4257 assert(decl.kind == kind); // ZIR tracking should preserve this
4236 const comp = zcu.comp;4258 assert(decl.alive);
4237 if (!gop.found_existing) {4259 decl.name = decl_name;
4260 decl.src_node = decl_node;
4261 decl.src_line = line;
4262 decl.is_pub = declaration.flags.is_pub;
4263 decl.is_exported = declaration.flags.is_export;
4264 break :decl_index .{ was_exported, decl_index };
4265 } else decl_index: {
4266 // Create and set up a new Decl.
4238 const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node);4267 const new_decl_index = try zcu.allocateNewDecl(namespace_index, decl_node);
4239 const new_decl = zcu.declPtr(new_decl_index);4268 const new_decl = zcu.declPtr(new_decl_index);
4240 new_decl.kind = kind;4269 new_decl.kind = kind;
4241 new_decl.name = decl_name;4270 new_decl.name = decl_name;
4242 if (kind == .@"usingnamespace") {
4243 namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, declaration.flags.is_pub);
4244 }
4245 new_decl.src_line = line;4271 new_decl.src_line = line;
4246 gop.key_ptr.* = new_decl_index;
4247 // Exported decls, comptime decls, usingnamespace decls, and
4248 // test decls if in test mode, get analyzed.
4249 const decl_mod = namespace.file_scope.mod;
4250 const want_analysis = declaration.flags.is_export or switch (kind) {
4251 .anon => unreachable,
4252 .@"comptime", .@"usingnamespace" => true,
4253 .named => false,
4254 .@"test" => a: {
4255 if (!comp.config.is_test) break :a false;
4256 if (decl_mod != zcu.main_mod) break :a false;
4257 if (is_named_test and comp.test_filters.len > 0) {
4258 const decl_fqn = ip.stringToSlice(try namespace.fullyQualifiedName(zcu, decl_name));
4259 for (comp.test_filters) |test_filter| {
4260 if (mem.indexOf(u8, decl_fqn, test_filter)) |_| break;
4261 } else break :a false;
4262 }
4263 try zcu.test_functions.put(gpa, new_decl_index, {});
4264 break :a true;
4265 },
4266 };
4267 if (want_analysis) {
4268 log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{s}' decl_index={d}", .{
4269 namespace.file_scope.sub_file_path, ip.stringToSlice(decl_name), new_decl_index,
4270 });
4271 comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = new_decl_index });
4272 }
4273 new_decl.is_pub = declaration.flags.is_pub;4272 new_decl.is_pub = declaration.flags.is_pub;
4274 new_decl.is_exported = declaration.flags.is_export;4273 new_decl.is_exported = declaration.flags.is_export;
4275 new_decl.zir_decl_index = tracked_inst.toOptional();4274 new_decl.zir_decl_index = tracked_inst.toOptional();
4276 new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive.4275 new_decl.alive = true; // This Decl corresponds to an AST node and is therefore always alive.
4277 return;4276 break :decl_index .{ false, new_decl_index };
4278 }4277 };
4279 const decl_index = gop.key_ptr.*;4278
4280 const decl = zcu.declPtr(decl_index);4279 const decl = zcu.declPtr(decl_index);
4281 // Update the AST node of the decl; even if its contents are unchanged, it may4280
4282 // have been re-ordered.4281 namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu });
4283 decl.src_node = decl_node;4282
4284 decl.src_line = line;4283 const comp = zcu.comp;
42854284 const decl_mod = namespace.file_scope.mod;
4286 decl.is_pub = declaration.flags.is_pub;4285 const want_analysis = declaration.flags.is_export or switch (kind) {
4287 decl.is_exported = declaration.flags.is_export;4286 .anon => unreachable,
4288 decl.kind = kind;4287 .@"comptime" => true,
4289 decl.zir_decl_index = tracked_inst.toOptional();4288 .@"usingnamespace" => a: {
4289 namespace.usingnamespace_set.putNoClobber(decl_index, declaration.flags.is_pub);
4290 break :a true;
4291 },
4292 .named => false,
4293 .@"test" => a: {
4294 if (!comp.config.is_test) break :a false;
4295 if (decl_mod != zcu.main_mod) break :a false;
4296 if (is_named_test and comp.test_filters.len > 0) {
4297 const decl_fqn = ip.stringToSlice(try namespace.fullyQualifiedName(zcu, decl_name));
4298 for (comp.test_filters) |test_filter| {
4299 if (mem.indexOf(u8, decl_fqn, test_filter)) |_| break;
4300 } else break :a false;
4301 }
4302 zcu.test_functions.putAssumeCapacity(decl_index, {}); // may clobber on incremental update
4303 break :a true;
4304 },
4305 };
4306
4307 if (want_analysis) {
4308 // We will not queue analysis if the decl has been analyzed on a previous update and
4309 // `is_export` is unchanged. In this case, the incremental update mechanism will handle
4310 // re-analysis for us if necessary.
4311 if (prev_exported != declaration.flags.is_export or decl.analysis == .unreferenced) {
4312 log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{s}' decl_index={d}", .{
4313 namespace.file_scope.sub_file_path, ip.stringToSlice(decl_name), decl_index,
4314 });
4315 comp.work_queue.writeItemAssumeCapacity(.{ .analyze_decl = decl_index });
4316 }
4317 }
4318
4290 if (decl.getOwnedFunction(zcu) != null) {4319 if (decl.getOwnedFunction(zcu) != null) {
4320 // TODO this logic is insufficient; namespaces we don't re-scan may still require
4321 // updated line numbers. Look into this!
4291 // TODO Look into detecting when this would be unnecessary by storing enough state4322 // TODO Look into detecting when this would be unnecessary by storing enough state
4292 // in `Decl` to notice that the line number did not change.4323 // in `Decl` to notice that the line number did not change.
4293 comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index });4324 comp.work_queue.writeItemAssumeCapacity(.{ .update_line_number = decl_index });