| ... | ... | @@ -27,7 +27,9 @@ const introspect = @import("../introspect.zig"); |
| 27 | 27 | const Module = @import("../Package.zig").Module; |
| 28 | 28 | const Sema = @import("../Sema.zig"); |
| 29 | 29 | const target_util = @import("../target.zig"); |
| 30 | | const trace = @import("../tracy.zig").trace; |
| 30 | const tracy = @import("../tracy.zig"); |
| 31 | const trace = tracy.trace; |
| 32 | const traceNamed = tracy.traceNamed; |
| 31 | 33 | const Type = @import("../Type.zig"); |
| 32 | 34 | const Value = @import("../Value.zig"); |
| 33 | 35 | const Zcu = @import("../Zcu.zig"); |
| ... | ... | @@ -125,6 +127,318 @@ pub fn deactivate(pt: Zcu.PerThread) void { |
| 125 | 127 | pt.zcu.intern_pool.deactivate(); |
| 126 | 128 | } |
| 127 | 129 | |
| 130 | /// Called from `Compilation.performAllTheWork`. Performs one incremental update of the ZCU: detects |
| 131 | /// changes to files, runs AstGen, and then enters the main semantic analysis loop, where we build |
| 132 | /// up a graph of declarations, functions, etc, while also sending declarations and functions to |
| 133 | /// codegen as they are analyzed. |
| 134 | pub fn update( |
| 135 | pt: Zcu.PerThread, |
| 136 | main_progress_node: std.Progress.Node, |
| 137 | decl_work_timer: *?Compilation.Timer, |
| 138 | ) (Allocator.Error || Io.Cancelable)!void { |
| 139 | const zcu = pt.zcu; |
| 140 | const comp = zcu.comp; |
| 141 | const gpa = comp.gpa; |
| 142 | const io = comp.io; |
| 143 | |
| 144 | { |
| 145 | const tracy_trace = traceNamed(@src(), "astgen"); |
| 146 | defer tracy_trace.end(); |
| 147 | |
| 148 | const zir_prog_node = main_progress_node.start("AST Lowering", 0); |
| 149 | defer zir_prog_node.end(); |
| 150 | |
| 151 | var timer = comp.startTimer(); |
| 152 | defer if (timer.finish(io)) |ns| { |
| 153 | comp.mutex.lockUncancelable(io); |
| 154 | defer comp.mutex.unlock(io); |
| 155 | comp.time_report.?.stats.real_ns_files = ns; |
| 156 | }; |
| 157 | |
| 158 | var astgen_group: Io.Group = .init; |
| 159 | defer astgen_group.cancel(io); |
| 160 | |
| 161 | // We cannot reference `zcu.import_table` after we spawn any `workerUpdateFile` jobs, |
| 162 | // because on single-threaded targets the worker will be run eagerly, meaning the |
| 163 | // `import_table` could be mutated, and not even holding `comp.mutex` will save us. So, |
| 164 | // build up a list of the files to update *before* we spawn any jobs. |
| 165 | var astgen_work_items: std.MultiArrayList(struct { |
| 166 | file_index: Zcu.File.Index, |
| 167 | file: *Zcu.File, |
| 168 | }) = .empty; |
| 169 | defer astgen_work_items.deinit(gpa); |
| 170 | // Not every item in `import_table` will need updating, because some are builtin.zig |
| 171 | // files. However, most will, so let's just reserve sufficient capacity upfront. |
| 172 | try astgen_work_items.ensureTotalCapacity(gpa, zcu.import_table.count()); |
| 173 | for (zcu.import_table.keys()) |file_index| { |
| 174 | const file = zcu.fileByIndex(file_index); |
| 175 | if (file.is_builtin) { |
| 176 | // This is a `builtin.zig`, so updating is redundant. However, we want to make |
| 177 | // sure the file contents are still correct on disk, since it can improve the |
| 178 | // debugging experience better. That job only needs `file`, so we can kick it |
| 179 | // off right now. |
| 180 | astgen_group.async(io, workerUpdateBuiltinFile, .{ comp, file }); |
| 181 | continue; |
| 182 | } |
| 183 | astgen_work_items.appendAssumeCapacity(.{ |
| 184 | .file_index = file_index, |
| 185 | .file = file, |
| 186 | }); |
| 187 | } |
| 188 | |
| 189 | // Now that we're not going to touch `zcu.import_table` again, we can spawn `workerUpdateFile` jobs. |
| 190 | for (astgen_work_items.items(.file_index), astgen_work_items.items(.file)) |file_index, file| { |
| 191 | astgen_group.async(io, workerUpdateFile, .{ |
| 192 | comp, file, file_index, zir_prog_node, &astgen_group, |
| 193 | }); |
| 194 | } |
| 195 | |
| 196 | // On the other hand, it's fine to directly iterate `zcu.embed_table.keys()` here |
| 197 | // because `workerUpdateEmbedFile` can't invalidate it. The different here is that one |
| 198 | // `@embedFile` can't trigger analysis of a new `@embedFile`! |
| 199 | for (0.., zcu.embed_table.keys()) |ef_index_usize, ef| { |
| 200 | const ef_index: Zcu.EmbedFile.Index = @enumFromInt(ef_index_usize); |
| 201 | astgen_group.async(io, workerUpdateEmbedFile, .{ |
| 202 | comp, ef_index, ef, |
| 203 | }); |
| 204 | } |
| 205 | |
| 206 | try astgen_group.await(io); |
| 207 | } |
| 208 | |
| 209 | // On an incremental update, a source file might become "dead", in that all imports of |
| 210 | // the file were removed. This could even change what module the file belongs to! As such, |
| 211 | // we do a traversal over the files, to figure out which ones are alive and the modules |
| 212 | // they belong to. |
| 213 | const any_fatal_files = try pt.computeAliveFiles(); |
| 214 | |
| 215 | // If the cache mode is `whole`, add every alive source file to the manifest. |
| 216 | switch (comp.cache_use) { |
| 217 | .whole => |whole| if (whole.cache_manifest) |man| { |
| 218 | for (zcu.alive_files.keys()) |file_index| { |
| 219 | const file = zcu.fileByIndex(file_index); |
| 220 | |
| 221 | switch (file.status) { |
| 222 | .never_loaded => unreachable, // AstGen tried to load it |
| 223 | .retryable_failure => continue, // the file cannot be read; this is a guaranteed error |
| 224 | .astgen_failure, .success => {}, // the file was read successfully |
| 225 | } |
| 226 | |
| 227 | const path = try file.path.toAbsolute(comp.dirs, gpa); |
| 228 | defer gpa.free(path); |
| 229 | |
| 230 | const result = res: { |
| 231 | try whole.cache_manifest_mutex.lock(io); |
| 232 | defer whole.cache_manifest_mutex.unlock(io); |
| 233 | if (file.source) |source| { |
| 234 | break :res man.addFilePostContents(path, source, file.stat); |
| 235 | } else { |
| 236 | break :res man.addFilePost(path); |
| 237 | } |
| 238 | }; |
| 239 | result catch |err| switch (err) { |
| 240 | error.OutOfMemory => |e| return e, |
| 241 | else => { |
| 242 | try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); |
| 243 | continue; |
| 244 | }, |
| 245 | }; |
| 246 | } |
| 247 | }, |
| 248 | .none, .incremental => {}, |
| 249 | } |
| 250 | |
| 251 | if (comp.time_report) |*tr| { |
| 252 | tr.stats.n_reachable_files = @intCast(zcu.alive_files.count()); |
| 253 | } |
| 254 | |
| 255 | if (any_fatal_files or |
| 256 | zcu.multi_module_err != null or |
| 257 | zcu.failed_imports.items.len > 0 or |
| 258 | comp.alloc_failure_occurred) |
| 259 | { |
| 260 | // We give up right now! No updating of ZIR refs, no nothing. The idea is that this prevents |
| 261 | // us from invalidating lots of incremental dependencies due to files with e.g. parse errors. |
| 262 | // However, this means our analysis data is invalid, so we want to omit all analysis errors. |
| 263 | zcu.skip_analysis_this_update = true; |
| 264 | return; |
| 265 | } |
| 266 | |
| 267 | if (comp.config.incremental) { |
| 268 | const update_zir_refs_node = main_progress_node.start("Update ZIR References", 0); |
| 269 | defer update_zir_refs_node.end(); |
| 270 | try pt.updateZirRefs(); |
| 271 | } |
| 272 | |
| 273 | try zcu.flushRetryableFailures(); |
| 274 | |
| 275 | if (!zcu.backendSupportsFeature(.separate_thread)) { |
| 276 | // Close the ZCU task queue. Prelink may still be running, but the closed |
| 277 | // queue will cause the linker task to exit once prelink finishes. The |
| 278 | // closed queue also communicates to `enqueueZcu` that it should wait for |
| 279 | // the linker task to finish and then run ZCU tasks serially. |
| 280 | comp.link_queue.finishZcuQueue(comp); |
| 281 | } |
| 282 | |
| 283 | zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); |
| 284 | if (comp.bin_file != null) { |
| 285 | zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); |
| 286 | } |
| 287 | // We increment `pending_codegen_jobs` so that it doesn't reach 0 until after analysis finishes. |
| 288 | // That prevents the "Code Generation" node from constantly disappearing and reappearing when |
| 289 | // we're probably going to analyze more functions at some point. |
| 290 | assert(zcu.pending_codegen_jobs.swap(1, .monotonic) == 0); // don't let this become 0 until analysis finishes |
| 291 | |
| 292 | defer { |
| 293 | zcu.sema_prog_node.end(); |
| 294 | zcu.sema_prog_node = .none; |
| 295 | if (zcu.pending_codegen_jobs.fetchSub(1, .monotonic) == 1) { |
| 296 | // Decremented to 0, so all done. |
| 297 | zcu.codegen_prog_node.end(); |
| 298 | zcu.codegen_prog_node = .none; |
| 299 | } |
| 300 | } |
| 301 | |
| 302 | // Start the timer for the "decls" part of the pipeline (Sema, CodeGen, link). |
| 303 | decl_work_timer.* = comp.startTimer(); |
| 304 | |
| 305 | // To kick off semantic analysis, populate the root source file of any module we have marked |
| 306 | // as an analysis root. Declarations in these files which want eager analysis---those being |
| 307 | // `comptime` declarations, any declarations marked `export`, and `test` declarations in the |
| 308 | // main module if this is a test compilation---become referenced, and so will be picked up |
| 309 | // up by the main semantic analysis loop below. |
| 310 | for (zcu.analysisRoots()) |analysis_root_mod| { |
| 311 | const analysis_root_file = zcu.module_roots.get(analysis_root_mod).?.unwrap().?; |
| 312 | try pt.ensureFilePopulated(analysis_root_file); |
| 313 | } |
| 314 | |
| 315 | // This is the main semantic analysis loop, which is essentially the main loop of the whole |
| 316 | // Zig compilation pipeline. It selects some `AnalUnit` which we know needs to be analyzed, |
| 317 | // and analyzes it, which may in turn discover more `AnalUnit`s which we need to analyze. |
| 318 | while (try zcu.findOutdatedToAnalyze()) |unit| { |
| 319 | const tracy_trace = traceNamed(@src(), "analyze_outdated"); |
| 320 | defer tracy_trace.end(); |
| 321 | |
| 322 | const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) { |
| 323 | .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu), |
| 324 | .nav_ty => |nav| pt.ensureNavTypeUpToDate(nav, null), |
| 325 | .nav_val => |nav| pt.ensureNavValUpToDate(nav, null), |
| 326 | .type_layout => |ty| pt.ensureTypeLayoutUpToDate(.fromInterned(ty), null), |
| 327 | .memoized_state => |stage| pt.ensureMemoizedStateUpToDate(stage, null), |
| 328 | .func => |func| pt.ensureFuncBodyUpToDate(func, null), |
| 329 | }; |
| 330 | maybe_err catch |err| switch (err) { |
| 331 | error.OutOfMemory, |
| 332 | error.Canceled, |
| 333 | => |e| return e, |
| 334 | |
| 335 | error.AnalysisFail => {}, // already reported |
| 336 | }; |
| 337 | } |
| 338 | } |
| 339 | fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void { |
| 340 | Builtin.updateFileOnDisk(file, comp) catch |err| comp.lockAndSetMiscFailure( |
| 341 | .write_builtin_zig, |
| 342 | "unable to write '{f}': {s}", |
| 343 | .{ file.path.fmt(comp), @errorName(err) }, |
| 344 | ); |
| 345 | } |
| 346 | fn workerUpdateFile( |
| 347 | comp: *Compilation, |
| 348 | file: *Zcu.File, |
| 349 | file_index: Zcu.File.Index, |
| 350 | prog_node: std.Progress.Node, |
| 351 | group: *Io.Group, |
| 352 | ) void { |
| 353 | const io = comp.io; |
| 354 | const tid: Zcu.PerThread.Id = .acquire(io); |
| 355 | defer tid.release(io); |
| 356 | |
| 357 | const child_prog_node = prog_node.start(std.fs.path.basename(file.path.sub_path), 0); |
| 358 | defer child_prog_node.end(); |
| 359 | |
| 360 | const pt: Zcu.PerThread = .activate(comp.zcu.?, tid); |
| 361 | defer pt.deactivate(); |
| 362 | pt.updateFile(file_index, file) catch |err| { |
| 363 | pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ std.fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) { |
| 364 | error.OutOfMemory => { |
| 365 | comp.mutex.lockUncancelable(io); |
| 366 | defer comp.mutex.unlock(io); |
| 367 | comp.setAllocFailure(); |
| 368 | }, |
| 369 | }; |
| 370 | return; |
| 371 | }; |
| 372 | |
| 373 | switch (file.getMode()) { |
| 374 | .zig => {}, // continue to logic below |
| 375 | .zon => return, // ZON can't import anything so we're done |
| 376 | } |
| 377 | |
| 378 | // Discover all imports in the file. Imports of modules we ignore for now since we don't |
| 379 | // know which module we're in, but imports of file paths might need us to queue up other |
| 380 | // AstGen jobs. |
| 381 | const imports_index = file.zir.?.extra[@intFromEnum(Zir.ExtraIndex.imports)]; |
| 382 | if (imports_index != 0) { |
| 383 | const extra = file.zir.?.extraData(Zir.Inst.Imports, imports_index); |
| 384 | var import_i: u32 = 0; |
| 385 | var extra_index = extra.end; |
| 386 | |
| 387 | while (import_i < extra.data.imports_len) : (import_i += 1) { |
| 388 | const item = file.zir.?.extraData(Zir.Inst.Imports.Item, extra_index); |
| 389 | extra_index = item.end; |
| 390 | |
| 391 | const import_path = file.zir.?.nullTerminatedString(item.data.name); |
| 392 | |
| 393 | if (pt.discoverImport(file.path, import_path)) |res| switch (res) { |
| 394 | .module, .existing_file => {}, |
| 395 | .new_file => |new| { |
| 396 | group.async(io, workerUpdateFile, .{ |
| 397 | comp, new.file, new.index, prog_node, group, |
| 398 | }); |
| 399 | }, |
| 400 | } else |err| switch (err) { |
| 401 | error.OutOfMemory => { |
| 402 | comp.mutex.lockUncancelable(io); |
| 403 | defer comp.mutex.unlock(io); |
| 404 | comp.setAllocFailure(); |
| 405 | }, |
| 406 | } |
| 407 | } |
| 408 | } |
| 409 | } |
| 410 | fn workerUpdateEmbedFile(comp: *Compilation, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) void { |
| 411 | const io = comp.io; |
| 412 | const tid: Zcu.PerThread.Id = .acquire(io); |
| 413 | defer tid.release(io); |
| 414 | detectEmbedFileUpdate(comp, tid, ef_index, ef) catch |err| switch (err) { |
| 415 | error.OutOfMemory => { |
| 416 | comp.mutex.lockUncancelable(io); |
| 417 | defer comp.mutex.unlock(io); |
| 418 | comp.setAllocFailure(); |
| 419 | }, |
| 420 | }; |
| 421 | } |
| 422 | fn detectEmbedFileUpdate(comp: *Compilation, tid: Zcu.PerThread.Id, ef_index: Zcu.EmbedFile.Index, ef: *Zcu.EmbedFile) !void { |
| 423 | const io = comp.io; |
| 424 | const zcu = comp.zcu.?; |
| 425 | const pt: Zcu.PerThread = .activate(zcu, tid); |
| 426 | defer pt.deactivate(); |
| 427 | |
| 428 | const old_val = ef.val; |
| 429 | const old_err = ef.err; |
| 430 | |
| 431 | try pt.updateEmbedFile(ef, null); |
| 432 | |
| 433 | if (ef.val != .none and ef.val == old_val) return; // success, value unchanged |
| 434 | if (ef.val == .none and old_val == .none and ef.err == old_err) return; // failure, error unchanged |
| 435 | |
| 436 | comp.mutex.lockUncancelable(io); |
| 437 | defer comp.mutex.unlock(io); |
| 438 | |
| 439 | try zcu.markDependeeOutdated(.not_marked_po, .{ .embed_file = ef_index }); |
| 440 | } |
| 441 | |
| 128 | 442 | fn deinitFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void { |
| 129 | 443 | const zcu = pt.zcu; |
| 130 | 444 | const gpa = zcu.gpa; |
| ... | ... | @@ -156,8 +470,8 @@ pub fn updateFile( |
| 156 | 470 | ) !void { |
| 157 | 471 | dev.check(.ast_gen); |
| 158 | 472 | |
| 159 | | const tracy = trace(@src()); |
| 160 | | defer tracy.end(); |
| 473 | const tracy_trace = trace(@src()); |
| 474 | defer tracy_trace.end(); |
| 161 | 475 | |
| 162 | 476 | const zcu = pt.zcu; |
| 163 | 477 | const comp = zcu.comp; |
| ... | ... | @@ -484,7 +798,7 @@ fn cleanupUpdatedFiles(gpa: Allocator, updated_files: *std.AutoArrayHashMapUnman |
| 484 | 798 | updated_files.deinit(gpa); |
| 485 | 799 | } |
| 486 | 800 | |
| 487 | | pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { |
| 801 | fn updateZirRefs(pt: Zcu.PerThread) (Io.Cancelable || Allocator.Error)!void { |
| 488 | 802 | assert(pt.tid == .main); |
| 489 | 803 | const zcu = pt.zcu; |
| 490 | 804 | const comp = zcu.comp; |
| ... | ... | @@ -566,7 +880,7 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { |
| 566 | 880 | const old_line = old_zir.getDeclaration(old_inst).src_line; |
| 567 | 881 | const new_line = new_zir.getDeclaration(new_inst).src_line; |
| 568 | 882 | if (old_line != new_line) { |
| 569 | | try comp.queueJob(.{ .update_line_number = tracked_inst_index }); |
| 883 | try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .debug_update_line_number = tracked_inst_index }); |
| 570 | 884 | } |
| 571 | 885 | }, |
| 572 | 886 | else => {}, |
| ... | ... | @@ -674,11 +988,11 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { |
| 674 | 988 | /// Typical Zig compilations begin by claling this function on the root source file of the standard |
| 675 | 989 | /// library, `lib/std/std.zig`. The resulting namespace scan discovers a `comptime` declaration in |
| 676 | 990 | /// that file, which is queued for analysis, and everything goes from there. |
| 677 | | pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) (Allocator.Error || Io.Cancelable)!void { |
| 991 | pub fn ensureFilePopulated(pt: Zcu.PerThread, file_index: Zcu.File.Index) (Allocator.Error || Io.Cancelable)!void { |
| 678 | 992 | dev.check(.sema); |
| 679 | 993 | |
| 680 | | const tracy = trace(@src()); |
| 681 | | defer tracy.end(); |
| 994 | const tracy_trace = trace(@src()); |
| 995 | defer tracy_trace.end(); |
| 682 | 996 | |
| 683 | 997 | const zcu = pt.zcu; |
| 684 | 998 | const comp = zcu.comp; |
| ... | ... | @@ -734,8 +1048,8 @@ pub fn ensureMemoizedStateUpToDate( |
| 734 | 1048 | /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. |
| 735 | 1049 | reason: ?*const Zcu.DependencyReason, |
| 736 | 1050 | ) Zcu.SemaError!void { |
| 737 | | const tracy = trace(@src()); |
| 738 | | defer tracy.end(); |
| 1051 | const tracy_trace = trace(@src()); |
| 1052 | defer tracy_trace.end(); |
| 739 | 1053 | |
| 740 | 1054 | const zcu = pt.zcu; |
| 741 | 1055 | const gpa = zcu.gpa; |
| ... | ... | @@ -844,8 +1158,8 @@ fn analyzeMemoizedState( |
| 844 | 1158 | /// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is |
| 845 | 1159 | /// free to ignore this, since the error is already registered. |
| 846 | 1160 | pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void { |
| 847 | | const tracy = trace(@src()); |
| 848 | | defer tracy.end(); |
| 1161 | const tracy_trace = trace(@src()); |
| 1162 | defer tracy_trace.end(); |
| 849 | 1163 | |
| 850 | 1164 | const zcu = pt.zcu; |
| 851 | 1165 | const gpa = zcu.gpa; |
| ... | ... | @@ -1008,8 +1322,8 @@ pub fn ensureTypeLayoutUpToDate( |
| 1008 | 1322 | /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. |
| 1009 | 1323 | reason: ?*const Zcu.DependencyReason, |
| 1010 | 1324 | ) Zcu.SemaError!void { |
| 1011 | | const tracy = trace(@src()); |
| 1012 | | defer tracy.end(); |
| 1325 | const tracy_trace = trace(@src()); |
| 1326 | defer tracy_trace.end(); |
| 1013 | 1327 | |
| 1014 | 1328 | const zcu = pt.zcu; |
| 1015 | 1329 | const comp = zcu.comp; |
| ... | ... | @@ -1121,8 +1435,8 @@ pub fn ensureNavValUpToDate( |
| 1121 | 1435 | /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. |
| 1122 | 1436 | reason: ?*const Zcu.DependencyReason, |
| 1123 | 1437 | ) Zcu.SemaError!void { |
| 1124 | | const tracy = trace(@src()); |
| 1125 | | defer tracy.end(); |
| 1438 | const tracy_trace = trace(@src()); |
| 1439 | defer tracy_trace.end(); |
| 1126 | 1440 | |
| 1127 | 1441 | const zcu = pt.zcu; |
| 1128 | 1442 | const gpa = zcu.gpa; |
| ... | ... | @@ -1457,12 +1771,20 @@ fn analyzeNavVal( |
| 1457 | 1771 | if (!queue_linker_work) break :queue_codegen; |
| 1458 | 1772 | |
| 1459 | 1773 | if (!nav_ty.hasRuntimeBits(zcu)) { |
| 1460 | | if (zcu.comp.config.use_llvm) break :queue_codegen; |
| 1774 | if (comp.config.use_llvm) break :queue_codegen; |
| 1461 | 1775 | if (file.mod.?.strip) break :queue_codegen; |
| 1462 | 1776 | } |
| 1463 | 1777 | |
| 1464 | | zcu.comp.link_prog_node.increaseEstimatedTotalItems(1); |
| 1465 | | try zcu.comp.queueJob(.{ .link_nav = nav_id }); |
| 1778 | comp.link_prog_node.increaseEstimatedTotalItems(1); |
| 1779 | try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .link_nav = nav_id }); |
| 1780 | } |
| 1781 | |
| 1782 | if (comp.config.is_test and zcu.test_functions.contains(nav_id)) { |
| 1783 | // We just analyzed a test function's "value" (essentially its signature); now we need to |
| 1784 | // implicitly reference the function *body*. `Zcu.resolveReferences` knows about this rule, |
| 1785 | // so we don't need to mark an explicit reference, but we do need to make sure that the test |
| 1786 | // body will actually get analyzed! |
| 1787 | try zcu.ensureFuncBodyAnalysisQueued(nav_val.toIntern()); |
| 1466 | 1788 | } |
| 1467 | 1789 | |
| 1468 | 1790 | switch (old_nav.status) { |
| ... | ... | @@ -1477,8 +1799,8 @@ pub fn ensureNavTypeUpToDate( |
| 1477 | 1799 | /// `null` is valid only for the "root" analysis, i.e. called from `Compilation.processOneJob`. |
| 1478 | 1800 | reason: ?*const Zcu.DependencyReason, |
| 1479 | 1801 | ) Zcu.SemaError!void { |
| 1480 | | const tracy = trace(@src()); |
| 1481 | | defer tracy.end(); |
| 1802 | const tracy_trace = trace(@src()); |
| 1803 | defer tracy_trace.end(); |
| 1482 | 1804 | |
| 1483 | 1805 | const zcu = pt.zcu; |
| 1484 | 1806 | const gpa = zcu.gpa; |
| ... | ... | @@ -1719,8 +2041,8 @@ pub fn ensureFuncBodyUpToDate( |
| 1719 | 2041 | ) Zcu.SemaError!void { |
| 1720 | 2042 | dev.check(.sema); |
| 1721 | 2043 | |
| 1722 | | const tracy = trace(@src()); |
| 1723 | | defer tracy.end(); |
| 2044 | const tracy_trace = trace(@src()); |
| 2045 | defer tracy_trace.end(); |
| 1724 | 2046 | |
| 1725 | 2047 | const zcu = pt.zcu; |
| 1726 | 2048 | const gpa = zcu.gpa; |
| ... | ... | @@ -1846,7 +2168,8 @@ fn analyzeFuncBody( |
| 1846 | 2168 | log.debug("analyze and generate fn body {f}", .{zcu.fmtAnalUnit(anal_unit)}); |
| 1847 | 2169 | |
| 1848 | 2170 | var air = try pt.analyzeFuncBodyInner(func_index, reason); |
| 1849 | | errdefer air.deinit(gpa); |
| 2171 | var air_owned = true; |
| 2172 | errdefer if (air_owned) air.deinit(gpa); |
| 1850 | 2173 | |
| 1851 | 2174 | const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or |
| 1852 | 2175 | func.resolvedErrorSetUnordered(ip) != old_resolved_ies; |
| ... | ... | @@ -1856,17 +2179,22 @@ fn analyzeFuncBody( |
| 1856 | 2179 | const dump_air = build_options.enable_debug_extensions and comp.verbose_air; |
| 1857 | 2180 | const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null); |
| 1858 | 2181 | |
| 1859 | | if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { |
| 1860 | | air.deinit(gpa); |
| 1861 | | return .{ .ies_outdated = ies_outdated }; |
| 1862 | | } |
| 2182 | if (comp.bin_file != null or zcu.llvm_object != null or dump_air or dump_llvm_ir) { |
| 2183 | zcu.codegen_prog_node.increaseEstimatedTotalItems(1); |
| 2184 | comp.link_prog_node.increaseEstimatedTotalItems(1); |
| 1863 | 2185 | |
| 1864 | | zcu.codegen_prog_node.increaseEstimatedTotalItems(1); |
| 1865 | | comp.link_prog_node.increaseEstimatedTotalItems(1); |
| 1866 | | try comp.queueJob(.{ .codegen_func = .{ |
| 1867 | | .func = func_index, |
| 1868 | | .air = air, |
| 1869 | | } }); |
| 2186 | // Some linkers need to refer to the AIR. In that case, the linker is not running |
| 2187 | // concurrently, so we'll just keep ownership of the AIR for ourselves instead of |
| 2188 | // letting the codegen job destroy it. |
| 2189 | const disown_air = zcu.backendSupportsFeature(.separate_thread); |
| 2190 | |
| 2191 | // Begin the codegen task. If the codegen/link queue is backed up, this might |
| 2192 | // block until the linker is able to process some tasks. |
| 2193 | const codegen_task = try zcu.codegen_task_pool.start(zcu, func_index, &air, disown_air); |
| 2194 | if (disown_air) air_owned = false; |
| 2195 | |
| 2196 | try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .link_func = codegen_task }); |
| 2197 | } |
| 1870 | 2198 | |
| 1871 | 2199 | return .{ .ies_outdated = ies_outdated }; |
| 1872 | 2200 | } |
| ... | ... | @@ -2121,7 +2449,7 @@ pub fn populateModuleRootTable(pt: Zcu.PerThread) error{ |
| 2121 | 2449 | /// modify `pt.zcu.skip_analysis_this_update`. |
| 2122 | 2450 | /// |
| 2123 | 2451 | /// If an error is returned, `pt.zcu.alive_files` might contain undefined values. |
| 2124 | | pub fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool { |
| 2452 | fn computeAliveFiles(pt: Zcu.PerThread) Allocator.Error!bool { |
| 2125 | 2453 | const zcu = pt.zcu; |
| 2126 | 2454 | const comp = zcu.comp; |
| 2127 | 2455 | const gpa = zcu.gpa; |
| ... | ... | @@ -2562,8 +2890,8 @@ pub fn scanNamespace( |
| 2562 | 2890 | namespace_index: Zcu.Namespace.Index, |
| 2563 | 2891 | decls: []const Zir.Inst.Index, |
| 2564 | 2892 | ) Allocator.Error!void { |
| 2565 | | const tracy = trace(@src()); |
| 2566 | | defer tracy.end(); |
| 2893 | const tracy_trace = trace(@src()); |
| 2894 | defer tracy_trace.end(); |
| 2567 | 2895 | |
| 2568 | 2896 | const zcu = pt.zcu; |
| 2569 | 2897 | const ip = &zcu.intern_pool; |
| ... | ... | @@ -2659,8 +2987,8 @@ const ScanDeclIter = struct { |
| 2659 | 2987 | } |
| 2660 | 2988 | |
| 2661 | 2989 | fn scanDecl(iter: *ScanDeclIter, decl_inst: Zir.Inst.Index) Allocator.Error!void { |
| 2662 | | const tracy = trace(@src()); |
| 2663 | | defer tracy.end(); |
| 2990 | const tracy_trace = trace(@src()); |
| 2991 | defer tracy_trace.end(); |
| 2664 | 2992 | |
| 2665 | 2993 | const pt = iter.pt; |
| 2666 | 2994 | const zcu = pt.zcu; |
| ... | ... | @@ -2713,77 +3041,65 @@ const ScanDeclIter = struct { |
| 2713 | 3041 | |
| 2714 | 3042 | const existing_unit = iter.existing_by_inst.get(tracked_inst); |
| 2715 | 3043 | |
| 2716 | | const unit: AnalUnit, const want_analysis = switch (decl.kind) { |
| 2717 | | .@"comptime" => unit: { |
| 2718 | | const cu = if (existing_unit) |eu| |
| 2719 | | eu.unwrap().@"comptime" |
| 2720 | | else |
| 2721 | | try ip.createComptimeUnit(gpa, io, pt.tid, tracked_inst, namespace_index); |
| 2722 | | |
| 3044 | const name = maybe_name.unwrap() orelse { |
| 3045 | // Only `comptime` declarations are unnamed. |
| 3046 | assert(decl.kind == .@"comptime"); |
| 3047 | if (existing_unit) |unit| { |
| 3048 | try namespace.comptime_decls.append(gpa, unit.unwrap().@"comptime"); |
| 3049 | } else { |
| 3050 | const cu = try ip.createComptimeUnit(gpa, io, pt.tid, tracked_inst, namespace_index); |
| 3051 | try zcu.queueComptimeUnitAnalysis(cu); |
| 2723 | 3052 | try namespace.comptime_decls.append(gpa, cu); |
| 3053 | } |
| 3054 | return; |
| 3055 | }; |
| 2724 | 3056 | |
| 2725 | | if (existing_unit == null) { |
| 2726 | | // For a `comptime` declaration, whether to analyze is based solely on whether the unit |
| 2727 | | // is outdated. So, add this fresh one to `outdated` and `outdated_ready`. |
| 2728 | | try zcu.queueComptimeUnitAnalysis(cu); |
| 2729 | | } |
| 3057 | const fqn = try namespace.internFullyQualifiedName(ip, gpa, io, pt.tid, name); |
| 3058 | |
| 3059 | const nav = if (existing_unit) |unit| nav: { |
| 3060 | const nav = unit.unwrap().nav_val; |
| 3061 | assert(ip.getNav(nav).name == name); |
| 3062 | assert(ip.getNav(nav).fqn == fqn); |
| 3063 | break :nav nav; |
| 3064 | } else nav: { |
| 3065 | const nav = try ip.createDeclNav(gpa, io, pt.tid, name, fqn, tracked_inst, namespace_index); |
| 3066 | if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav); |
| 3067 | break :nav nav; |
| 3068 | }; |
| 2730 | 3069 | |
| 2731 | | break :unit .{ .wrap(.{ .@"comptime" = cu }), true }; |
| 3070 | const want_analysis: bool = switch (decl.kind) { |
| 3071 | .@"comptime" => unreachable, |
| 3072 | .unnamed_test, .@"test", .decltest => a: { |
| 3073 | const is_named = decl.kind != .unnamed_test; |
| 3074 | try namespace.test_decls.append(gpa, nav); |
| 3075 | // TODO: incremental compilation! |
| 3076 | // * remove from `test_functions` if no longer matching filter |
| 3077 | // * add to `test_functions` if newly passing filter |
| 3078 | // This logic is unaware of incremental: we'll end up with duplicates. |
| 3079 | // Perhaps we should add all test indiscriminately and filter at the end of the update. |
| 3080 | if (!comp.config.is_test) break :a false; |
| 3081 | if (file.mod != zcu.main_mod) break :a false; |
| 3082 | if (is_named and comp.test_filters.len > 0) { |
| 3083 | const fqn_slice = fqn.toSlice(ip); |
| 3084 | for (comp.test_filters) |test_filter| { |
| 3085 | if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; |
| 3086 | } else break :a false; |
| 3087 | } |
| 3088 | try zcu.test_functions.put(gpa, nav, {}); |
| 3089 | break :a true; |
| 2732 | 3090 | }, |
| 2733 | | else => unit: { |
| 2734 | | const name = maybe_name.unwrap().?; |
| 2735 | | const fqn = try namespace.internFullyQualifiedName(ip, gpa, io, pt.tid, name); |
| 2736 | | const nav = if (existing_unit) |eu| eu.unwrap().nav_val else nav: { |
| 2737 | | const nav = try ip.createDeclNav(gpa, io, pt.tid, name, fqn, tracked_inst, namespace_index); |
| 2738 | | if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav); |
| 2739 | | break :nav nav; |
| 2740 | | }; |
| 2741 | | |
| 2742 | | const unit: AnalUnit = .wrap(.{ .nav_val = nav }); |
| 2743 | | |
| 2744 | | assert(ip.getNav(nav).name == name); |
| 2745 | | assert(ip.getNav(nav).fqn == fqn); |
| 2746 | | |
| 2747 | | const want_analysis = switch (decl.kind) { |
| 2748 | | .@"comptime" => unreachable, |
| 2749 | | .unnamed_test, .@"test", .decltest => a: { |
| 2750 | | const is_named = decl.kind != .unnamed_test; |
| 2751 | | try namespace.test_decls.append(gpa, nav); |
| 2752 | | // TODO: incremental compilation! |
| 2753 | | // * remove from `test_functions` if no longer matching filter |
| 2754 | | // * add to `test_functions` if newly passing filter |
| 2755 | | // This logic is unaware of incremental: we'll end up with duplicates. |
| 2756 | | // Perhaps we should add all test indiscriminately and filter at the end of the update. |
| 2757 | | if (!comp.config.is_test) break :a false; |
| 2758 | | if (file.mod != zcu.main_mod) break :a false; |
| 2759 | | if (is_named and comp.test_filters.len > 0) { |
| 2760 | | const fqn_slice = fqn.toSlice(ip); |
| 2761 | | for (comp.test_filters) |test_filter| { |
| 2762 | | if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; |
| 2763 | | } else break :a false; |
| 2764 | | } |
| 2765 | | try zcu.test_functions.put(gpa, nav, {}); |
| 2766 | | break :a true; |
| 2767 | | }, |
| 2768 | | .@"const", .@"var" => a: { |
| 2769 | | if (decl.is_pub) { |
| 2770 | | try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); |
| 2771 | | } else { |
| 2772 | | try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); |
| 2773 | | } |
| 2774 | | break :a false; |
| 2775 | | }, |
| 2776 | | }; |
| 2777 | | break :unit .{ unit, want_analysis }; |
| 3091 | .@"const", .@"var" => a: { |
| 3092 | if (decl.is_pub) { |
| 3093 | try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); |
| 3094 | } else { |
| 3095 | try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); |
| 3096 | } |
| 3097 | break :a false; |
| 2778 | 3098 | }, |
| 2779 | 3099 | }; |
| 2780 | 3100 | |
| 2781 | | if (existing_unit == null and (want_analysis or decl.linkage == .@"export")) { |
| 2782 | | log.debug( |
| 2783 | | "scanDecl queue analyze_unit file='{s}' unit={f}", |
| 2784 | | .{ namespace.fileScope(zcu).sub_file_path, zcu.fmtAnalUnit(unit) }, |
| 2785 | | ); |
| 2786 | | try comp.queueJob(.{ .analyze_unit = unit }); |
| 3101 | if (want_analysis or decl.linkage == .@"export") { |
| 3102 | try zcu.ensureNavValAnalysisQueued(nav); |
| 2787 | 3103 | } |
| 2788 | 3104 | } |
| 2789 | 3105 | }; |
| ... | ... | @@ -2793,8 +3109,8 @@ fn analyzeFuncBodyInner( |
| 2793 | 3109 | func_index: InternPool.Index, |
| 2794 | 3110 | reason: ?*const Zcu.DependencyReason, |
| 2795 | 3111 | ) Zcu.SemaError!Air { |
| 2796 | | const tracy = trace(@src()); |
| 2797 | | defer tracy.end(); |
| 3112 | const tracy_trace = trace(@src()); |
| 3113 | defer tracy_trace.end(); |
| 2798 | 3114 | |
| 2799 | 3115 | const zcu = pt.zcu; |
| 2800 | 3116 | const comp = zcu.comp; |
| ... | ... | @@ -3437,36 +3753,45 @@ pub fn intern(pt: Zcu.PerThread, key: InternPool.Key) Allocator.Error!InternPool |
| 3437 | 3753 | |
| 3438 | 3754 | /// Essentially a shortcut for calling `intern_pool.getCoerced`. |
| 3439 | 3755 | /// However, this function also allows coercing `extern`s. The `InternPool` function can't do |
| 3440 | | /// this because it requires potentially pushing to the job queue. |
| 3756 | /// this because it requires potentially queueing a link task. |
| 3441 | 3757 | pub fn getCoerced(pt: Zcu.PerThread, val: Value, new_ty: Type) Allocator.Error!Value { |
| 3442 | 3758 | const ip = &pt.zcu.intern_pool; |
| 3443 | 3759 | const comp = pt.zcu.comp; |
| 3444 | 3760 | const gpa = comp.gpa; |
| 3445 | 3761 | const io = comp.io; |
| 3446 | 3762 | switch (ip.indexToKey(val.toIntern())) { |
| 3447 | | .@"extern" => |e| { |
| 3448 | | const coerced = try pt.getExtern(.{ |
| 3449 | | .name = e.name, |
| 3763 | .@"extern" => |@"extern"| { |
| 3764 | // TODO: it's awkward to make this function cancelable. The problem is really that |
| 3765 | // `getCoerced` is a bad API: it should be replaced with smaller, more specialized |
| 3766 | // functions, so that this cancel point is only possible in the rare case that you |
| 3767 | // may actually need to coerce an extern! |
| 3768 | const old_prot = io.swapCancelProtection(.blocked); |
| 3769 | defer _ = io.swapCancelProtection(old_prot); |
| 3770 | const coerced = pt.getExtern(.{ |
| 3771 | .name = @"extern".name, |
| 3450 | 3772 | .ty = new_ty.toIntern(), |
| 3451 | | .lib_name = e.lib_name, |
| 3452 | | .is_const = e.is_const, |
| 3453 | | .is_threadlocal = e.is_threadlocal, |
| 3454 | | .linkage = e.linkage, |
| 3455 | | .visibility = e.visibility, |
| 3456 | | .is_dll_import = e.is_dll_import, |
| 3457 | | .relocation = e.relocation, |
| 3458 | | .decoration = e.decoration, |
| 3459 | | .alignment = e.alignment, |
| 3460 | | .@"addrspace" = e.@"addrspace", |
| 3461 | | .zir_index = e.zir_index, |
| 3773 | .lib_name = @"extern".lib_name, |
| 3774 | .is_const = @"extern".is_const, |
| 3775 | .is_threadlocal = @"extern".is_threadlocal, |
| 3776 | .linkage = @"extern".linkage, |
| 3777 | .visibility = @"extern".visibility, |
| 3778 | .is_dll_import = @"extern".is_dll_import, |
| 3779 | .relocation = @"extern".relocation, |
| 3780 | .decoration = @"extern".decoration, |
| 3781 | .alignment = @"extern".alignment, |
| 3782 | .@"addrspace" = @"extern".@"addrspace", |
| 3783 | .zir_index = @"extern".zir_index, |
| 3462 | 3784 | .owner_nav = undefined, // ignored by `getExtern`. |
| 3463 | | .source = e.source, |
| 3464 | | }); |
| 3465 | | return Value.fromInterned(coerced); |
| 3785 | .source = @"extern".source, |
| 3786 | }) catch |err| switch (err) { |
| 3787 | error.Canceled => unreachable, // blocked above |
| 3788 | error.OutOfMemory => |e| return e, |
| 3789 | }; |
| 3790 | return .fromInterned(coerced); |
| 3466 | 3791 | }, |
| 3467 | 3792 | else => {}, |
| 3468 | 3793 | } |
| 3469 | | return Value.fromInterned(try ip.getCoerced(gpa, io, pt.tid, val.toIntern(), new_ty.toIntern())); |
| 3794 | return .fromInterned(try ip.getCoerced(gpa, io, pt.tid, val.toIntern(), new_ty.toIntern())); |
| 3470 | 3795 | } |
| 3471 | 3796 | |
| 3472 | 3797 | pub fn intType(pt: Zcu.PerThread, signedness: std.builtin.Signedness, bits: u16) Allocator.Error!Type { |
| ... | ... | @@ -3865,14 +4190,15 @@ pub fn navPtrType(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Allocator.Err |
| 3865 | 4190 | /// Intern an `.@"extern"`, creating a corresponding owner `Nav` if necessary. |
| 3866 | 4191 | /// If necessary, the new `Nav` is queued for codegen. |
| 3867 | 4192 | /// `key.owner_nav` is ignored and may be `undefined`. |
| 3868 | | pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!InternPool.Index { |
| 4193 | pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) (Io.Cancelable || Allocator.Error)!InternPool.Index { |
| 3869 | 4194 | const zcu = pt.zcu; |
| 3870 | 4195 | const comp = zcu.comp; |
| 4196 | Type.fromInterned(key.ty).assertHasLayout(zcu); |
| 3871 | 4197 | const result = try zcu.intern_pool.getExtern(comp.gpa, comp.io, pt.tid, key); |
| 3872 | 4198 | if (result.new_nav.unwrap()) |nav| { |
| 3873 | | comp.link_prog_node.increaseEstimatedTotalItems(1); |
| 3874 | | try comp.queueJob(.{ .link_nav = nav }); |
| 3875 | 4199 | if (comp.debugIncremental()) try zcu.incremental_debug_state.newNav(zcu, nav); |
| 4200 | comp.link_prog_node.increaseEstimatedTotalItems(1); |
| 4201 | try comp.link_queue.enqueueZcu(comp, pt.tid, .{ .link_nav = nav }); |
| 3876 | 4202 | } |
| 3877 | 4203 | return result.index; |
| 3878 | 4204 | } |