diff options
| author | mlugg <mlugg@mlugg.co.uk> | 2024-12-22 21:16:29 +0000 |
|---|---|---|
| committer | mlugg <mlugg@mlugg.co.uk> | 2024-12-24 02:18:41 +0000 |
| commit | 40aafcd6a85d3c517f445f17149c17523c832420 (patch) | |
| tree | b8e1a5361c6a20ce9e3ba568b61b199aff1c8f13 /src/Zcu/PerThread.zig | |
| parent | 18362ebe13ece2ea7c4f57303ec4687f55d2dba5 (diff) | |
| download | zig-40aafcd6a85d3c517f445f17149c17523c832420.tar.gz zig-40aafcd6a85d3c517f445f17149c17523c832420.zip | |
compiler: remove Cau
The `Cau` abstraction originated from noting that one of the two primary
roles of the legacy `Decl` type was to be the subject of comptime
semantic analysis. However, the data stored in `Cau` has always had some
level of redundancy. While preparing for #131, I went to remove that
redundany, and realised that `Cau` now had exactly one field: `owner`.
This led me to conclude that `Cau` is, in fact, an unnecessary level of
abstraction over what are in reality *fundamentally different* kinds of
analysis unit (`AnalUnit`). Types, `Nav` vals, and `comptime`
declarations are all analyzed in different ways, and trying to treat
them as the same thing is counterproductive!
So, these 3 cases are now different alternatives in `AnalUnit`. To avoid
stealing bits from `InternPool`-based IDs, which are already a little
starved for bits due to the sharding datastructures, `AnalUnit` is
expanded to 64 bits (30 of which are currently unused). This doesn't
impact memory usage too much by default, because we don't store
`AnalUnit`s all too often; however, we do store them a lot under
`-fincremental`, so a non-trivial bump to peak RSS can be observed
there. This will be improved in the future when I made
`InternPool.DepEntry` less memory-inefficient.
`Zcu.PerThread.ensureCauAnalyzed` is split into 3 functions, for each of
the 3 new types of `AnalUnit`. The new logic is much easier to
understand, because it avoids conflating the logic of these
fundamentally different cases.
Diffstat (limited to 'src/Zcu/PerThread.zig')
| -rw-r--r-- | src/Zcu/PerThread.zig | 1421 |
1 files changed, 695 insertions, 726 deletions
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 2cf6e3670c..0d7ca0eb26 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -545,117 +545,270 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void { pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { const file_root_type = pt.zcu.fileRootType(file_index); if (file_root_type != .none) { - _ = try pt.ensureTypeUpToDate(file_root_type, false); + _ = try pt.ensureTypeUpToDate(file_root_type); } else { return pt.semaFile(file_index); } } -/// This ensures that the state of the `Cau`, and of its corresponding `Nav` or type, -/// is fully up-to-date. Note that the type of the `Nav` may not be fully resolved. -/// Returns `error.AnalysisFail` if the `Cau` has an error. -pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu.SemaError!void { +/// Ensures that the state of the given `ComptimeUnit` is fully up-to-date, performing re-analysis +/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is +/// free to ignore this, since the error is already registered. +pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void { const tracy = trace(@src()); defer tracy.end(); const zcu = pt.zcu; const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); - const cau = ip.getCau(cau_index); + const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id }); - log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureComptimeUnitUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); assert(!zcu.analysis_in_progress.contains(anal_unit)); - // Determine whether or not this Cau is outdated, i.e. requires re-analysis - // even if `complete`. If a Cau is PO, we pessismistically assume that it - // *does* require re-analysis, to ensure that the Cau is definitely - // up-to-date when this function returns. - - // If analysis occurs in a poor order, this could result in over-analysis. - // We do our best to avoid this by the other dependency logic in this file - // which tries to limit re-analysis to Caus whose previously listed - // dependencies are all up-to-date. + // Determine whether or not this `ComptimeUnit` is outdated. For this kind of `AnalUnit`, that's + // the only indicator as to whether or not analysis is required; when a `ComptimeUnit` is first + // created, it's marked as outdated. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. - const cau_outdated = zcu.outdated.swapRemove(anal_unit) or + const was_outdated = zcu.outdated.swapRemove(anal_unit) or zcu.potentially_outdated.swapRemove(anal_unit); - const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); - - if (cau_outdated) { + if (was_outdated) { _ = zcu.outdated_ready.swapRemove(anal_unit); + // `was_outdated` can be true in the initial update for comptime units, so this isn't a `dev.check`. + if (dev.env.supports(.incremental)) { + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } } else { - // We can trust the current information about this `Cau`. - if (prev_failed) { + // We can trust the current information about this unit. + if (zcu.failed_analysis.contains(anal_unit)) return error.AnalysisFail; + if (zcu.transitive_failed_analysis.contains(anal_unit)) return error.AnalysisFail; + return; + } + + const unit_prog_node = zcu.sema_prog_node.start("comptime", 0); + defer unit_prog_node.end(); + + return pt.analyzeComptimeUnit(cu_id) catch |err| switch (err) { + error.AnalysisFail => { + if (!zcu.failed_analysis.contains(anal_unit)) { + // If this unit caused the error, it would have an entry in `failed_analysis`. + // Since it does not, this must be a transitive failure. + try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); + log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); + } return error.AnalysisFail; + }, + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + }; +} + +/// Re-analyzes a `ComptimeUnit`. The unit has already been determined to be out-of-date, and old +/// side effects (exports/references/etc) have been dropped. If semantic analysis fails, this +/// function will return `error.AnalysisFail`, and it is the caller's reponsibility to add an entry +/// to `transitive_failed_analysis` if necessary. +fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.CompileError!void { + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id }); + const comptime_unit = ip.getComptimeUnit(cu_id); + + log.debug("analyzeComptimeUnit {}", .{zcu.fmtAnalUnit(anal_unit)}); + + const inst_resolved = comptime_unit.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; + + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + defer assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = zir, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + // The comptime unit declares on the source of the corresponding `comptime` declaration. + try sema.declareDependency(.{ .src_hash = comptime_unit.zir_index }); + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = comptime_unit.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = comptime_unit.zir_index, + .type_name_ctx = try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{ + Type.fromInterned(zcu.namespacePtr(comptime_unit.namespace).owner_type).containerTypeName(ip).fmt(ip), + }, .no_embedded_nulls), + }; + defer block.instructions.deinit(gpa); + + const zir_decl = zir.getDeclaration(inst_resolved.inst); + assert(zir_decl.kind == .@"comptime"); + assert(zir_decl.type_body == null); + assert(zir_decl.align_body == null); + assert(zir_decl.linksection_body == null); + assert(zir_decl.addrspace_body == null); + const value_body = zir_decl.value_body.?; + + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + assert(result_ref == .void_value); // AstGen should always uphold this + + // Nothing else to do -- for a comptime decl, all we care about are the side effects. + // Just make sure to `flushExports`. + try sema.flushExports(); +} + +/// Ensures that the resolved value of the given `Nav` is fully up-to-date, performing re-analysis +/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is +/// free to ignore this, since the error is already registered. +pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void { + const tracy = trace(@src()); + defer tracy.end(); + + // TODO: document this elsewhere mlugg! + // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: + // `const S = struct { ... };` + // We are adding or removing a declaration within this `struct`. + // * `S` registers a dependency on `.{ .src_hash = (declaration of S) }` + // * Any change to the `struct` body -- including changing a declaration -- invalidates this + // * `S` is re-analyzed, but notes: + // * there is an existing struct instance (at this `TrackedInst` with these captures) + // * the struct's resolution is up-to-date (because nothing about the fields changed) + // * so, it uses the same `struct` + // * but this doesn't stop it from updating the namespace! + // * we basically do `scanDecls`, updating the namespace as needed + // * so everyone lived happily ever after + + const zcu = pt.zcu; + const gpa = zcu.gpa; + const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const nav = ip.getNav(nav_id); + + log.debug("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); + + // Determine whether or not this `Nav`'s value is outdated. This also includes checking if the + // status is `.unresolved`, which indicates that the value is outdated because it has *never* + // been analyzed so far. + // + // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to + // ensure that the unit is definitely up-to-date when this function returns. This mechanism could + // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by + // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`. + + const was_outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + + const prev_failed = zcu.failed_analysis.contains(anal_unit) or + zcu.transitive_failed_analysis.contains(anal_unit); + + if (was_outdated) { + dev.check(.incremental); + _ = zcu.outdated_ready.swapRemove(anal_unit); + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); } - // If it wasn't failed and wasn't marked outdated, then either... - // * it is a type and is up-to-date, or - // * it is a `comptime` decl and is up-to-date, or - // * it is another decl and is EITHER up-to-date OR never-referenced (so unresolved) - // We just need to check for that last case. - switch (cau.owner.unwrap()) { - .type, .none => return, - .nav => |nav| if (ip.getNav(nav).status == .resolved) return, - } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + } else { + // We can trust the current information about this unit. + if (prev_failed) return error.AnalysisFail; + if (nav.status == .resolved) return; } - const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result| - // This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually - // change, we need to invalidate the dependencies anyway. - .{ .{ - .invalidate_decl_val = result.invalidate_decl_val or prev_failed, - .invalidate_decl_ref = result.invalidate_decl_ref or prev_failed, - }, false } - else |err| switch (err) { + const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0); + defer unit_prog_node.end(); + + const sema_result: SemaNavResult, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: { + break :res .{ + .{ + // If the unit has gone from failed to success, we still need to invalidate the dependencies. + .invalidate_nav_val = result.invalidate_nav_val or prev_failed, + .invalidate_nav_ref = result.invalidate_nav_ref or prev_failed, + }, + false, + }; + } else |err| switch (err) { error.AnalysisFail => res: { if (!zcu.failed_analysis.contains(anal_unit)) { - // If this `Cau` caused the error, it would have an entry in `failed_analysis`. + // If this unit caused the error, it would have an entry in `failed_analysis`. // Since it does not, this must be a transitive failure. try zcu.transitive_failed_analysis.put(gpa, anal_unit, {}); log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)}); } - // We consider this `Cau` to be outdated if: - // * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure - // they hit a transitive error here, rather than reporting a different error later (which - // may now be invalid). - // * The `Cau` is a type; in this case, the declaration site may require re-analysis to - // construct a valid type. - const outdated = !prev_failed or cau.owner.unwrap() == .type; break :res .{ .{ - .invalidate_decl_val = outdated, - .invalidate_decl_ref = outdated, + .invalidate_nav_val = !prev_failed, + .invalidate_nav_ref = !prev_failed, }, true }; }, - error.OutOfMemory => res: { - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - try zcu.retryable_failures.ensureUnusedCapacity(gpa, 1); - const msg = try Zcu.ErrorMsg.create( - gpa, - .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) }, - "unable to analyze: OutOfMemory", - .{}, - ); - zcu.retryable_failures.appendAssumeCapacity(anal_unit); - zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, msg); - break :res .{ .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }, true }; + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; }, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, }; - if (cau_outdated) { - // TODO: we do not yet have separate dependencies for decl values vs types. - const invalidate = sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref; - const dependee: InternPool.Dependee = switch (cau.owner.unwrap()) { - .none => return, // there are no dependencies on a `comptime` decl! - .nav => |nav_index| .{ .nav_val = nav_index }, - .type => |ty| .{ .interned = ty }, - }; - + if (was_outdated) { + // TODO: we do not yet have separate dependencies for Nav values vs types. + const invalidate = sema_result.invalidate_nav_val or sema_result.invalidate_nav_ref; + const dependee: InternPool.Dependee = .{ .nav_val = nav_id }; if (invalidate) { // This dependency was marked as PO, meaning dependees were waiting // on its analysis result, and it has turned out to be outdated. @@ -668,67 +821,320 @@ pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu } } - if (analysis_fail) return error.AnalysisFail; + if (new_failed) return error.AnalysisFail; } -fn ensureCauAnalyzedInner( - pt: Zcu.PerThread, - cau_index: InternPool.Cau.Index, - cau_outdated: bool, -) Zcu.SemaError!SemaCauResult { +const SemaNavResult = packed struct { + /// Whether the value of a `decl_val` of the corresponding Nav changed. + invalidate_nav_val: bool, + /// Whether the type of a `decl_ref` of the corresponding Nav changed. + invalidate_nav_ref: bool, +}; + +fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!SemaNavResult { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; - const cau = ip.getCau(cau_index); - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id }); + const old_nav = ip.getNav(nav_id); - const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + log.debug("analyzeNavVal {}", .{zcu.fmtAnalUnit(anal_unit)}); - // TODO: document this elsewhere mlugg! - // For my own benefit, here's how a namespace update for a normal (non-file-root) type works: - // `const S = struct { ... };` - // We are adding or removing a declaration within this `struct`. - // * `S` registers a dependency on `.{ .src_hash = (declaration of S) }` - // * Any change to the `struct` body -- including changing a declaration -- invalidates this - // * `S` is re-analyzed, but notes: - // * there is an existing struct instance (at this `TrackedInst` with these captures) - // * the struct's `Cau` is up-to-date (because nothing about the fields changed) - // * so, it uses the same `struct` - // * but this doesn't stop it from updating the namespace! - // * we basically do `scanDecls`, updating the namespace as needed - // * so everyone lived happily ever after + const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail; + const file = zcu.fileByIndex(inst_resolved.file); + // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is + // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends + // in `ensureComptimeUnitUpToDate`. + if (file.status != .success_zir) return error.AnalysisFail; + const zir = file.zir; - if (zcu.fileByIndex(inst_info.file).status != .success_zir) { - return error.AnalysisFail; + // We are about to re-analyze this unit; drop its depenndencies. + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + + try zcu.analysis_in_progress.put(gpa, anal_unit, {}); + errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); + + var analysis_arena: std.heap.ArenaAllocator = .init(gpa); + defer analysis_arena.deinit(); + + var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa); + defer comptime_err_ret_trace.deinit(); + + var sema: Sema = .{ + .pt = pt, + .gpa = gpa, + .arena = analysis_arena.allocator(), + .code = zir, + .owner = anal_unit, + .func_index = .none, + .func_is_naked = false, + .fn_ret_ty = .void, + .fn_ret_ty_ies = null, + .comptime_err_ret_trace = &comptime_err_ret_trace, + }; + defer sema.deinit(); + + // The comptime unit declares on the source of the corresponding declaration. + try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index }); + + var block: Sema.Block = .{ + .parent = null, + .sema = &sema, + .namespace = old_nav.analysis.?.namespace, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + .src_base_inst = old_nav.analysis.?.zir_index, + .type_name_ctx = old_nav.fqn, + }; + defer block.instructions.deinit(gpa); + + const zir_decl = zir.getDeclaration(inst_resolved.inst); + + assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace")); + + const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); + const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); + const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); + const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); + const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); + + // First, we must resolve the declaration's type. To do this, we analyze the type body if available, + // or otherwise, we analyze the value body, populating `early_val` in the process. + + const nav_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { + // We evaluate only the type now; no need for the value yet. + const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst); + const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); + break :ty .{ .fromInterned(type_ref.toInterned().?), null }; + } else ty: { + // We don't have a type body, so we need to evaluate the value immediately. + const value_body = zir_decl.value_body.?; + const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); + break :ty .{ val.typeOf(zcu), val }; + }; + + switch (zir_decl.kind) { + .@"comptime" => unreachable, // this is not a Nav + .unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"), + .@"usingnamespace" => {}, + .@"const" => {}, + .@"var" => try sema.validateVarType( + &block, + if (zir_decl.type_body != null) ty_src else init_src, + nav_ty, + zir_decl.linkage == .@"extern", + ), } - // `cau_outdated` can be true in the initial update for `comptime` declarations, - // so this isn't a `dev.check`. - if (cau_outdated and dev.env.supports(.incremental)) { - // The exports this `Cau` performs will be re-discovered, so we remove them here - // prior to re-analysis. - zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(zcu.gpa); + // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine + // the full pointer type of this declaration. + + const alignment: InternPool.Alignment = a: { + const align_body = zir_decl.align_body orelse break :a .none; + const align_ref = try sema.resolveInlineBody(&block, align_body, inst_resolved.inst); + break :a try sema.analyzeAsAlign(&block, align_src, align_ref); + }; + + const @"linksection": InternPool.OptionalNullTerminatedString = ls: { + const linksection_body = zir_decl.linksection_body orelse break :ls .none; + const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_resolved.inst); + const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{ + .needed_comptime_reason = "linksection must be comptime-known", + }); + if (std.mem.indexOfScalar(u8, bytes, 0) != null) { + return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{}); + } else if (bytes.len == 0) { + return sema.fail(&block, section_src, "linksection cannot be empty", .{}); } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls); + }; + + const @"addrspace": std.builtin.AddressSpace = as: { + const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) { + .@"var" => .variable, + else => switch (nav_ty.zigTypeTag(zcu)) { + .@"fn" => .function, + else => .constant, + }, + }; + const target = zcu.getTarget(); + const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) { + .function => target_util.defaultAddressSpace(target, .function), + .variable => target_util.defaultAddressSpace(target, .global_mutable), + .constant => target_util.defaultAddressSpace(target, .global_constant), + else => unreachable, + }; + const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_resolved.inst); + break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); + }; + + // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations + // don't have an associated value body. + + const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { + // Put the resolved type into `inst_map` to be used as the result type of the init. + try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_resolved.inst}); + sema.inst_map.putAssumeCapacity(inst_resolved.inst, Air.internedToRef(nav_ty.toIntern())); + const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst); + assert(sema.inst_map.remove(inst_resolved.inst)); + + const result_ref = try sema.coerce(&block, nav_ty, uncoerced_result_ref, init_src); + break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); + } else null; + + const nav_val: Value = switch (zir_decl.linkage) { + .normal, .@"export" => switch (zir_decl.kind) { + .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ + .ty = nav_ty.toIntern(), + .init = final_val.?.toIntern(), + .owner_nav = nav_id, + .is_threadlocal = zir_decl.is_threadlocal, + .is_weak_linkage = false, + } })), + else => final_val.?, + }, + .@"extern" => val: { + assert(final_val == null); // extern decls do not have a value body + const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: { + break :l zir.nullTerminatedString(zir_decl.lib_name); + } else null; + if (lib_name) |l| { + const lib_name_src = block.src(.{ .node_offset_lib_name = 0 }); + try sema.handleExternLibName(&block, lib_name_src, l); + } + break :val .fromInterned(try pt.getExtern(.{ + .name = old_nav.name, + .ty = nav_ty.toIntern(), + .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls), + .is_const = zir_decl.kind == .@"const", + .is_threadlocal = zir_decl.is_threadlocal, + .is_weak_linkage = false, + .is_dll_import = false, + .alignment = alignment, + .@"addrspace" = @"addrspace", + .zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction + .owner_nav = undefined, // ignored by `getExtern` + })); + }, + }; + + switch (nav_val.toIntern()) { + .generic_poison => unreachable, // assertion failure + .unreachable_value => unreachable, // assertion failure + else => {}, } - const decl_prog_node = zcu.sema_prog_node.start(switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn.toSlice(ip), - .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip), - .none => "comptime", - }, 0); - defer decl_prog_node.end(); + // This resolves the type of the resolved value, not that value itself. If `nav_val` is a struct type, + // this resolves the type `type` (which needs no resolution), not the struct itself. + try nav_ty.resolveLayout(pt); - return pt.semaCau(cau_index) catch |err| switch (err) { - error.GenericPoison, error.ComptimeBreak, error.ComptimeReturn => unreachable, - error.AnalysisFail, error.OutOfMemory => |e| return e, + // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`. + if (zir_decl.kind == .@"usingnamespace") { + if (nav_ty.toIntern() != .type_type) { + return sema.fail(&block, ty_src, "expected type, found {}", .{nav_ty.fmt(pt)}); + } + if (nav_val.toType().getNamespace(zcu) == .none) { + return sema.fail(&block, ty_src, "type {} has no namespace", .{nav_val.toType().fmt(pt)}); + } + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = .none, + .@"linksection" = .none, + .@"addrspace" = .generic, + }); + // TODO: usingnamespace cannot participate in incremental compilation + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + return .{ + .invalidate_nav_val = true, + .invalidate_nav_ref = true, + }; + } + + const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) { + .func => |f| .{ true, f.owner_nav == nav_id }, // note that this lets function aliases reach codegen + .variable => |v| .{ v.owner_nav == nav_id, false }, + .@"extern" => |e| .{ + false, + Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" and zir_decl.linkage == .@"extern", + }, + else => .{ true, false }, }; + + if (is_owned_fn) { + // linksection etc are legal, except some targets do not support function alignment. + if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) { + return sema.fail(&block, align_src, "target does not support function alignment", .{}); + } + } else if (try nav_ty.comptimeOnlySema(pt)) { + // alignment, linksection, addrspace annotations are not allowed for comptime-only types. + const reason: []const u8 = switch (ip.indexToKey(nav_val.toIntern())) { + .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* + else => "comptime-only type", + }; + if (zir_decl.align_body != null) { + return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); + } + if (zir_decl.linksection_body != null) { + return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); + } + if (zir_decl.addrspace_body != null) { + return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); + } + } + + ip.resolveNavValue(nav_id, .{ + .val = nav_val.toIntern(), + .alignment = alignment, + .@"linksection" = @"linksection", + .@"addrspace" = @"addrspace", + }); + + // Mark the unit as completed before evaluating the export! + assert(zcu.analysis_in_progress.swapRemove(anal_unit)); + + if (zir_decl.linkage == .@"export") { + const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) }); + const name_slice = zir.nullTerminatedString(zir_decl.name); + const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls); + try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_id); + } + + try sema.flushExports(); + + queue_codegen: { + if (!queue_linker_work) break :queue_codegen; + + if (!try nav_ty.hasRuntimeBitsSema(pt)) { + if (zcu.comp.config.use_llvm) break :queue_codegen; + if (file.mod.strip) break :queue_codegen; + } + + // This job depends on any resolve_type_fully jobs queued up before it. + try zcu.comp.queueJob(.{ .codegen_nav = nav_id }); + } + + switch (old_nav.status) { + .unresolved => return .{ + .invalidate_nav_val = true, + .invalidate_nav_ref = true, + }, + .resolved => |old| { + const new = ip.getNav(nav_id).status.resolved; + return .{ + .invalidate_nav_val = new.val != old.val, + .invalidate_nav_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or + new.alignment != old.alignment or + new.@"linksection" != old.@"linksection" or + new.@"addrspace" != old.@"addrspace", + }; + }, + } } -pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { +pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void { dev.check(.sema); const tracy = trace(@src()); @@ -740,19 +1146,26 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter // We only care about the uncoerced function. const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); - const anal_unit = AnalUnit.wrap(.{ .func = func_index }); + const anal_unit: AnalUnit = .wrap(.{ .func = func_index }); - log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)}); + log.debug("ensureFuncBodyUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)}); const func = zcu.funcInfo(maybe_coerced_func_index); - const func_outdated = zcu.outdated.swapRemove(anal_unit) or + const was_outdated = zcu.outdated.swapRemove(anal_unit) or zcu.potentially_outdated.swapRemove(anal_unit); const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit); - if (func_outdated) { + if (was_outdated) { + dev.check(.incremental); _ = zcu.outdated_ready.swapRemove(anal_unit); + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); } else { // We can trust the current information about this function. if (prev_failed) { @@ -765,8 +1178,11 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter } } - const ies_outdated, const analysis_fail = if (pt.ensureFuncBodyAnalyzedInner(func_index, func_outdated)) |result| - .{ result.ies_outdated, false } + const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0); + defer func_prog_node.end(); + + const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index)) |result| + .{ prev_failed or result.ies_outdated, false } else |err| switch (err) { error.AnalysisFail => res: { if (!zcu.failed_analysis.contains(anal_unit)) { @@ -780,10 +1196,18 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter // a different error later (which may now be invalid). break :res .{ !prev_failed, true }; }, - error.OutOfMemory => return error.OutOfMemory, // TODO: graceful handling like `ensureCauAnalyzed` + error.OutOfMemory => { + // TODO: it's unclear how to gracefully handle this. + // To report the error cleanly, we need to add a message to `failed_analysis` and a + // corresponding entry to `retryable_failures`; but either of these things is quite + // likely to OOM at this point. + // If that happens, what do we do? Perhaps we could have a special field on `Zcu` + // for reporting OOM errors without allocating. + return error.OutOfMemory; + }, }; - if (func_outdated) { + if (was_outdated) { if (ies_outdated) { try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index }); } else { @@ -791,13 +1215,12 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter } } - if (analysis_fail) return error.AnalysisFail; + if (new_failed) return error.AnalysisFail; } -fn ensureFuncBodyAnalyzedInner( +fn analyzeFuncBody( pt: Zcu.PerThread, func_index: InternPool.Index, - func_outdated: bool, ) Zcu.SemaError!struct { ies_outdated: bool } { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -812,7 +1235,7 @@ fn ensureFuncBodyAnalyzedInner( if (func.generic_owner == .none) { // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?); + try pt.ensureNavValUpToDate(func.owner_nav); if (ip.getNav(func.owner_nav).status.resolved.val != func_index) { // This function is no longer referenced! There's no point in re-analyzing it. // Just mark a transitive failure and move on. @@ -821,7 +1244,7 @@ fn ensureFuncBodyAnalyzedInner( } else { const go_nav = zcu.funcInfo(func.generic_owner).owner_nav; // Among another things, this ensures that the function's `zir_body_inst` is correct. - try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?); + try pt.ensureNavValUpToDate(go_nav); if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) { // The generic owner is no longer referenced, so this function is also unreferenced. // There's no point in re-analyzing it. Just mark a transitive failure and move on. @@ -836,38 +1259,13 @@ fn ensureFuncBodyAnalyzedInner( else .none; - if (func_outdated) { - dev.check(.incremental); - zcu.deleteUnitExports(anal_unit); - zcu.deleteUnitReferences(anal_unit); - if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { - kv.value.destroy(gpa); - } - _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); - } - - if (!func_outdated) { - // We can trust the current information about this function. - if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { - return error.AnalysisFail; - } - switch (func.analysisUnordered(ip).state) { - .unreferenced => {}, // this is the first reference - .queued => {}, // we're waiting on first-time analysis - .analyzed => return .{ .ies_outdated = false }, // up-to-date - } - } - - log.debug("analyze and generate fn body {}; reason='{s}'", .{ - zcu.fmtAnalUnit(anal_unit), - if (func_outdated) "outdated" else "never analyzed", - }); + log.debug("analyze and generate fn body {}", .{zcu.fmtAnalUnit(anal_unit)}); - var air = try pt.analyzeFnBody(func_index); + var air = try pt.analyzeFnBodyInner(func_index); errdefer air.deinit(gpa); - const ies_outdated = func_outdated and - (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies); + const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or + func.resolvedErrorSetUnordered(ip) != old_resolved_ies; const comp = zcu.comp; @@ -1035,12 +1433,11 @@ fn createFileRootStruct( wip_ty.setName(ip, try file.internFullyQualifiedName(pt)); ip.namespacePtr(namespace_index).owner_type = wip_ty.index; - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, namespace_index, wip_ty.index); if (zcu.comp.incremental) { try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst }, ); } @@ -1054,7 +1451,7 @@ fn createFileRootStruct( try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index }); } zcu.setFileRootType(file_index, wip_ty.index); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); + return wip_ty.finish(ip, namespace_index); } /// Re-scan the namespace of a file's root struct type on an incremental update. @@ -1147,369 +1544,6 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void { } } -const SemaCauResult = packed struct { - /// Whether the value of a `decl_val` of the corresponding Nav changed. - invalidate_decl_val: bool, - /// Whether the type of a `decl_ref` of the corresponding Nav changed. - invalidate_decl_ref: bool, -}; - -/// Performs semantic analysis on the given `Cau`, storing results to its owner `Nav` if needed. -/// If analysis fails, returns `error.AnalysisFail`, storing an error in `zcu.failed_analysis` unless -/// the error is transitive. -/// On success, returns information about whether the `Nav` value changed. -fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - - const anal_unit = AnalUnit.wrap(.{ .cau = cau_index }); - - const cau = ip.getCau(cau_index); - const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail; - const file = zcu.fileByIndex(inst_info.file); - const zir = file.zir; - - if (file.status != .success_zir) { - return error.AnalysisFail; - } - - // We are about to re-analyze this `Cau`; drop its depenndencies. - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); - - switch (cau.owner.unwrap()) { - .none => {}, // `comptime` decl -- we will re-analyze its body. - .nav => {}, // Other decl -- we will re-analyze its value. - .type => |ty| { - // This is an incremental update, and this type is being re-analyzed because it is outdated. - // Create a new type in its place, and mark the old one as outdated so that use sites will - // be re-analyzed and discover an up-to-date type. - const new_ty = try pt.ensureTypeUpToDate(ty, true); - assert(new_ty != ty); - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - }, - } - - const is_usingnamespace = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).is_usingnamespace, - .none, .type => false, - }; - - log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)}); - - try zcu.analysis_in_progress.put(gpa, anal_unit, {}); - errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit); - - var analysis_arena = std.heap.ArenaAllocator.init(gpa); - defer analysis_arena.deinit(); - - var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); - - var sema: Sema = .{ - .pt = pt, - .gpa = gpa, - .arena = analysis_arena.allocator(), - .code = zir, - .owner = anal_unit, - .func_index = .none, - .func_is_naked = false, - .fn_ret_ty = Type.void, - .fn_ret_ty_ies = null, - .comptime_err_ret_trace = &comptime_err_ret_trace, - }; - defer sema.deinit(); - - // Every `Cau` has a dependency on the source of its own ZIR instruction. - try sema.declareDependency(.{ .src_hash = cau.zir_index }); - - var block: Sema.Block = .{ - .parent = null, - .sema = &sema, - .namespace = cau.namespace, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - .src_base_inst = cau.zir_index, - .type_name_ctx = switch (cau.owner.unwrap()) { - .nav => |nav| ip.getNav(nav).fqn, - .type => |ty| Type.fromInterned(ty).containerTypeName(ip), - .none => try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{ - Type.fromInterned(zcu.namespacePtr(cau.namespace).owner_type).containerTypeName(ip).fmt(ip), - }, .no_embedded_nulls), - }, - }; - defer block.instructions.deinit(gpa); - - const zir_decl = zir.getDeclaration(inst_info.inst); - - // We have to fetch this state before resolving the body because of the `nav_already_populated` - // case below. We might change the language in future so that align/linksection/etc for functions - // work in a way more in line with other declarations, in which case that logic will go away. - const old_nav_info = switch (cau.owner.unwrap()) { - .none, .type => undefined, // we'll never use `old_nav_info` - .nav => |nav| ip.getNav(nav), - }; - - const align_src = block.src(.{ .node_offset_var_decl_align = 0 }); - const section_src = block.src(.{ .node_offset_var_decl_section = 0 }); - const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 }); - const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 }); - const init_src = block.src(.{ .node_offset_var_decl_init = 0 }); - - // First, we must resolve the declaration's type. To do this, we analyze the type body if available, - // or otherwise, we analyze the value body, populating `early_val` in the process. - - const decl_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: { - // We evaluate only the type now; no need for the value yet. - const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_info.inst); - const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src); - break :ty .{ .fromInterned(type_ref.toInterned().?), null }; - } else ty: { - // We don't have a type body, so we need to evaluate the value immediately. - const value_body = zir_decl.value_body.?; - const result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); - const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref); - break :ty .{ val.typeOf(zcu), val }; - }; - - switch (zir_decl.kind) { - .unnamed_test, .@"test", .decltest => assert(decl_ty.zigTypeTag(zcu) == .@"fn"), - .@"comptime" => assert(decl_ty.toIntern() == .void_type), - .@"usingnamespace" => {}, - .@"const" => {}, - .@"var" => try sema.validateVarType( - &block, - if (zir_decl.type_body != null) ty_src else init_src, - decl_ty, - zir_decl.linkage == .@"extern", - ), - } - - // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine - // the full pointer type of this declaration. - - const alignment: InternPool.Alignment = a: { - const align_body = zir_decl.align_body orelse break :a .none; - const align_ref = try sema.resolveInlineBody(&block, align_body, inst_info.inst); - break :a try sema.analyzeAsAlign(&block, align_src, align_ref); - }; - - const @"linksection": InternPool.OptionalNullTerminatedString = ls: { - const linksection_body = zir_decl.linksection_body orelse break :ls .none; - const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_info.inst); - const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{ - .needed_comptime_reason = "linksection must be comptime-known", - }); - if (std.mem.indexOfScalar(u8, bytes, 0) != null) { - return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{}); - } else if (bytes.len == 0) { - return sema.fail(&block, section_src, "linksection cannot be empty", .{}); - } - break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls); - }; - - const @"addrspace": std.builtin.AddressSpace = as: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) { - .@"var" => .variable, - else => switch (decl_ty.zigTypeTag(zcu)) { - .@"fn" => .function, - else => .constant, - }, - }; - const target = zcu.getTarget(); - const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) { - .function => target_util.defaultAddressSpace(target, .function), - .variable => target_util.defaultAddressSpace(target, .global_mutable), - .constant => target_util.defaultAddressSpace(target, .global_constant), - else => unreachable, - }; - const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst); - break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx); - }; - - // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations - // don't have an associated value body. - - const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: { - // Put the resolved type into `inst_map` to be used as the result type of the init. - try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_info.inst}); - sema.inst_map.putAssumeCapacity(inst_info.inst, Air.internedToRef(decl_ty.toIntern())); - const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst); - assert(sema.inst_map.remove(inst_info.inst)); - - const result_ref = try sema.coerce(&block, decl_ty, uncoerced_result_ref, init_src); - break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref); - } else null; - - // TODO: missing validation? - - const decl_val: Value = switch (zir_decl.linkage) { - .normal, .@"export" => switch (zir_decl.kind) { - .@"var" => .fromInterned(try pt.intern(.{ .variable = .{ - .ty = decl_ty.toIntern(), - .init = final_val.?.toIntern(), - .owner_nav = cau.owner.unwrap().nav, - .is_threadlocal = zir_decl.is_threadlocal, - .is_weak_linkage = false, - } })), - else => final_val.?, - }, - .@"extern" => val: { - assert(final_val == null); // extern decls do not have a value body - const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: { - break :l zir.nullTerminatedString(zir_decl.lib_name); - } else null; - if (lib_name) |l| { - const lib_name_src = block.src(.{ .node_offset_lib_name = 0 }); - try sema.handleExternLibName(&block, lib_name_src, l); - } - break :val .fromInterned(try pt.getExtern(.{ - .name = old_nav_info.name, - .ty = decl_ty.toIntern(), - .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls), - .is_const = zir_decl.kind == .@"const", - .is_threadlocal = zir_decl.is_threadlocal, - .is_weak_linkage = false, - .is_dll_import = false, - .alignment = alignment, - .@"addrspace" = @"addrspace", - .zir_index = cau.zir_index, // `declaration` instruction - .owner_nav = undefined, // ignored by `getExtern` - })); - }, - }; - - const nav_index = switch (cau.owner.unwrap()) { - .none => { - // This is a `comptime` decl, so we are done -- the side effects are all we care about. - // Just make sure to `flushExports`. - try sema.flushExports(); - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_decl_val = false, - .invalidate_decl_ref = false, - }; - }, - .nav => |nav| nav, // We will resolve this `Nav` below. - .type => unreachable, // Handled at top of function. - }; - - switch (decl_val.toIntern()) { - .generic_poison => unreachable, // assertion failure - .unreachable_value => unreachable, // assertion failure - else => {}, - } - - // This resolves the type of the resolved value, not that value itself. If `decl_val` is a struct type, - // this resolves the type `type` (which needs no resolution), not the struct itself. - try decl_ty.resolveLayout(pt); - - // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`. - if (is_usingnamespace) { - if (decl_ty.toIntern() != .type_type) { - return sema.fail(&block, ty_src, "expected type, found {}", .{decl_ty.fmt(pt)}); - } - if (decl_val.toType().getNamespace(zcu) == .none) { - return sema.fail(&block, ty_src, "type {} has no namespace", .{decl_val.toType().fmt(pt)}); - } - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = .none, - .@"linksection" = .none, - .@"addrspace" = .generic, - }); - // TODO: usingnamespace cannot participate in incremental compilation - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - } - - const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(decl_val.toIntern())) { - .func => |f| .{ true, f.owner_nav == nav_index }, // note that this lets function aliases reach codegen - .variable => |v| .{ v.owner_nav == nav_index, false }, - .@"extern" => |e| .{ false, Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" }, - else => .{ true, false }, - }; - - // Keep in sync with logic in `Sema.zirVarExtended`. - - if (is_owned_fn) { - // linksection etc are legal, except some targets do not support function alignment. - if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) { - return sema.fail(&block, align_src, "target does not support function alignment", .{}); - } - } else if (try decl_ty.comptimeOnlySema(pt)) { - // alignment, linksection, addrspace annotations are not allowed for comptime-only types. - const reason: []const u8 = switch (ip.indexToKey(decl_val.toIntern())) { - .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations* - else => "comptime-only type", - }; - if (zir_decl.align_body != null) { - return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason}); - } - if (zir_decl.linksection_body != null) { - return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason}); - } - if (zir_decl.addrspace_body != null) { - return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason}); - } - } - - ip.resolveNavValue(nav_index, .{ - .val = decl_val.toIntern(), - .alignment = alignment, - .@"linksection" = @"linksection", - .@"addrspace" = @"addrspace", - }); - - // Mark the `Cau` as completed before evaluating the export! - assert(zcu.analysis_in_progress.swapRemove(anal_unit)); - - if (zir_decl.linkage == .@"export") { - const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) }); - const name_slice = zir.nullTerminatedString(zir_decl.name); - const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls); - try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_index); - } - - try sema.flushExports(); - - queue_codegen: { - if (!queue_linker_work) break :queue_codegen; - - if (!try decl_ty.hasRuntimeBitsSema(pt)) { - if (zcu.comp.config.use_llvm) break :queue_codegen; - if (file.mod.strip) break :queue_codegen; - } - - // This job depends on any resolve_type_fully jobs queued up before it. - try zcu.comp.queueJob(.{ .codegen_nav = nav_index }); - } - - switch (old_nav_info.status) { - .unresolved => return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }, - .resolved => |old| { - const new = ip.getNav(nav_index).status.resolved; - return .{ - .invalidate_decl_val = new.val != old.val, - .invalidate_decl_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or - new.alignment != old.alignment or - new.@"linksection" != old.@"linksection" or - new.@"addrspace" != old.@"addrspace", - }; - }, - } -} - pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -1880,45 +1914,42 @@ pub fn scanNamespace( // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather // than their name. We'll build an efficient mapping now, then discard the current `decls`. - // We map to the `Cau`, since not every declaration has a `Nav`. - var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index) = .empty; + // We map to the `AnalUnit`, since not every declaration has a `Nav`. + var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit) = .empty; defer existing_by_inst.deinit(gpa); try existing_by_inst.ensureTotalCapacity(gpa, @intCast( namespace.pub_decls.count() + namespace.priv_decls.count() + namespace.pub_usingnamespace.items.len + namespace.priv_usingnamespace.items.len + - namespace.other_decls.items.len, + namespace.comptime_decls.items.len + + namespace.test_decls.items.len, )); for (namespace.pub_decls.keys()) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.priv_decls.keys()) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.pub_usingnamespace.items) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); } for (namespace.priv_usingnamespace.items) |nav| { - const cau_index = ip.getNav(nav).analysis_owner.unwrap().?; - const zir_index = ip.getCau(cau_index).zir_index; - existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index); - } - for (namespace.other_decls.items) |cau_index| { - const cau = ip.getCau(cau_index); - existing_by_inst.putAssumeCapacityNoClobber(cau.zir_index, cau_index); - // If this is a test, it'll be re-added to `test_functions` later on - // if still alive. Remove it for now. - switch (cau.owner.unwrap()) { - .none, .type => {}, - .nav => |nav| _ = zcu.test_functions.swapRemove(nav), - } + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); + } + for (namespace.comptime_decls.items) |cu| { + const zir_index = ip.getComptimeUnit(cu).zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .@"comptime" = cu })); + } + for (namespace.test_decls.items) |nav| { + const zir_index = ip.getNav(nav).analysis.?.zir_index; + existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav })); + // This test will be re-added to `test_functions` later on if it's still alive. Remove it for now. + _ = zcu.test_functions.swapRemove(nav); } var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty; @@ -1928,7 +1959,8 @@ pub fn scanNamespace( namespace.priv_decls.clearRetainingCapacity(); namespace.pub_usingnamespace.clearRetainingCapacity(); namespace.priv_usingnamespace.clearRetainingCapacity(); - namespace.other_decls.clearRetainingCapacity(); + namespace.comptime_decls.clearRetainingCapacity(); + namespace.test_decls.clearRetainingCapacity(); var scan_decl_iter: ScanDeclIter = .{ .pt = pt, @@ -1950,7 +1982,7 @@ const ScanDeclIter = struct { pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index, seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void), - existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index), + existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit), /// Decl scanning is run in two passes, so that we can detect when a generated /// name would clash with an explicit name and use a different one. pass: enum { named, unnamed }, @@ -1988,48 +2020,30 @@ const ScanDeclIter = struct { const decl = zir.getDeclaration(decl_inst); - const Kind = enum { @"comptime", @"usingnamespace", @"test", named }; - - const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (decl.kind) { - .@"comptime" => info: { + const maybe_name: InternPool.OptionalNullTerminatedString = switch (decl.kind) { + .@"comptime" => name: { if (iter.pass != .unnamed) return; - break :info .{ - .none, - .@"comptime", - false, - }; + break :name .none; }, - .@"usingnamespace" => info: { + .@"usingnamespace" => name: { if (iter.pass != .unnamed) return; const i = iter.usingnamespace_index; iter.usingnamespace_index += 1; - break :info .{ - (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(), - .@"usingnamespace", - false, - }; + break :name (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(); }, - .unnamed_test => info: { + .unnamed_test => name: { if (iter.pass != .unnamed) return; const i = iter.unnamed_test_index; iter.unnamed_test_index += 1; - break :info .{ - (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(), - .@"test", - false, - }; + break :name (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(); }, - .@"test", .decltest => |kind| info: { + .@"test", .decltest => |kind| name: { // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary. if (iter.pass != .unnamed) return; const prefix = @tagName(kind); - break :info .{ - (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(), - .@"test", - true, - }; + break :name (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(); }, - .@"const", .@"var" => info: { + .@"const", .@"var" => name: { if (iter.pass != .named) return; const name = try ip.getOrPutString( gpa, @@ -2038,11 +2052,7 @@ const ScanDeclIter = struct { .no_embedded_nulls, ); try iter.seen_decls.putNoClobber(gpa, name, {}); - break :info .{ - name.toOptional(), - .named, - false, - }; + break :name name.toOptional(); }, }; @@ -2051,46 +2061,44 @@ const ScanDeclIter = struct { .inst = decl_inst, }); - const existing_cau = iter.existing_by_inst.get(tracked_inst); + const existing_unit = iter.existing_by_inst.get(tracked_inst); - const cau, const want_analysis = switch (kind) { - .@"comptime" => cau: { - const cau = existing_cau orelse try ip.createComptimeCau(gpa, pt.tid, tracked_inst, namespace_index); + const unit, const want_analysis = switch (decl.kind) { + .@"comptime" => unit: { + const cu = if (existing_unit) |eu| + eu.unwrap().@"comptime" + else + try ip.createComptimeUnit(gpa, pt.tid, tracked_inst, namespace_index); - try namespace.other_decls.append(gpa, cau); + const unit: AnalUnit = .wrap(.{ .@"comptime" = cu }); - if (existing_cau == null) { - // For a `comptime` declaration, whether to analyze is based solely on whether the - // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already. - const unit = AnalUnit.wrap(.{ .cau = cau }); - if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value); - if (kv.value == 0) { // no PO deps - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); - } - } else if (!zcu.outdated.contains(unit)) { - try zcu.outdated.ensureUnusedCapacity(gpa, 1); - try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); - zcu.outdated.putAssumeCapacityNoClobber(unit, 0); - zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); - } + try namespace.comptime_decls.append(gpa, cu); + + if (existing_unit == null) { + // For a `comptime` declaration, whether to analyze is based solely on whether the unit + // is outdated. So, add this fresh one to `outdated` and `outdated_ready`. + try zcu.outdated.ensureUnusedCapacity(gpa, 1); + try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1); + zcu.outdated.putAssumeCapacityNoClobber(unit, 0); + zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {}); } - break :cau .{ cau, true }; + break :unit .{ unit, true }; }, - else => cau: { + else => unit: { const name = maybe_name.unwrap().?; const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name); - const cau, const nav = if (existing_cau) |cau_index| cau_nav: { - const nav_index = ip.getCau(cau_index).owner.unwrap().nav; - const nav = ip.getNav(nav_index); - assert(nav.name == name); - assert(nav.fqn == fqn); - break :cau_nav .{ cau_index, nav_index }; - } else try ip.createPairedCauNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, kind == .@"usingnamespace"); - const want_analysis = switch (kind) { + const nav = if (existing_unit) |eu| + eu.unwrap().nav_val + else + try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace"); + + const unit: AnalUnit = .wrap(.{ .nav_val = nav }); + + assert(ip.getNav(nav).name == name); + assert(ip.getNav(nav).fqn == fqn); + + const want_analysis = switch (decl.kind) { .@"comptime" => unreachable, .@"usingnamespace" => a: { if (comp.incremental) { @@ -2103,8 +2111,9 @@ const ScanDeclIter = struct { } break :a true; }, - .@"test" => a: { - try namespace.other_decls.append(gpa, cau); + .unnamed_test, .@"test", .decltest => a: { + const is_named = decl.kind != .unnamed_test; + try namespace.test_decls.append(gpa, nav); // TODO: incremental compilation! // * remove from `test_functions` if no longer matching filter // * add to `test_functions` if newly passing filter @@ -2112,7 +2121,7 @@ const ScanDeclIter = struct { // Perhaps we should add all test indiscriminately and filter at the end of the update. if (!comp.config.is_test) break :a false; if (file.mod != zcu.main_mod) break :a false; - if (is_named_test and comp.test_filters.len > 0) { + if (is_named and comp.test_filters.len > 0) { const fqn_slice = fqn.toSlice(ip); for (comp.test_filters) |test_filter| { if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break; @@ -2121,7 +2130,7 @@ const ScanDeclIter = struct { try zcu.test_functions.put(gpa, nav, {}); break :a true; }, - .named => a: { + .@"const", .@"var" => a: { if (decl.is_pub) { try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu }); } else { @@ -2130,23 +2139,23 @@ const ScanDeclIter = struct { break :a false; }, }; - break :cau .{ cau, want_analysis }; + break :unit .{ unit, want_analysis }; }, }; - if (existing_cau == null and (want_analysis or decl.linkage == .@"export")) { + if (existing_unit == null and (want_analysis or decl.linkage == .@"export")) { log.debug( - "scanDecl queue analyze_cau file='{s}' cau_index={d}", - .{ namespace.fileScope(zcu).sub_file_path, cau }, + "scanDecl queue analyze_comptime_unit file='{s}' unit={}", + .{ namespace.fileScope(zcu).sub_file_path, zcu.fmtAnalUnit(unit) }, ); - try comp.queueJob(.{ .analyze_cau = cau }); + try comp.queueJob(.{ .analyze_comptime_unit = unit }); } // TODO: we used to do line number updates here, but this is an inappropriate place for this logic to live. } }; -fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { +fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air { const tracy = trace(@src()); defer tracy.end(); @@ -2168,21 +2177,14 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! func.setResolvedErrorSet(ip, .none); } - // This is the `Cau` corresponding to the `declaration` instruction which the function or its generic owner originates from. - const decl_cau = ip.getCau(cau: { - const orig_nav = if (func.generic_owner == .none) - func.owner_nav - else - zcu.funcInfo(func.generic_owner).owner_nav; - - break :cau ip.getNav(orig_nav).analysis_owner.unwrap().?; - }); + // This is the `Nau` corresponding to the `declaration` instruction which the function or its generic owner originates from. + const decl_nav = ip.getNav(if (func.generic_owner == .none) + func.owner_nav + else + zcu.funcInfo(func.generic_owner).owner_nav); const func_nav = ip.getNav(func.owner_nav); - const decl_prog_node = zcu.sema_prog_node.start(func_nav.fqn.toSlice(ip), 0); - defer decl_prog_node.end(); - zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); var analysis_arena = std.heap.ArenaAllocator.init(gpa); @@ -2216,7 +2218,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! // Every runtime function has a dependency on the source of the Decl it originates from. // It also depends on the value of its owner Decl. - try sema.declareDependency(.{ .src_hash = decl_cau.zir_index }); + try sema.declareDependency(.{ .src_hash = decl_nav.analysis.?.zir_index }); try sema.declareDependency(.{ .nav_val = func.owner_nav }); if (func.analysisUnordered(ip).inferred_error_set) { @@ -2236,11 +2238,11 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError! var inner_block: Sema.Block = .{ .parent = null, .sema = &sema, - .namespace = decl_cau.namespace, + .namespace = decl_nav.analysis.?.namespace, .instructions = .{}, .inlining = null, .is_comptime = false, - .src_base_inst = decl_cau.zir_index, + .src_base_inst = decl_nav.analysis.?.zir_index, .type_name_ctx = func_nav.fqn, }; defer inner_block.instructions.deinit(gpa); @@ -2542,10 +2544,10 @@ fn processExportsInner( .nav => |nav_index| if (failed: { const nav = ip.getNav(nav_index); if (zcu.failed_codegen.contains(nav_index)) break :failed true; - if (nav.analysis_owner.unwrap()) |cau| { - const cau_unit = AnalUnit.wrap(.{ .cau = cau }); - if (zcu.failed_analysis.contains(cau_unit)) break :failed true; - if (zcu.transitive_failed_analysis.contains(cau_unit)) break :failed true; + if (nav.analysis != null) { + const unit: AnalUnit = .wrap(.{ .nav_val = nav_index }); + if (zcu.failed_analysis.contains(unit)) break :failed true; + if (zcu.transitive_failed_analysis.contains(unit)) break :failed true; } const val = switch (nav.status) { .unresolved => break :failed true, @@ -2593,15 +2595,14 @@ pub fn populateTestFunctions( Zcu.Namespace.NameAdapter{ .zcu = zcu }, ).?; { - // We have to call `ensureCauAnalyzed` here in case `builtin.test_functions` + // We have to call `ensureNavValUpToDate` here in case `builtin.test_functions` // was not referenced by start code. zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); defer { zcu.sema_prog_node.end(); zcu.sema_prog_node = std.Progress.Node.none; } - const cau_index = ip.getNav(nav_index).analysis_owner.unwrap().?; - pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) { + pt.ensureNavValUpToDate(nav_index) catch |err| switch (err) { error.AnalysisFail => return, error.OutOfMemory => return error.OutOfMemory, }; @@ -2622,8 +2623,7 @@ pub fn populateTestFunctions( { // The test declaration might have failed; if that's the case, just return, as we'll // be emitting a compile error anyway. - const cau = test_nav.analysis_owner.unwrap().?; - const anal_unit: AnalUnit = .wrap(.{ .cau = cau }); + const anal_unit: AnalUnit = .wrap(.{ .nav_val = test_nav_index }); if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) { @@ -2748,8 +2748,8 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error "unable to codegen: {s}", .{@errorName(err)}, )); - if (nav.analysis_owner.unwrap()) |cau| { - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .cau = cau })); + if (nav.analysis != null) { + try zcu.retryable_failures.append(zcu.gpa, .wrap(.{ .nav_val = nav_index })); } else { // TODO: we don't have a way to indicate that this failure is retryable! // Since these are really rare, we could as a cop-out retry the whole build next update. @@ -3255,7 +3255,7 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls); const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'"); - pt.ensureCauAnalyzed(ip.getNav(builtin_nav).analysis_owner.unwrap().?) catch @panic("std.builtin is corrupt"); + pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt"); const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val); const builtin_namespace = zcu.namespacePtr(builtin_type.getNamespace(zcu).unwrap() orelse @panic("std.builtin is corrupt")); const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls); @@ -3307,68 +3307,45 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo /// Given a container type requiring resolution, ensures that it is up-to-date. /// If not, the type is recreated at a new `InternPool.Index`. /// The new index is returned. This is the same as the old index if the fields were up-to-date. -/// If `already_updating` is set, assumes the type is already outdated and undergoing re-analysis rather than checking `zcu.outdated`. -pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index, already_updating: bool) Zcu.SemaError!InternPool.Index { +pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; + const gpa = zcu.gpa; const ip = &zcu.intern_pool; + + const anal_unit: AnalUnit = .wrap(.{ .type = ty }); + const outdated = zcu.outdated.swapRemove(anal_unit) or + zcu.potentially_outdated.swapRemove(anal_unit); + + if (!outdated) return ty; + + // We will recreate the type at a new `InternPool.Index`. + + _ = zcu.outdated_ready.swapRemove(anal_unit); + try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); + + // Delete old state which is no longer in use. Technically, this is not necessary: these exports, + // references, etc, will be ignored because the type itself is unreferenced. However, it allows + // reusing the memory which is currently being used to track this state. + zcu.deleteUnitExports(anal_unit); + zcu.deleteUnitReferences(anal_unit); + if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| { + kv.value.destroy(gpa); + } + _ = zcu.transitive_failed_analysis.swapRemove(anal_unit); + zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit); + switch (ip.indexToKey(ty)) { - .struct_type => |key| { - const struct_obj = ip.loadStructType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = struct_obj.cau }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateStructType(key, struct_obj); - }, - .union_type => |key| { - const union_obj = ip.loadUnionType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = union_obj.cau }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateUnionType(key, union_obj); - }, - .enum_type => |key| { - const enum_obj = ip.loadEnumType(ty); - const outdated = already_updating or o: { - const anal_unit = AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? }); - const o = zcu.outdated.swapRemove(anal_unit) or - zcu.potentially_outdated.swapRemove(anal_unit); - if (o) { - _ = zcu.outdated_ready.swapRemove(anal_unit); - try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty }); - } - break :o o; - }; - if (!outdated) return ty; - return pt.recreateEnumType(key, enum_obj); - }, - .opaque_type => { - assert(!already_updating); - return ty; - }, + .struct_type => |key| return pt.recreateStructType(ty, key), + .union_type => |key| return pt.recreateUnionType(ty, key), + .enum_type => |key| return pt.recreateEnumType(ty, key), else => unreachable, } } fn recreateStructType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - struct_obj: InternPool.LoadedStructType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3405,8 +3382,7 @@ fn recreateStructType( if (captures_len != key.captures.owned.len) return error.AnalysisFail; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = struct_obj.cau })); + const struct_obj = ip.loadStructType(old_ty); const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{ .layout = small.layout, @@ -3428,17 +3404,16 @@ fn recreateStructType( errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, struct_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, struct_obj.namespace, wip_ty.index); try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index }, ); zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive. try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - const new_ty = wip_ty.finish(ip, new_cau_index.toOptional(), struct_obj.namespace); + const new_ty = wip_ty.finish(ip, struct_obj.namespace); if (inst_info.inst == .main_struct_inst) { // This is the root type of a file! Update the reference. zcu.setFileRootType(inst_info.file, new_ty); @@ -3448,8 +3423,8 @@ fn recreateStructType( fn recreateUnionType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - union_obj: InternPool.LoadedUnionType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3488,8 +3463,7 @@ fn recreateUnionType( if (captures_len != key.captures.owned.len) return error.AnalysisFail; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = union_obj.cau })); + const union_obj = ip.loadUnionType(old_ty); const namespace_index = union_obj.namespace; @@ -3526,22 +3500,21 @@ fn recreateUnionType( errdefer wip_ty.cancel(ip, pt.tid); wip_ty.setName(ip, union_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); try ip.addDependency( gpa, - AnalUnit.wrap(.{ .cau = new_cau_index }), + .wrap(.{ .type = wip_ty.index }), .{ .src_hash = key.zir_index }, ); zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive. try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index }); - return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index); + return wip_ty.finish(ip, namespace_index); } fn recreateEnumType( pt: Zcu.PerThread, + old_ty: InternPool.Index, full_key: InternPool.Key.NamespaceType, - enum_obj: InternPool.LoadedEnumType, ) Zcu.SemaError!InternPool.Index { const zcu = pt.zcu; const gpa = zcu.gpa; @@ -3610,8 +3583,7 @@ fn recreateEnumType( if (bag != 0) break true; } else false; - // The old type will be unused, so drop its dependency information. - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? })); + const enum_obj = ip.loadEnumType(old_ty); const namespace_index = enum_obj.namespace; @@ -3637,12 +3609,10 @@ fn recreateEnumType( wip_ty.setName(ip, enum_obj.name); - const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index); - zcu.namespacePtr(namespace_index).owner_type = wip_ty.index; // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive. - wip_ty.prepare(ip, new_cau_index, namespace_index); + wip_ty.prepare(ip, namespace_index); done = true; Sema.resolveDeclaredEnum( @@ -3652,7 +3622,6 @@ fn recreateEnumType( key.zir_index, namespace_index, enum_obj.name, - new_cau_index, small, body, tag_type_ref, |
