aboutsummaryrefslogtreecommitdiff
path: root/src/Zcu/PerThread.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Zcu/PerThread.zig')
-rw-r--r--src/Zcu/PerThread.zig1421
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,