aboutsummaryrefslogtreecommitdiff
path: root/src/Zcu/PerThread.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2024-08-11 15:07:06 -0700
committerGitHub <noreply@github.com>2024-08-11 15:07:06 -0700
commitfc2924080694d68945308d394751a2ec4841b3c0 (patch)
treec4b372662b0253de33483225d17a8a79c735ada5 /src/Zcu/PerThread.zig
parent531cd177e89c1edfcd2e52f74f220eb186a25f78 (diff)
parent153e7d6235a7d74d0c02d51f84edc5c06ab7469d (diff)
downloadzig-fc2924080694d68945308d394751a2ec4841b3c0.tar.gz
zig-fc2924080694d68945308d394751a2ec4841b3c0.zip
Merge pull request #20964 from mlugg/the-great-decl-split-mk2
compiler: split `Decl` into `Nav` and `Cau`
Diffstat (limited to 'src/Zcu/PerThread.zig')
-rw-r--r--src/Zcu/PerThread.zig1678
1 files changed, 834 insertions, 844 deletions
diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig
index b4c8c834f9..1074062d55 100644
--- a/src/Zcu/PerThread.zig
+++ b/src/Zcu/PerThread.zig
@@ -6,26 +6,6 @@ tid: Id,
pub const IdBacking = u7;
pub const Id = if (InternPool.single_threaded) enum { main } else enum(IdBacking) { main, _ };
-pub fn destroyDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) void {
- const zcu = pt.zcu;
- const gpa = zcu.gpa;
-
- {
- _ = zcu.test_functions.swapRemove(decl_index);
- if (zcu.global_assembly.fetchSwapRemove(decl_index)) |kv| {
- gpa.free(kv.value);
- }
- }
-
- pt.zcu.intern_pool.destroyDecl(pt.tid, decl_index);
-
- if (zcu.emit_h) |zcu_emit_h| {
- const decl_emit_h = zcu_emit_h.declPtr(decl_index);
- decl_emit_h.fwd_decl.deinit(gpa);
- decl_emit_h.* = undefined;
- }
-}
-
fn deinitFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
@@ -40,9 +20,6 @@ fn deinitFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) void {
file.unload(gpa);
}
file.references.deinit(gpa);
- if (zcu.fileRootDecl(file_index).unwrap()) |root_decl| {
- pt.zcu.intern_pool.destroyDecl(pt.tid, root_decl);
- }
if (file.prev_zir) |prev_zir| {
prev_zir.deinit(gpa);
gpa.destroy(prev_zir);
@@ -62,7 +39,7 @@ pub fn astGenFile(
pt: Zcu.PerThread,
file: *Zcu.File,
path_digest: Cache.BinDigest,
- opt_root_decl: Zcu.Decl.OptionalIndex,
+ old_root_type: InternPool.Index,
) !void {
dev.check(.ast_gen);
assert(!file.mod.isBuiltin());
@@ -323,13 +300,13 @@ pub fn astGenFile(
return error.AnalysisFail;
}
- if (opt_root_decl.unwrap()) |root_decl| {
+ if (old_root_type != .none) {
// The root of this file must be re-analyzed, since the file has changed.
comp.mutex.lock();
defer comp.mutex.unlock();
- log.debug("outdated root Decl: {}", .{root_decl});
- try zcu.outdated_file_root.put(gpa, root_decl, {});
+ log.debug("outdated file root type: {}", .{old_root_type});
+ try zcu.outdated_file_root.put(gpa, old_root_type, {});
}
}
@@ -491,137 +468,171 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
}
}
-/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl.
+/// Ensures that `zcu.fileRootType` on this `file_index` gives an up-to-date answer.
+/// Returns `error.AnalysisFail` if the file has an error.
pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
- if (pt.zcu.fileRootDecl(file_index).unwrap()) |existing_root| {
- return pt.ensureDeclAnalyzed(existing_root);
+ const file_root_type = pt.zcu.fileRootType(file_index);
+ if (file_root_type != .none) {
+ const file_root_type_cau = pt.zcu.intern_pool.loadStructType(file_root_type).cau.unwrap().?;
+ return pt.ensureCauAnalyzed(file_root_type_cau);
} else {
return pt.semaFile(file_index);
}
}
-/// This ensures that the Decl will have an up-to-date Type and Value populated.
-/// However the resolution status of the Type may not be fully resolved.
-/// For example an inferred error set is not resolved until after `analyzeFnBody`.
-/// is called.
-pub fn ensureDeclAnalyzed(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) Zcu.SemaError!void {
- dev.check(.sema);
-
+/// 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 {
const tracy = trace(@src());
defer tracy.end();
- const mod = pt.zcu;
- const ip = &mod.intern_pool;
- const decl = mod.declPtr(decl_index);
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
- log.debug("ensureDeclAnalyzed '{d}' (name '{}')", .{
- @intFromEnum(decl_index),
- decl.name.fmt(ip),
- });
+ const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
+ const cau = ip.getCau(cau_index);
+ const inst_info = cau.zir_index.resolveFull(ip);
+
+ log.debug("ensureCauAnalyzed {d}", .{@intFromEnum(cau_index)});
+
+ assert(!zcu.analysis_in_progress.contains(anal_unit));
- // Determine whether or not this Decl is outdated, i.e. requires re-analysis
- // even if `complete`. If a Decl is PO, we pessismistically assume that it
- // *does* require re-analysis, to ensure that the Decl is definitely
+ // 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 Decls whose previously listed
+ // which tries to limit re-analysis to Caus whose previously listed
// dependencies are all up-to-date.
- const decl_as_depender = InternPool.AnalUnit.wrap(.{ .decl = decl_index });
- const decl_was_outdated = mod.outdated.swapRemove(decl_as_depender) or
- mod.potentially_outdated.swapRemove(decl_as_depender);
+ const cau_outdated = zcu.outdated.swapRemove(anal_unit) or
+ zcu.potentially_outdated.swapRemove(anal_unit);
+
+ if (cau_outdated) {
+ _ = zcu.outdated_ready.swapRemove(anal_unit);
+ }
+
+ // TODO: this only works if namespace lookups in Sema trigger `ensureCauAnalyzed`, because
+ // `outdated_file_root` information is not "viral", so we need that a namespace lookup first
+ // handles the case where the file root is not an outdated *type* but does have an outdated
+ // *namespace*. A more logically simple alternative may be for a file's root struct to register
+ // a dependency on the file's entire source code (hash). Alternatively, we could make sure that
+ // these are always handled first in an update. Actually, that's probably the best option.
+ // 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
+ // * TODO: optimize this to make sure we only do it once a generation i guess?
+ // * so everyone lived happily ever after
+ const file_root_outdated = switch (cau.owner.unwrap()) {
+ .type => |ty| zcu.outdated_file_root.swapRemove(ty),
+ .nav, .none => false,
+ };
- if (decl_was_outdated) {
- _ = mod.outdated_ready.swapRemove(decl_as_depender);
+ if (zcu.fileByIndex(inst_info.file).status != .success_zir) {
+ return error.AnalysisFail;
}
- const was_outdated = mod.outdated_file_root.swapRemove(decl_index) or decl_was_outdated;
-
- switch (decl.analysis) {
- .in_progress => unreachable,
-
- .file_failure => return error.AnalysisFail,
-
- .sema_failure,
- .dependency_failure,
- .codegen_failure,
- => if (!was_outdated) return error.AnalysisFail,
-
- .complete => if (!was_outdated) return,
-
- .unreferenced => {},
+ if (!cau_outdated and !file_root_outdated) {
+ // We can trust the current information about this `Cau`.
+ if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) {
+ return error.AnalysisFail;
+ }
+ // 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,
+ }
}
- if (was_outdated) {
- dev.check(.incremental);
- // The exports this Decl performs will be re-discovered, so we remove them here
+ // `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.
- mod.deleteUnitExports(decl_as_depender);
- mod.deleteUnitReferences(decl_as_depender);
+ zcu.deleteUnitExports(anal_unit);
+ zcu.deleteUnitReferences(anal_unit);
}
- const sema_result: Zcu.SemaDeclResult = blk: {
- if (decl.zir_decl_index == .none and !mod.declIsRoot(decl_index)) {
- // Anonymous decl. We don't semantically analyze these.
- break :blk .{
- .invalidate_decl_val = false,
- .invalidate_decl_ref = false,
- };
- }
-
- if (mod.declIsRoot(decl_index)) {
- const changed = try pt.semaFileUpdate(decl.getFileScopeIndex(mod), decl_was_outdated);
- break :blk .{
+ const sema_result: SemaCauResult = res: {
+ if (inst_info.inst == .main_struct_inst) {
+ const changed = try pt.semaFileUpdate(inst_info.file, cau_outdated);
+ break :res .{
.invalidate_decl_val = changed,
.invalidate_decl_ref = changed,
};
}
- const decl_prog_node = mod.sema_prog_node.start(decl.fqn.toSlice(ip), 0);
+ 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();
- break :blk pt.semaDecl(decl_index) catch |err| switch (err) {
+ break :res pt.semaCau(cau_index) catch |err| switch (err) {
error.AnalysisFail => {
- if (decl.analysis == .in_progress) {
- // If this decl caused the compile error, the analysis field would
- // be changed to indicate it was this Decl's fault. Because this
- // did not happen, we infer here that it was a dependency failure.
- decl.analysis = .dependency_failure;
+ if (!zcu.failed_analysis.contains(anal_unit)) {
+ // If this `Cau` 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, {});
}
return error.AnalysisFail;
},
error.GenericPoison => unreachable,
- else => |e| {
- decl.analysis = .sema_failure;
- try mod.failed_analysis.ensureUnusedCapacity(mod.gpa, 1);
- try mod.retryable_failures.append(mod.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
- mod.failed_analysis.putAssumeCapacityNoClobber(InternPool.AnalUnit.wrap(.{ .decl = decl_index }), try Zcu.ErrorMsg.create(
- mod.gpa,
- decl.navSrcLoc(mod),
- "unable to analyze: {s}",
- .{@errorName(e)},
+ error.ComptimeBreak => unreachable,
+ error.ComptimeReturn => unreachable,
+ error.OutOfMemory => {
+ try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
+ try zcu.retryable_failures.append(gpa, anal_unit);
+ zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, try Zcu.ErrorMsg.create(
+ gpa,
+ .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) },
+ "unable to analyze: OutOfMemory",
+ .{},
));
return error.AnalysisFail;
},
};
};
+ if (!cau_outdated) {
+ // We definitely don't need to do any dependency tracking, so our work is done.
+ return;
+ }
+
// TODO: we do not yet have separate dependencies for decl values vs types.
- if (decl_was_outdated) {
- if (sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref) {
- log.debug("Decl tv invalidated ('{d}')", .{@intFromEnum(decl_index)});
- // This dependency was marked as PO, meaning dependees were waiting
- // on its analysis result, and it has turned out to be outdated.
- // Update dependees accordingly.
- try mod.markDependeeOutdated(.{ .decl_val = decl_index });
- } else {
- log.debug("Decl tv up-to-date ('{d}')", .{@intFromEnum(decl_index)});
- // This dependency was previously PO, but turned out to be up-to-date.
- // We do not need to queue successive analysis.
- try mod.markPoDependeeUpToDate(.{ .decl_val = decl_index });
- }
+ 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 (invalidate) {
+ // This dependency was marked as PO, meaning dependees were waiting
+ // on its analysis result, and it has turned out to be outdated.
+ // Update dependees accordingly.
+ try zcu.markDependeeOutdated(dependee);
+ } else {
+ // This dependency was previously PO, but turned out to be up-to-date.
+ // We do not need to queue successive analysis.
+ try zcu.markPoDependeeUpToDate(dependee);
}
}
@@ -636,28 +647,32 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
const ip = &zcu.intern_pool;
// We only care about the uncoerced function.
- // We need to do this for the "orphaned function" check below to be valid.
const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
const func = zcu.funcInfo(maybe_coerced_func_index);
- const decl_index = func.owner_decl;
- const decl = zcu.declPtr(decl_index);
- log.debug("ensureFuncBodyAnalyzed '{d}' (instance of '{}')", .{
- @intFromEnum(func_index),
- decl.name.fmt(ip),
- });
+ log.debug("ensureFuncBodyAnalyzed {d}", .{@intFromEnum(func_index)});
- // First, our owner decl must be up-to-date. This will always be the case
- // during the first update, but may not on successive updates if we happen
- // to get analyzed before our parent decl.
- try pt.ensureDeclAnalyzed(decl_index);
+ // Here's an interesting question: is this function actually valid?
+ // Maybe the signature changed, so we'll end up creating a whole different `func`
+ // in the InternPool, and this one is a waste of time to analyze. Worse, we'd be
+ // analyzing new ZIR with old data, and get bogus errors. They would be unused,
+ // but they would still hang around internally! So, let's detect this case.
+ // For function decls, we must ensure the declaration's `Cau` is up-to-date, and
+ // check if `func_index` was removed by that update.
+ // For function instances, we do that process on the generic owner.
- // On an update, it's possible this function changed such that our owner
- // decl now refers to a different function, making this one orphaned. If
- // that's the case, we should remove this function from the binary.
- if (decl.val.ip_index != func_index) {
- try zcu.markDependeeOutdated(.{ .func_ies = func_index });
+ try pt.ensureCauAnalyzed(cau: {
+ const func_nav = if (func.generic_owner == .none)
+ func.owner_nav
+ else
+ zcu.funcInfo(func.generic_owner).owner_nav;
+
+ break :cau ip.getNav(func_nav).analysis_owner.unwrap().?;
+ });
+
+ if (ip.isRemoved(func_index) or (func.generic_owner != .none and ip.isRemoved(func.generic_owner))) {
+ try zcu.markDependeeOutdated(.{ .interned = func_index }); // IES
ip.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index }));
ip.remove(pt.tid, func_index);
@panic("TODO: remove orphaned function from binary");
@@ -670,58 +685,40 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
else
.none;
- switch (decl.analysis) {
- .unreferenced => unreachable,
- .in_progress => unreachable,
-
- .codegen_failure => unreachable, // functions do not perform constant value generation
+ const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index });
+ const func_outdated = zcu.outdated.swapRemove(anal_unit) or
+ zcu.potentially_outdated.swapRemove(anal_unit);
- .file_failure,
- .sema_failure,
- .dependency_failure,
- => return error.AnalysisFail,
-
- .complete => {},
- }
-
- const func_as_depender = InternPool.AnalUnit.wrap(.{ .func = func_index });
- const was_outdated = zcu.outdated.swapRemove(func_as_depender) or
- zcu.potentially_outdated.swapRemove(func_as_depender);
-
- if (was_outdated) {
+ if (func_outdated) {
dev.check(.incremental);
- _ = zcu.outdated_ready.swapRemove(func_as_depender);
- zcu.deleteUnitExports(func_as_depender);
- zcu.deleteUnitReferences(func_as_depender);
+ _ = zcu.outdated_ready.swapRemove(anal_unit);
+ zcu.deleteUnitExports(anal_unit);
+ zcu.deleteUnitReferences(anal_unit);
}
- switch (func.analysisUnordered(ip).state) {
- .success => if (!was_outdated) return,
- .sema_failure,
- .dependency_failure,
- .codegen_failure,
- => if (!was_outdated) return error.AnalysisFail,
- .none, .queued => {},
- .in_progress => unreachable,
- .inline_only => unreachable, // don't queue work for this
+ 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, // up-to-date
+ }
}
log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{
@intFromEnum(func_index),
- if (was_outdated) "outdated" else "never analyzed",
+ if (func_outdated) "outdated" else "never analyzed",
});
- var tmp_arena = std.heap.ArenaAllocator.init(gpa);
- defer tmp_arena.deinit();
- const sema_arena = tmp_arena.allocator();
-
- var air = pt.analyzeFnBody(func_index, sema_arena) catch |err| switch (err) {
+ var air = pt.analyzeFnBody(func_index) catch |err| switch (err) {
error.AnalysisFail => {
- if (func.analysisUnordered(ip).state == .in_progress) {
- // If this decl caused the compile error, the analysis field would
- // be changed to indicate it was this Decl's fault. Because this
- // did not happen, we infer here that it was a dependency failure.
- func.setAnalysisState(ip, .dependency_failure);
+ if (!zcu.failed_analysis.contains(anal_unit)) {
+ // If this function 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, {});
}
return error.AnalysisFail;
},
@@ -729,18 +726,14 @@ pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: Inter
};
errdefer air.deinit(gpa);
- const invalidate_ies_deps = i: {
- if (!was_outdated) break :i false;
- if (!func.analysisUnordered(ip).inferred_error_set) break :i true;
- const new_resolved_ies = func.resolvedErrorSetUnordered(ip);
- break :i new_resolved_ies != old_resolved_ies;
- };
- if (invalidate_ies_deps) {
- log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)});
- try zcu.markDependeeOutdated(.{ .func_ies = func_index });
- } else if (was_outdated) {
- log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)});
- try zcu.markPoDependeeUpToDate(.{ .func_ies = func_index });
+ if (func_outdated) {
+ if (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies) {
+ log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)});
+ try zcu.markDependeeOutdated(.{ .interned = func_index });
+ } else {
+ log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)});
+ try zcu.markPoDependeeUpToDate(.{ .interned = func_index });
+ }
}
const comp = zcu.comp;
@@ -773,16 +766,16 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
}
const func = zcu.funcInfo(func_index);
- const decl_index = func.owner_decl;
- const decl = zcu.declPtr(decl_index);
+ const nav_index = func.owner_nav;
+ const nav = ip.getNav(nav_index);
var liveness = try Liveness.analyze(gpa, air, ip);
defer liveness.deinit(gpa);
if (build_options.enable_debug_extensions and comp.verbose_air) {
- std.debug.print("# Begin Function AIR: {}:\n", .{decl.fqn.fmt(ip)});
+ std.debug.print("# Begin Function AIR: {}:\n", .{nav.fqn.fmt(ip)});
@import("../print_air.zig").dump(pt, air, liveness);
- std.debug.print("# End Function AIR: {}\n\n", .{decl.fqn.fmt(ip)});
+ std.debug.print("# End Function AIR: {}\n\n", .{nav.fqn.fmt(ip)});
}
if (std.debug.runtime_safety) {
@@ -797,23 +790,18 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
verify.verify() catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => {
- try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
- zcu.failed_analysis.putAssumeCapacityNoClobber(
- InternPool.AnalUnit.wrap(.{ .func = func_index }),
- try Zcu.ErrorMsg.create(
- gpa,
- decl.navSrcLoc(zcu),
- "invalid liveness: {s}",
- .{@errorName(err)},
- ),
- );
- func.setAnalysisState(ip, .codegen_failure);
+ try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
+ gpa,
+ zcu.navSrcLoc(nav_index),
+ "invalid liveness: {s}",
+ .{@errorName(err)},
+ ));
return;
},
};
}
- const codegen_prog_node = zcu.codegen_prog_node.start(decl.fqn.toSlice(ip), 0);
+ const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0);
defer codegen_prog_node.end();
if (!air.typesFullyResolved(zcu)) {
@@ -821,22 +809,21 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
// Correcting this failure will involve changing a type this function
// depends on, hence triggering re-analysis of this function, so this
// interacts correctly with incremental compilation.
- func.setAnalysisState(ip, .codegen_failure);
+ // TODO: do we need to mark this failure anywhere? I don't think so, since compilation
+ // will fail due to the type error anyway.
} else if (comp.bin_file) |lf| {
lf.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {
- func.setAnalysisState(ip, .codegen_failure);
+ assert(zcu.failed_codegen.contains(nav_index));
},
else => {
- try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
- zcu.failed_analysis.putAssumeCapacityNoClobber(InternPool.AnalUnit.wrap(.{ .func = func_index }), try Zcu.ErrorMsg.create(
+ try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
gpa,
- decl.navSrcLoc(zcu),
+ zcu.navSrcLoc(nav_index),
"unable to codegen: {s}",
.{@errorName(err)},
));
- func.setAnalysisState(ip, .codegen_failure);
try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .func = func_index }));
},
};
@@ -851,17 +838,16 @@ pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Ai
pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void {
dev.check(.sema);
const import_file_result = try pt.importPkg(pkg);
- const root_decl_index = pt.zcu.fileRootDecl(import_file_result.file_index);
- if (root_decl_index == .none) {
+ const root_type = pt.zcu.fileRootType(import_file_result.file_index);
+ if (root_type == .none) {
return pt.semaFile(import_file_result.file_index);
}
}
-fn getFileRootStruct(
+fn createFileRootStruct(
pt: Zcu.PerThread,
- decl_index: Zcu.Decl.Index,
- namespace_index: Zcu.Namespace.Index,
file_index: Zcu.File.Index,
+ namespace_index: Zcu.Namespace.Index,
) Allocator.Error!InternPool.Index {
const zcu = pt.zcu;
const gpa = zcu.gpa;
@@ -901,7 +887,6 @@ fn getFileRootStruct(
.any_default_inits = small.any_default_inits,
.inits_resolved = false,
.any_aligned_fields = small.any_aligned_fields,
- .has_namespace = true,
.key = .{ .declared = .{
.zir_index = tracked_inst,
.captures = &.{},
@@ -912,34 +897,37 @@ fn getFileRootStruct(
};
errdefer wip_ty.cancel(ip, pt.tid);
+ 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,
- InternPool.AnalUnit.wrap(.{ .decl = decl_index }),
+ InternPool.AnalUnit.wrap(.{ .cau = new_cau_index }),
.{ .src_hash = tracked_inst },
);
}
- const decl = zcu.declPtr(decl_index);
- decl.val = Value.fromInterned(wip_ty.index);
- decl.has_tv = true;
- decl.owns_tv = true;
- decl.analysis = .complete;
-
- try pt.scanNamespace(namespace_index, decls, decl);
+ try pt.scanNamespace(namespace_index, decls);
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
- return wip_ty.finish(ip, decl_index, namespace_index.toOptional());
+ zcu.setFileRootType(file_index, wip_ty.index);
+ return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
}
-/// Re-analyze the root Decl of a file on an incremental update.
+/// Re-analyze the root type of a file on an incremental update.
/// If `type_outdated`, the struct type itself is considered outdated and is
/// reconstructed at a new InternPool index. Otherwise, the namespace is just
/// re-analyzed. Returns whether the decl's tyval was invalidated.
+/// Returns `error.AnalysisFail` if the file has an error.
fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated: bool) Zcu.SemaError!bool {
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const file = zcu.fileByIndex(file_index);
- const decl = zcu.declPtr(zcu.fileRootDecl(file_index).unwrap().?);
+ const file_root_type = zcu.fileRootType(file_index);
+ const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);
+
+ assert(file_root_type != .none);
log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{
file.mod.fully_qualified_name,
@@ -948,33 +936,18 @@ fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated:
});
if (file.status != .success_zir) {
- if (decl.analysis == .file_failure) {
- return false;
- } else {
- decl.analysis = .file_failure;
- return true;
- }
- }
-
- if (decl.analysis == .file_failure) {
- // No struct type currently exists. Create one!
- const root_decl = zcu.fileRootDecl(file_index);
- _ = try pt.getFileRootStruct(root_decl.unwrap().?, decl.src_namespace, file_index);
- return true;
+ return error.AnalysisFail;
}
- assert(decl.has_tv);
- assert(decl.owns_tv);
-
if (type_outdated) {
- // Invalidate the existing type, reusing the decl and namespace.
- const file_root_decl = zcu.fileRootDecl(file_index).unwrap().?;
- ip.removeDependenciesForDepender(zcu.gpa, InternPool.AnalUnit.wrap(.{
- .decl = file_root_decl,
- }));
- ip.remove(pt.tid, decl.val.toIntern());
- decl.val = undefined;
- _ = try pt.getFileRootStruct(file_root_decl, decl.src_namespace, file_index);
+ // Invalidate the existing type, reusing its namespace.
+ const file_root_type_cau = ip.loadStructType(file_root_type).cau.unwrap().?;
+ ip.removeDependenciesForDepender(
+ zcu.gpa,
+ InternPool.AnalUnit.wrap(.{ .cau = file_root_type_cau }),
+ );
+ ip.remove(pt.tid, file_root_type);
+ _ = try pt.createFileRootStruct(file_index, namespace_index);
return true;
}
@@ -994,7 +967,7 @@ fn semaFileUpdate(pt: Zcu.PerThread, file_index: Zcu.File.Index, type_outdated:
const decls = file.zir.bodySlice(extra_index, decls_len);
if (!type_outdated) {
- try pt.scanNamespace(decl.src_namespace, decls, decl);
+ try pt.scanNamespace(namespace_index, decls);
}
return false;
@@ -1009,43 +982,19 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const file = zcu.fileByIndex(file_index);
- assert(zcu.fileRootDecl(file_index) == .none);
- log.debug("semaFile zcu={s} sub_file_path={s}", .{
- file.mod.fully_qualified_name, file.sub_file_path,
- });
-
- // Because these three things each reference each other, `undefined`
- // placeholders are used before being set after the struct type gains an
- // InternPool index.
- const new_namespace_index = try pt.createNamespace(.{
- .parent = .none,
- .decl_index = undefined,
- .file_scope = file_index,
- });
- errdefer pt.destroyNamespace(new_namespace_index);
-
- const new_decl_index = try pt.allocateNewDecl(new_namespace_index);
- const new_decl = zcu.declPtr(new_decl_index);
- errdefer @panic("TODO error handling");
-
- zcu.setFileRootDecl(file_index, new_decl_index.toOptional());
- zcu.namespacePtr(new_namespace_index).decl_index = new_decl_index;
-
- new_decl.fqn = try file.internFullyQualifiedName(pt);
- new_decl.name = new_decl.fqn;
- new_decl.is_pub = true;
- new_decl.is_exported = false;
- new_decl.alignment = .none;
- new_decl.@"linksection" = .none;
- new_decl.analysis = .in_progress;
+ assert(zcu.fileRootType(file_index) == .none);
if (file.status != .success_zir) {
- new_decl.analysis = .file_failure;
- return;
+ return error.AnalysisFail;
}
assert(file.zir_loaded);
- const struct_ty = try pt.getFileRootStruct(new_decl_index, new_namespace_index, file_index);
+ const new_namespace_index = try pt.createNamespace(.{
+ .parent = .none,
+ .owner_type = undefined, // set in `createFileRootStruct`
+ .file_scope = file_index,
+ });
+ const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index);
errdefer zcu.intern_pool.remove(pt.tid, struct_ty);
switch (zcu.comp.cache_use) {
@@ -1067,98 +1016,121 @@ fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
whole.cache_manifest_mutex.lock();
defer whole.cache_manifest_mutex.unlock();
- try man.addFilePostContents(resolved_path, source.bytes, source.stat);
+ man.addFilePostContents(resolved_path, source.bytes, source.stat) catch |err| switch (err) {
+ error.OutOfMemory => |e| return e,
+ else => {
+ try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)});
+ return error.AnalysisFail;
+ },
+ };
},
.incremental => {},
}
}
-fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
- const tracy = trace(@src());
- defer tracy.end();
+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 decl = zcu.declPtr(decl_index);
+ const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
- if (decl.getFileScope(zcu).status != .success_zir) {
- return error.AnalysisFail;
- }
+ const anal_unit = InternPool.AnalUnit.wrap(.{ .cau = cau_index });
- assert(!zcu.declIsRoot(decl_index));
+ const cau = ip.getCau(cau_index);
+ const inst_info = cau.zir_index.resolveFull(ip);
+ const file = zcu.fileByIndex(inst_info.file);
+ const zir = file.zir;
- if (decl.zir_decl_index == .none and decl.owns_tv) {
- // We are re-analyzing an anonymous owner Decl (for a function or a namespace type).
- return pt.semaAnonOwnerDecl(decl_index);
+ if (file.status != .success_zir) {
+ return error.AnalysisFail;
}
- log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)});
- log.debug("decl name '{}'", .{decl.fqn.fmt(ip)});
- defer log.debug("finish decl name '{}'", .{decl.fqn.fmt(ip)});
+ // We are about to re-analyze this `Cau`; drop its depenndencies.
+ zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
- const old_has_tv = decl.has_tv;
- // The following values are ignored if `!old_has_tv`
- const old_ty = if (old_has_tv) decl.typeOf(zcu) else undefined;
- const old_val = decl.val;
- const old_align = decl.alignment;
- const old_linksection = decl.@"linksection";
- const old_addrspace = decl.@"addrspace";
- const old_is_inline = if (decl.getOwnedFunction(zcu)) |prev_func|
- prev_func.analysisUnordered(ip).state == .inline_only
- else
- false;
-
- const decl_inst = decl.zir_decl_index.unwrap().?.resolve(ip);
+ const builtin_type_target_index: InternPool.Index = switch (cau.owner.unwrap()) {
+ .none => ip_index: {
+ // `comptime` decl -- we will re-analyze its body.
+ // This declaration has no value so is definitely not a std.builtin type.
+ break :ip_index .none;
+ },
+ .type => |ty| {
+ // This is an incremental update, and this type is being re-analyzed because it is outdated.
+ // The type must be recreated at a new `InternPool.Index`.
+ // Remove it from the InternPool and mark it outdated so that creation sites are re-analyzed.
+ ip.remove(pt.tid, ty);
+ return .{
+ .invalidate_decl_val = true,
+ .invalidate_decl_ref = true,
+ };
+ },
+ .nav => |nav| ip_index: {
+ // Other decl -- we will re-analyze its value.
+ // This might be a type in `builtin.zig` -- check.
+ if (file.mod != zcu.std_mod) break :ip_index .none;
+ // We're in the std module.
+ const nav_name = ip.getNav(nav).name;
+ const std_file_imported = try pt.importPkg(zcu.std_mod);
+ const std_type = Type.fromInterned(zcu.fileRootType(std_file_imported.file_index));
+ const std_namespace = zcu.namespacePtr(std_type.getNamespace(zcu).unwrap().?);
+ const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
+ const builtin_nav = ip.getNav(std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse break :ip_index .none);
+ const builtin_namespace = switch (builtin_nav.status) {
+ .unresolved => break :ip_index .none,
+ .resolved => |r| Type.fromInterned(r.val).getNamespace(zcu).unwrap().?,
+ };
+ if (cau.namespace != builtin_namespace) break :ip_index .none;
+ // We're in builtin.zig. This could be a builtin we need to add to a specific InternPool index.
+ for ([_][]const u8{
+ "AtomicOrder",
+ "AtomicRmwOp",
+ "CallingConvention",
+ "AddressSpace",
+ "FloatMode",
+ "ReduceOp",
+ "CallModifier",
+ "PrefetchOptions",
+ "ExportOptions",
+ "ExternOptions",
+ "Type",
+ }, [_]InternPool.Index{
+ .atomic_order_type,
+ .atomic_rmw_op_type,
+ .calling_convention_type,
+ .address_space_type,
+ .float_mode_type,
+ .reduce_op_type,
+ .call_modifier_type,
+ .prefetch_options_type,
+ .export_options_type,
+ .extern_options_type,
+ .type_info_type,
+ }) |type_name, type_ip| {
+ if (nav_name.eqlSlice(type_name, ip)) break :ip_index type_ip;
+ }
+ break :ip_index .none;
+ },
+ };
- const gpa = zcu.gpa;
- const zir = decl.getFileScope(zcu).zir;
-
- const builtin_type_target_index: InternPool.Index = ip_index: {
- const std_mod = zcu.std_mod;
- if (decl.getFileScope(zcu).mod != std_mod) break :ip_index .none;
- // We're in the std module.
- const std_file_imported = try pt.importPkg(std_mod);
- const std_file_root_decl_index = zcu.fileRootDecl(std_file_imported.file_index);
- const std_decl = zcu.declPtr(std_file_root_decl_index.unwrap().?);
- const std_namespace = std_decl.getInnerNamespace(zcu).?;
- const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
- const builtin_decl = zcu.declPtr(std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse break :ip_index .none);
- const builtin_namespace = builtin_decl.getInnerNamespaceIndex(zcu).unwrap() orelse break :ip_index .none;
- if (decl.src_namespace != builtin_namespace) break :ip_index .none;
- // We're in builtin.zig. This could be a builtin we need to add to a specific InternPool index.
- for ([_][]const u8{
- "AtomicOrder",
- "AtomicRmwOp",
- "CallingConvention",
- "AddressSpace",
- "FloatMode",
- "ReduceOp",
- "CallModifier",
- "PrefetchOptions",
- "ExportOptions",
- "ExternOptions",
- "Type",
- }, [_]InternPool.Index{
- .atomic_order_type,
- .atomic_rmw_op_type,
- .calling_convention_type,
- .address_space_type,
- .float_mode_type,
- .reduce_op_type,
- .call_modifier_type,
- .prefetch_options_type,
- .export_options_type,
- .extern_options_type,
- .type_info_type,
- }) |type_name, type_ip| {
- if (decl.name.eqlSlice(type_name, ip)) break :ip_index type_ip;
- }
- break :ip_index .none;
+ const is_usingnamespace = switch (cau.owner.unwrap()) {
+ .nav => |nav| ip.getNav(nav).is_usingnamespace,
+ .none, .type => false,
};
- zcu.intern_pool.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
+ log.debug("semaCau '{d}'", .{@intFromEnum(cau_index)});
- decl.analysis = .in_progress;
+ 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();
@@ -1171,224 +1143,216 @@ fn semaDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
.gpa = gpa,
.arena = analysis_arena.allocator(),
.code = zir,
- .owner_decl = decl,
- .owner_decl_index = decl_index,
+ .owner = anal_unit,
.func_index = .none,
.func_is_naked = false,
.fn_ret_ty = Type.void,
.fn_ret_ty_ies = null,
- .owner_func_index = .none,
.comptime_err_ret_trace = &comptime_err_ret_trace,
.builtin_type_target_index = builtin_type_target_index,
};
defer sema.deinit();
- // Every Decl (other than file root Decls, which do not have a ZIR index) has a dependency on its own source.
- try sema.declareDependency(.{ .src_hash = try ip.trackZir(gpa, pt.tid, .{
- .file = decl.getFileScopeIndex(zcu),
- .inst = decl_inst,
- }) });
+ // Every `Cau` has a dependency on the source of its own ZIR instruction.
+ try sema.declareDependency(.{ .src_hash = cau.zir_index });
- var block_scope: Sema.Block = .{
+ var block: Sema.Block = .{
.parent = null,
.sema = &sema,
- .namespace = decl.src_namespace,
+ .namespace = cau.namespace,
.instructions = .{},
.inlining = null,
.is_comptime = true,
- .src_base_inst = decl.zir_decl_index.unwrap().?,
- .type_name_ctx = decl.name,
+ .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.Inst.Declaration, const decl_bodies: Zir.Inst.Declaration.Bodies = decl: {
+ const decl, const extra_end = zir.getDeclaration(inst_info.inst);
+ break :decl .{ decl, decl.getBodies(extra_end, zir) };
+ };
+
+ // 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),
};
- defer block_scope.instructions.deinit(gpa);
- const decl_bodies = decl.zirBodies(zcu);
+ const result_ref = try sema.resolveInlineBody(&block, decl_bodies.value_body, inst_info.inst);
- const result_ref = try sema.resolveInlineBody(&block_scope, decl_bodies.value_body, decl_inst);
- // We'll do some other bits with the Sema. Clear the type target index just
- // in case they analyze any type.
+ 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.
+ };
+
+ // We'll do more work with the Sema. Clear the target type index just in case we analyze any type.
sema.builtin_type_target_index = .none;
- const align_src = block_scope.src(.{ .node_offset_var_decl_align = 0 });
- const section_src = block_scope.src(.{ .node_offset_var_decl_section = 0 });
- const address_space_src = block_scope.src(.{ .node_offset_var_decl_addrspace = 0 });
- const ty_src = block_scope.src(.{ .node_offset_var_decl_ty = 0 });
- const init_src = block_scope.src(.{ .node_offset_var_decl_init = 0 });
- const decl_val = try sema.resolveFinalDeclValue(&block_scope, init_src, result_ref);
+
+ 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 });
+
+ const decl_val = try sema.resolveFinalDeclValue(&block, init_src, result_ref);
const decl_ty = decl_val.typeOf(zcu);
- // Note this resolves the type of the Decl, not the value; if this Decl
- // is a struct, for example, this resolves `type` (which needs no resolution),
- // not the struct itself.
+ 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);
- if (decl.kind == .@"usingnamespace") {
- if (!decl_ty.eql(Type.type, zcu)) {
- return sema.fail(&block_scope, ty_src, "expected type, found {}", .{decl_ty.fmt(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)});
}
- const ty = decl_val.toType();
- if (ty.getNamespace(zcu) == null) {
- return sema.fail(&block_scope, ty_src, "type {} has no namespace", .{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)});
}
-
- decl.val = ty.toValue();
- decl.alignment = .none;
- decl.@"linksection" = .none;
- decl.has_tv = true;
- decl.owns_tv = false;
- decl.analysis = .complete;
-
- // TODO: usingnamespace cannot currently participate in incremental compilation
+ 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,
};
}
- var queue_linker_work = true;
- var is_func = false;
- var is_inline = false;
- switch (decl_val.toIntern()) {
- .generic_poison => unreachable,
- .unreachable_value => unreachable,
- else => switch (ip.indexToKey(decl_val.toIntern())) {
- .variable => |variable| {
- decl.owns_tv = variable.decl == decl_index;
- queue_linker_work = decl.owns_tv;
- },
-
- .extern_func => |extern_func| {
- decl.owns_tv = extern_func.decl == decl_index;
- queue_linker_work = decl.owns_tv;
- is_func = decl.owns_tv;
- },
-
- .func => |func| {
- decl.owns_tv = func.owner_decl == decl_index;
- queue_linker_work = false;
- is_inline = decl.owns_tv and decl_ty.fnCallingConvention(zcu) == .Inline;
- is_func = decl.owns_tv;
- },
-
- else => {},
- },
- }
+ const nav_already_populated, const queue_linker_work = switch (ip.indexToKey(decl_val.toIntern())) {
+ .func => |f| .{ f.owner_nav == nav_index, false },
+ .variable => |v| .{ false, v.owner_nav == nav_index },
+ .@"extern" => .{ false, false },
+ else => .{ false, true },
+ };
- decl.val = decl_val;
- // Function linksection, align, and addrspace were already set by Sema
- if (!is_func) {
- decl.alignment = blk: {
- const align_body = decl_bodies.align_body orelse break :blk .none;
- const align_ref = try sema.resolveInlineBody(&block_scope, align_body, decl_inst);
- break :blk try sema.analyzeAsAlign(&block_scope, align_src, align_ref);
+ if (nav_already_populated) {
+ // This is a function declaration.
+ // Logic in `Sema.funcCommon` has already populated the `Nav` for us.
+ assert(ip.getNav(nav_index).status.resolved.val == decl_val.toIntern());
+ } else {
+ // Keep in sync with logic in `Sema.zirVarExtended`.
+ const alignment: InternPool.Alignment = a: {
+ const align_body = decl_bodies.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);
};
- decl.@"linksection" = blk: {
- const linksection_body = decl_bodies.linksection_body orelse break :blk .none;
- const linksection_ref = try sema.resolveInlineBody(&block_scope, linksection_body, decl_inst);
- const bytes = try sema.toConstString(&block_scope, section_src, linksection_ref, .{
+
+ const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
+ const linksection_body = decl_bodies.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_scope, section_src, "linksection cannot contain null bytes", .{});
+ return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{});
} else if (bytes.len == 0) {
- return sema.fail(&block_scope, section_src, "linksection cannot be empty", .{});
+ return sema.fail(&block, section_src, "linksection cannot be empty", .{});
}
- break :blk try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
+ break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
};
- decl.@"addrspace" = blk: {
+
+ const @"addrspace": std.builtin.AddressSpace = as: {
const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(decl_val.toIntern())) {
+ .func => .function,
.variable => .variable,
- .extern_func, .func => .function,
+ .@"extern" => |e| if (ip.indexToKey(e.ty) == .func_type)
+ .function
+ else
+ .variable,
else => .constant,
};
-
const target = zcu.getTarget();
-
- const addrspace_body = decl_bodies.addrspace_body orelse break :blk switch (addrspace_ctx) {
+ const addrspace_body = decl_bodies.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_scope, addrspace_body, decl_inst);
- break :blk try sema.analyzeAsAddressSpace(&block_scope, address_space_src, addrspace_ref, addrspace_ctx);
+ const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst);
+ break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx);
};
- }
- decl.has_tv = true;
- decl.analysis = .complete;
-
- const result: Zcu.SemaDeclResult = if (old_has_tv) .{
- .invalidate_decl_val = !decl_ty.eql(old_ty, zcu) or
- !decl.val.eql(old_val, decl_ty, zcu) or
- is_inline != old_is_inline,
- .invalidate_decl_ref = !decl_ty.eql(old_ty, zcu) or
- decl.alignment != old_align or
- decl.@"linksection" != old_linksection or
- decl.@"addrspace" != old_addrspace or
- is_inline != old_is_inline,
- } else .{
- .invalidate_decl_val = true,
- .invalidate_decl_ref = true,
- };
-
- const has_runtime_bits = queue_linker_work and (is_func or try sema.typeHasRuntimeBits(decl_ty));
- if (has_runtime_bits) {
- // Needed for codegen_decl which will call updateDecl and then the
- // codegen backend wants full access to the Decl Type.
- try decl_ty.resolveFully(pt);
-
- try zcu.comp.queueJob(.{ .codegen_decl = decl_index });
- if (result.invalidate_decl_ref and zcu.emit_h != null) {
- try zcu.comp.queueJob(.{ .emit_h_decl = decl_index });
- }
+ ip.resolveNavValue(nav_index, .{
+ .val = decl_val.toIntern(),
+ .alignment = alignment,
+ .@"linksection" = @"linksection",
+ .@"addrspace" = @"addrspace",
+ });
}
- if (decl.is_exported) {
- const export_src = block_scope.src(.{ .token_offset = @intFromBool(decl.is_pub) });
- if (is_inline) return sema.fail(&block_scope, export_src, "export of inline function", .{});
- // The scope needs to have the decl in it.
- try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index);
+ // Mark the `Cau` as completed before evaluating the export!
+ assert(zcu.analysis_in_progress.swapRemove(anal_unit));
+
+ if (zir_decl.flags.is_export) {
+ const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.flags.is_pub) });
+ const name_slice = zir.nullTerminatedString(zir_decl.name.toString(zir).?);
+ 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();
- return result;
-}
-
-pub fn semaAnonOwnerDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !Zcu.SemaDeclResult {
- const zcu = pt.zcu;
- const decl = zcu.declPtr(decl_index);
+ queue_codegen: {
+ if (!queue_linker_work) break :queue_codegen;
- assert(decl.has_tv);
- assert(decl.owns_tv);
+ // Needed for codegen_nav which will call updateDecl and then the
+ // codegen backend wants full access to the Decl Type.
+ // We also need this for the `isFnOrHasRuntimeBits` check below.
+ // TODO: we could make the language more lenient by deferring this work
+ // to the `codegen_nav` job.
+ try decl_ty.resolveFully(pt);
- log.debug("semaAnonOwnerDecl '{d}'", .{@intFromEnum(decl_index)});
+ if (!decl_ty.isFnOrHasRuntimeBits(pt)) break :queue_codegen;
- switch (decl.typeOf(zcu).zigTypeTag(zcu)) {
- .Fn => @panic("TODO: update fn instance"),
- .Type => {},
- else => unreachable,
+ try zcu.comp.queueJob(.{ .codegen_nav = nav_index });
}
- // We are the owner Decl of a type, and we were marked as outdated. That means the *structure*
- // of this type changed; not just its namespace. Therefore, we need a new InternPool index.
- //
- // However, as soon as we make that, the context that created us will require re-analysis anyway
- // (as it depends on this Decl's value), meaning the `struct_decl` (or equivalent) instruction
- // will be analyzed again. Since Sema already needs to be able to reconstruct types like this,
- // why should we bother implementing it here too when the Sema logic will be hit right after?
- //
- // So instead, let's just mark this Decl as failed - so that any remaining Decls which genuinely
- // reference it (via `@This`) end up silently erroring too - and we'll let Sema make a new type
- // with a new Decl.
- //
- // Yes, this does mean that any type owner Decl has a constant value for its entire lifetime.
- zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
- zcu.intern_pool.remove(pt.tid, decl.val.toIntern());
- decl.analysis = .dependency_failure;
- return .{
- .invalidate_decl_val = true,
- .invalidate_decl_ref = true,
- };
+ 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 {
@@ -1426,7 +1390,7 @@ pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult {
const file_index = try ip.createFile(gpa, pt.tid, .{
.bin_digest = path_digest,
.file = builtin_file,
- .root_decl = .none,
+ .root_type = .none,
});
keep_resolved_path = true; // It's now owned by import_table.
gop.value_ptr.* = file_index;
@@ -1453,7 +1417,7 @@ pub fn importPkg(pt: Zcu.PerThread, mod: *Module) !Zcu.ImportFileResult {
const new_file_index = try ip.createFile(gpa, pt.tid, .{
.bin_digest = path_digest,
.file = new_file,
- .root_decl = .none,
+ .root_type = .none,
});
keep_resolved_path = true; // It's now owned by import_table.
gop.value_ptr.* = new_file_index;
@@ -1563,7 +1527,7 @@ pub fn importFile(
const new_file_index = try ip.createFile(gpa, pt.tid, .{
.bin_digest = path_digest,
.file = new_file,
- .root_decl = .none,
+ .root_type = .none,
});
keep_resolved_path = true; // It's now owned by import_table.
gop.value_ptr.* = new_file_index;
@@ -1726,7 +1690,7 @@ fn newEmbedFile(
})).toIntern();
const ptr_val = try pt.intern(.{ .ptr = .{
.ty = ptr_ty,
- .base_addr = .{ .anon_decl = .{
+ .base_addr = .{ .uav = .{
.val = array_val,
.orig_ty = ptr_ty,
} },
@@ -1748,39 +1712,70 @@ pub fn scanNamespace(
pt: Zcu.PerThread,
namespace_index: Zcu.Namespace.Index,
decls: []const Zir.Inst.Index,
- parent_decl: *Zcu.Decl,
) Allocator.Error!void {
const tracy = trace(@src());
defer tracy.end();
const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
const gpa = zcu.gpa;
const namespace = zcu.namespacePtr(namespace_index);
// 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`.
- var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Zcu.Decl.Index) = .{};
+ // We map to the `Cau`, since not every declaration has a `Nav`.
+ var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index) = .{};
defer existing_by_inst.deinit(gpa);
- try existing_by_inst.ensureTotalCapacity(gpa, @intCast(namespace.decls.count()));
-
- for (namespace.decls.keys()) |decl_index| {
- const decl = zcu.declPtr(decl_index);
- existing_by_inst.putAssumeCapacityNoClobber(decl.zir_decl_index.unwrap().?, decl_index);
+ 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,
+ ));
+
+ 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);
+ }
+ 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);
+ }
+ 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);
+ }
+ 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),
+ }
}
var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
defer seen_decls.deinit(gpa);
- namespace.decls.clearRetainingCapacity();
- try namespace.decls.ensureTotalCapacity(gpa, decls.len);
-
- namespace.usingnamespace_set.clearRetainingCapacity();
+ namespace.pub_decls.clearRetainingCapacity();
+ namespace.priv_decls.clearRetainingCapacity();
+ namespace.pub_usingnamespace.clearRetainingCapacity();
+ namespace.priv_usingnamespace.clearRetainingCapacity();
+ namespace.other_decls.clearRetainingCapacity();
var scan_decl_iter: ScanDeclIter = .{
.pt = pt,
.namespace_index = namespace_index,
- .parent_decl = parent_decl,
.seen_decls = &seen_decls,
.existing_by_inst = &existing_by_inst,
.pass = .named,
@@ -1792,34 +1787,17 @@ pub fn scanNamespace(
for (decls) |decl_inst| {
try scan_decl_iter.scanDecl(decl_inst);
}
-
- if (seen_decls.count() != namespace.decls.count()) {
- // Do a pass over the namespace contents and remove any decls from the last update
- // which were removed in this one.
- var i: usize = 0;
- while (i < namespace.decls.count()) {
- const decl_index = namespace.decls.keys()[i];
- const decl = zcu.declPtr(decl_index);
- if (!seen_decls.contains(decl.name)) {
- // We must preserve namespace ordering for @typeInfo.
- namespace.decls.orderedRemoveAt(i);
- i -= 1;
- }
- }
- }
}
const ScanDeclIter = struct {
pt: Zcu.PerThread,
namespace_index: Zcu.Namespace.Index,
- parent_decl: *Zcu.Decl,
seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
- existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, Zcu.Decl.Index),
+ existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index),
/// 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 },
usingnamespace_index: usize = 0,
- comptime_index: usize = 0,
unnamed_test_index: usize = 0,
fn avoidNameConflict(iter: *ScanDeclIter, comptime fmt: []const u8, args: anytype) !InternPool.NullTerminatedString {
@@ -1843,37 +1821,35 @@ const ScanDeclIter = struct {
const pt = iter.pt;
const zcu = pt.zcu;
+ const comp = zcu.comp;
const namespace_index = iter.namespace_index;
const namespace = zcu.namespacePtr(namespace_index);
const gpa = zcu.gpa;
- const zir = namespace.fileScope(zcu).zir;
+ const file = namespace.fileScope(zcu);
+ const zir = file.zir;
const ip = &zcu.intern_pool;
const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration;
const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index);
const declaration = extra.data;
- // Every Decl needs a name.
- const decl_name: InternPool.NullTerminatedString, const kind: Zcu.Decl.Kind, const is_named_test: bool = switch (declaration.name) {
+ const Kind = enum { @"comptime", @"usingnamespace", @"test", named };
+
+ const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (declaration.name) {
.@"comptime" => info: {
if (iter.pass != .unnamed) return;
- const i = iter.comptime_index;
- iter.comptime_index += 1;
break :info .{
- try iter.avoidNameConflict("comptime_{d}", .{i}),
+ .none,
.@"comptime",
false,
};
},
.@"usingnamespace" => info: {
- // TODO: this isn't right! These should be considered unnamed. Name conflicts can happen here.
- // The problem is, we need to preserve the decl ordering for `@typeInfo`.
- // I'm not bothering to fix this now, since some upcoming changes will change this code significantly anyway.
- if (iter.pass != .named) return;
+ if (iter.pass != .unnamed) return;
const i = iter.usingnamespace_index;
iter.usingnamespace_index += 1;
break :info .{
- try iter.avoidNameConflict("usingnamespace_{d}", .{i}),
+ (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(),
.@"usingnamespace",
false,
};
@@ -1883,7 +1859,7 @@ const ScanDeclIter = struct {
const i = iter.unnamed_test_index;
iter.unnamed_test_index += 1;
break :info .{
- try iter.avoidNameConflict("test_{d}", .{i}),
+ (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(),
.@"test",
false,
};
@@ -1894,7 +1870,7 @@ const ScanDeclIter = struct {
assert(declaration.flags.has_doc_comment);
const name = zir.nullTerminatedString(@enumFromInt(zir.extra[extra.end]));
break :info .{
- try iter.avoidNameConflict("decltest.{s}", .{name}),
+ (try iter.avoidNameConflict("decltest.{s}", .{name})).toOptional(),
.@"test",
true,
};
@@ -1903,7 +1879,7 @@ const ScanDeclIter = struct {
// We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary.
if (iter.pass != .unnamed) return;
break :info .{
- try iter.avoidNameConflict("test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)}),
+ (try iter.avoidNameConflict("test.{s}", .{zir.nullTerminatedString(declaration.name.toString(zir).?)})).toOptional(),
.@"test",
true,
};
@@ -1917,132 +1893,144 @@ const ScanDeclIter = struct {
);
try iter.seen_decls.putNoClobber(gpa, name, {});
break :info .{
- name,
+ name.toOptional(),
.named,
false,
};
},
};
- switch (kind) {
- .@"usingnamespace" => try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1),
- .@"test" => try zcu.test_functions.ensureUnusedCapacity(gpa, 1),
- else => {},
- }
-
- const parent_file_scope_index = iter.parent_decl.getFileScopeIndex(zcu);
const tracked_inst = try ip.trackZir(gpa, pt.tid, .{
- .file = parent_file_scope_index,
+ .file = namespace.file_scope,
.inst = decl_inst,
});
- // We create a Decl for it regardless of analysis status.
-
- const prev_exported, const decl_index = if (iter.existing_by_inst.get(tracked_inst)) |decl_index| decl_index: {
- // We need only update this existing Decl.
- const decl = zcu.declPtr(decl_index);
- const was_exported = decl.is_exported;
- assert(decl.kind == kind); // ZIR tracking should preserve this
- decl.name = decl_name;
- decl.fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, decl_name);
- decl.is_pub = declaration.flags.is_pub;
- decl.is_exported = declaration.flags.is_export;
- break :decl_index .{ was_exported, decl_index };
- } else decl_index: {
- // Create and set up a new Decl.
- const new_decl_index = try pt.allocateNewDecl(namespace_index);
- const new_decl = zcu.declPtr(new_decl_index);
- new_decl.kind = kind;
- new_decl.name = decl_name;
- new_decl.fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, decl_name);
- new_decl.is_pub = declaration.flags.is_pub;
- new_decl.is_exported = declaration.flags.is_export;
- new_decl.zir_decl_index = tracked_inst.toOptional();
- break :decl_index .{ false, new_decl_index };
- };
-
- const decl = zcu.declPtr(decl_index);
-
- namespace.decls.putAssumeCapacityNoClobberContext(decl_index, {}, .{ .zcu = zcu });
+ const existing_cau = 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);
+
+ // For a `comptime` declaration, whether to re-analyze is based solely on whether the
+ // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already.
+ const unit = InternPool.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, {});
+ }
- const comp = zcu.comp;
- const decl_mod = namespace.fileScope(zcu).mod;
- const want_analysis = declaration.flags.is_export or switch (kind) {
- .anon => unreachable,
- .@"comptime" => true,
- .@"usingnamespace" => a: {
- namespace.usingnamespace_set.putAssumeCapacityNoClobber(decl_index, declaration.flags.is_pub);
- break :a true;
+ break :cau .{ cau, true };
},
- .named => false,
- .@"test" => a: {
- if (!comp.config.is_test) break :a false;
- if (decl_mod != zcu.main_mod) break :a false;
- if (is_named_test and comp.test_filters.len > 0) {
- const decl_fqn = decl.fqn.toSlice(ip);
- for (comp.test_filters) |test_filter| {
- if (std.mem.indexOf(u8, decl_fqn, test_filter)) |_| break;
- } else break :a false;
- }
- zcu.test_functions.putAssumeCapacity(decl_index, {}); // may clobber on incremental update
- break :a true;
+ else => cau: {
+ 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) {
+ .@"comptime" => unreachable,
+ .@"usingnamespace" => a: {
+ if (declaration.flags.is_pub) {
+ try namespace.pub_usingnamespace.append(gpa, nav);
+ } else {
+ try namespace.priv_usingnamespace.append(gpa, nav);
+ }
+ break :a true;
+ },
+ .@"test" => a: {
+ try namespace.other_decls.append(gpa, cau);
+ // TODO: incremental compilation!
+ // * remove from `test_functions` if no longer matching filter
+ // * add to `test_functions` if newly passing filter
+ // This logic is unaware of incremental: we'll end up with duplicates.
+ // 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) {
+ const fqn_slice = fqn.toSlice(ip);
+ for (comp.test_filters) |test_filter| {
+ if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break;
+ } else break :a false;
+ }
+ try zcu.test_functions.put(gpa, nav, {});
+ break :a true;
+ },
+ .named => a: {
+ if (declaration.flags.is_pub) {
+ try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
+ } else {
+ try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
+ }
+ break :a false;
+ },
+ };
+ break :cau .{ cau, want_analysis };
},
};
- if (want_analysis) {
- // We will not queue analysis if the decl has been analyzed on a previous update and
- // `is_export` is unchanged. In this case, the incremental update mechanism will handle
- // re-analysis for us if necessary.
- if (prev_exported != declaration.flags.is_export or decl.analysis == .unreferenced) {
- log.debug("scanDecl queue analyze_decl file='{s}' decl_name='{}' decl_index={d}", .{
- namespace.fileScope(zcu).sub_file_path, decl_name.fmt(ip), decl_index,
- });
- try comp.queueJob(.{ .analyze_decl = decl_index });
- }
+ if (want_analysis or declaration.flags.is_export) {
+ log.debug(
+ "scanDecl queue analyze_cau file='{s}' cau_index={d}",
+ .{ namespace.fileScope(zcu).sub_file_path, cau },
+ );
+ try comp.queueJob(.{ .analyze_cau = cau });
}
- if (decl.getOwnedFunction(zcu) != null) {
- // TODO this logic is insufficient; namespaces we don't re-scan may still require
- // updated line numbers. Look into this!
- // TODO Look into detecting when this would be unnecessary by storing enough state
- // in `Decl` to notice that the line number did not change.
- try comp.queueJob(.{ .update_line_number = decl_index });
- }
+ // TODO: we used to do line number updates here, but this is an inappropriate place for this logic to live.
}
};
-/// Cancel the creation of an anon decl and delete any references to it.
-/// If other decls depend on this decl, they must be aborted first.
-pub fn abortAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) void {
- assert(!pt.zcu.declIsRoot(decl_index));
- pt.destroyDecl(decl_index);
-}
-
-/// Finalize the creation of an anon decl.
-pub fn finalizeAnonDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) Allocator.Error!void {
- if (pt.zcu.declPtr(decl_index).typeOf(pt.zcu).isFnOrHasRuntimeBits(pt)) {
- try pt.zcu.comp.queueJob(.{ .codegen_decl = decl_index });
- }
-}
-
-pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: Allocator) Zcu.SemaError!Air {
+fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air {
const tracy = trace(@src());
defer tracy.end();
- const mod = pt.zcu;
- const gpa = mod.gpa;
- const ip = &mod.intern_pool;
- const func = mod.funcInfo(func_index);
- const decl_index = func.owner_decl;
- const decl = mod.declPtr(decl_index);
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+ const ip = &zcu.intern_pool;
+
+ const anal_unit = InternPool.AnalUnit.wrap(.{ .func = func_index });
+ const func = zcu.funcInfo(func_index);
+ const inst_info = func.zir_body_inst.resolveFull(ip);
+ const file = zcu.fileByIndex(inst_info.file);
+ const zir = file.zir;
+
+ try zcu.analysis_in_progress.put(gpa, anal_unit, {});
+ errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
+
+ func.setAnalysisState(ip, .analyzed);
+
+ // 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().?;
+ });
- log.debug("func name '{}'", .{decl.fqn.fmt(ip)});
- defer log.debug("finish func name '{}'", .{decl.fqn.fmt(ip)});
+ const func_nav = ip.getNav(func.owner_nav);
- const decl_prog_node = mod.sema_prog_node.start(decl.fqn.toSlice(ip), 0);
+ const decl_prog_node = zcu.sema_prog_node.start(func_nav.fqn.toSlice(ip), 0);
defer decl_prog_node.end();
- mod.intern_pool.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index }));
+ zcu.intern_pool.removeDependenciesForDepender(gpa, 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();
@@ -2052,21 +2040,19 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
// the runtime-known parameters only, not to be confused with the
// generic_owner function type, which potentially has more parameters,
// including comptime parameters.
- const fn_ty = decl.typeOf(mod);
- const fn_ty_info = mod.typeToFunc(fn_ty).?;
+ const fn_ty = Type.fromInterned(func.ty);
+ const fn_ty_info = zcu.typeToFunc(fn_ty).?;
var sema: Sema = .{
.pt = pt,
.gpa = gpa,
- .arena = arena,
- .code = decl.getFileScope(mod).zir,
- .owner_decl = decl,
- .owner_decl_index = decl_index,
+ .arena = analysis_arena.allocator(),
+ .code = zir,
+ .owner = anal_unit,
.func_index = func_index,
.func_is_naked = fn_ty_info.cc == .Naked,
.fn_ret_ty = Type.fromInterned(fn_ty_info.return_type),
.fn_ret_ty_ies = null,
- .owner_func_index = func_index,
.branch_quota = @max(func.branchQuotaUnordered(ip), Sema.default_branch_quota),
.comptime_err_ret_trace = &comptime_err_ret_trace,
};
@@ -2074,11 +2060,11 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
// 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.zir_decl_index.unwrap().? });
- try sema.declareDependency(.{ .decl_val = decl_index });
+ try sema.declareDependency(.{ .src_hash = decl_cau.zir_index });
+ try sema.declareDependency(.{ .nav_val = func.owner_nav });
if (func.analysisUnordered(ip).inferred_error_set) {
- const ies = try arena.create(Sema.InferredErrorSet);
+ const ies = try analysis_arena.allocator().create(Sema.InferredErrorSet);
ies.* = .{ .func = func_index };
sema.fn_ret_ty_ies = ies;
}
@@ -2094,19 +2080,12 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
var inner_block: Sema.Block = .{
.parent = null,
.sema = &sema,
- .namespace = decl.src_namespace,
+ .namespace = decl_cau.namespace,
.instructions = .{},
.inlining = null,
.is_comptime = false,
- .src_base_inst = inst: {
- const owner_info = if (func.generic_owner == .none)
- func
- else
- mod.funcInfo(func.generic_owner);
- const orig_decl = mod.declPtr(owner_info.owner_decl);
- break :inst orig_decl.zir_decl_index.unwrap().?;
- },
- .type_name_ctx = decl.name,
+ .src_base_inst = decl_cau.zir_index,
+ .type_name_ctx = func_nav.fqn,
};
defer inner_block.instructions.deinit(gpa);
@@ -2144,10 +2123,10 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
const gop = sema.inst_map.getOrPutAssumeCapacity(inst);
if (gop.found_existing) continue; // provided above by comptime arg
- const inst_info = sema.code.instructions.get(@intFromEnum(inst));
- const param_name: Zir.NullTerminatedString = switch (inst_info.tag) {
- .param_anytype => inst_info.data.str_tok.start,
- .param => sema.code.extraData(Zir.Inst.Param, inst_info.data.pl_tok.payload_index).data.name,
+ const param_inst_info = sema.code.instructions.get(@intFromEnum(inst));
+ const param_name: Zir.NullTerminatedString = switch (param_inst_info.tag) {
+ .param_anytype => param_inst_info.data.str_tok.start,
+ .param => sema.code.extraData(Zir.Inst.Param, param_inst_info.data.pl_tok.payload_index).data.name,
else => unreachable,
};
@@ -2179,8 +2158,6 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
});
}
- func.setAnalysisState(ip, .in_progress);
-
const last_arg_index = inner_block.instructions.items.len;
// Save the error trace as our first action in the function.
@@ -2190,9 +2167,8 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
inner_block.error_return_trace_index = error_return_trace_index;
sema.analyzeFnBody(&inner_block, fn_info.body) catch |err| switch (err) {
- // TODO make these unreachable instead of @panic
- error.GenericPoison => @panic("zig compiler bug: GenericPoison"),
- error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"),
+ error.GenericPoison => unreachable,
+ error.ComptimeReturn => unreachable,
else => |e| return e,
};
@@ -2207,14 +2183,13 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
// If we don't get an error return trace from a caller, create our own.
if (func.analysisUnordered(ip).calls_or_awaits_errorable_fn and
- mod.comp.config.any_error_tracing and
- !sema.fn_ret_ty.isError(mod))
+ zcu.comp.config.any_error_tracing and
+ !sema.fn_ret_ty.isError(zcu))
{
sema.setupErrorReturnTrace(&inner_block, last_arg_index) catch |err| switch (err) {
- // TODO make these unreachable instead of @panic
- error.GenericPoison => @panic("zig compiler bug: GenericPoison"),
- error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"),
- error.ComptimeBreak => @panic("zig compiler bug: ComptimeBreak"),
+ error.GenericPoison => unreachable,
+ error.ComptimeReturn => unreachable,
+ error.ComptimeBreak => unreachable,
else => |e| return e,
};
}
@@ -2239,35 +2214,25 @@ pub fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index, arena: All
error.GenericPoison => unreachable,
error.ComptimeReturn => unreachable,
error.ComptimeBreak => unreachable,
- error.AnalysisFail => {
- // In this case our function depends on a type that had a compile error.
- // We should not try to lower this function.
- decl.analysis = .dependency_failure;
- return error.AnalysisFail;
- },
else => |e| return e,
};
assert(ies.resolved != .none);
ip.funcSetIesResolved(func_index, ies.resolved);
}
- func.setAnalysisState(ip, .success);
+ assert(zcu.analysis_in_progress.swapRemove(anal_unit));
// Finally we must resolve the return type and parameter types so that backends
// have full access to type information.
// Crucially, this happens *after* we set the function state to success above,
// so that dependencies on the function body will now be satisfied rather than
// result in circular dependency errors.
+ // TODO: this can go away once we fix backends having to resolve `StackTrace`.
+ // The codegen timing guarantees that the parameter types will be populated.
sema.resolveFnTypes(fn_ty) catch |err| switch (err) {
error.GenericPoison => unreachable,
error.ComptimeReturn => unreachable,
error.ComptimeBreak => unreachable,
- error.AnalysisFail => {
- // In this case our function depends on a type that had a compile error.
- // We should not try to lower this function.
- decl.analysis = .dependency_failure;
- return error.AnalysisFail;
- },
else => |e| return e,
};
@@ -2287,36 +2252,6 @@ pub fn destroyNamespace(pt: Zcu.PerThread, namespace_index: Zcu.Namespace.Index)
return pt.zcu.intern_pool.destroyNamespace(pt.tid, namespace_index);
}
-pub fn allocateNewDecl(pt: Zcu.PerThread, namespace: Zcu.Namespace.Index) !Zcu.Decl.Index {
- const zcu = pt.zcu;
- const gpa = zcu.gpa;
- const decl_index = try zcu.intern_pool.createDecl(gpa, pt.tid, .{
- .name = undefined,
- .fqn = undefined,
- .src_namespace = namespace,
- .has_tv = false,
- .owns_tv = false,
- .val = undefined,
- .alignment = undefined,
- .@"linksection" = .none,
- .@"addrspace" = .generic,
- .analysis = .unreferenced,
- .zir_decl_index = .none,
- .is_pub = false,
- .is_exported = false,
- .kind = .anon,
- });
-
- if (zcu.emit_h) |zcu_emit_h| {
- if (@intFromEnum(decl_index) >= zcu_emit_h.allocated_emit_h.len) {
- try zcu_emit_h.allocated_emit_h.append(gpa, .{});
- assert(@intFromEnum(decl_index) == zcu_emit_h.allocated_emit_h.len);
- }
- }
-
- return decl_index;
-}
-
pub fn getErrorValue(
pt: Zcu.PerThread,
name: InternPool.NullTerminatedString,
@@ -2328,25 +2263,6 @@ pub fn getErrorValueFromSlice(pt: Zcu.PerThread, name: []const u8) Allocator.Err
return pt.getErrorValue(try pt.zcu.intern_pool.getOrPutString(pt.zcu.gpa, name));
}
-pub fn initNewAnonDecl(
- pt: Zcu.PerThread,
- new_decl_index: Zcu.Decl.Index,
- val: Value,
- name: InternPool.NullTerminatedString,
- fqn: InternPool.OptionalNullTerminatedString,
-) Allocator.Error!void {
- const new_decl = pt.zcu.declPtr(new_decl_index);
-
- new_decl.name = name;
- new_decl.fqn = fqn.unwrap() orelse try pt.zcu.namespacePtr(new_decl.src_namespace)
- .internFullyQualifiedName(&pt.zcu.intern_pool, pt.zcu.gpa, pt.tid, name);
- new_decl.val = val;
- new_decl.alignment = .none;
- new_decl.@"linksection" = .none;
- new_decl.has_tv = true;
- new_decl.analysis = .complete;
-}
-
fn lockAndClearFileCompileError(pt: Zcu.PerThread, file: *Zcu.File) void {
switch (file.status) {
.success_zir, .retryable_failure => {},
@@ -2367,35 +2283,35 @@ pub fn processExports(pt: Zcu.PerThread) !void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
- // First, construct a mapping of every exported value and Decl to the indices of all its different exports.
- var decl_exports: std.AutoArrayHashMapUnmanaged(Zcu.Decl.Index, std.ArrayListUnmanaged(u32)) = .{};
- var value_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(u32)) = .{};
+ // First, construct a mapping of every exported value and Nav to the indices of all its different exports.
+ var nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, std.ArrayListUnmanaged(u32)) = .{};
+ var uav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(u32)) = .{};
defer {
- for (decl_exports.values()) |*exports| {
+ for (nav_exports.values()) |*exports| {
exports.deinit(gpa);
}
- decl_exports.deinit(gpa);
- for (value_exports.values()) |*exports| {
+ nav_exports.deinit(gpa);
+ for (uav_exports.values()) |*exports| {
exports.deinit(gpa);
}
- value_exports.deinit(gpa);
+ uav_exports.deinit(gpa);
}
// We note as a heuristic:
// * It is rare to export a value.
- // * It is rare for one Decl to be exported multiple times.
+ // * It is rare for one Nav to be exported multiple times.
// So, this ensureTotalCapacity serves as a reasonable (albeit very approximate) optimization.
- try decl_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count());
+ try nav_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count());
for (zcu.single_exports.values()) |export_idx| {
const exp = zcu.all_exports.items[export_idx];
const value_ptr, const found_existing = switch (exp.exported) {
- .decl_index => |i| gop: {
- const gop = try decl_exports.getOrPut(gpa, i);
+ .nav => |nav| gop: {
+ const gop = try nav_exports.getOrPut(gpa, nav);
break :gop .{ gop.value_ptr, gop.found_existing };
},
- .value => |i| gop: {
- const gop = try value_exports.getOrPut(gpa, i);
+ .uav => |uav| gop: {
+ const gop = try uav_exports.getOrPut(gpa, uav);
break :gop .{ gop.value_ptr, gop.found_existing };
},
};
@@ -2406,12 +2322,12 @@ pub fn processExports(pt: Zcu.PerThread) !void {
for (zcu.multi_exports.values()) |info| {
for (zcu.all_exports.items[info.index..][0..info.len], info.index..) |exp, export_idx| {
const value_ptr, const found_existing = switch (exp.exported) {
- .decl_index => |i| gop: {
- const gop = try decl_exports.getOrPut(gpa, i);
+ .nav => |nav| gop: {
+ const gop = try nav_exports.getOrPut(gpa, nav);
break :gop .{ gop.value_ptr, gop.found_existing };
},
- .value => |i| gop: {
- const gop = try value_exports.getOrPut(gpa, i);
+ .uav => |uav| gop: {
+ const gop = try uav_exports.getOrPut(gpa, uav);
break :gop .{ gop.value_ptr, gop.found_existing };
},
};
@@ -2424,13 +2340,13 @@ pub fn processExports(pt: Zcu.PerThread) !void {
var symbol_exports: SymbolExports = .{};
defer symbol_exports.deinit(gpa);
- for (decl_exports.keys(), decl_exports.values()) |exported_decl, exports_list| {
- const exported: Zcu.Exported = .{ .decl_index = exported_decl };
+ for (nav_exports.keys(), nav_exports.values()) |exported_nav, exports_list| {
+ const exported: Zcu.Exported = .{ .nav = exported_nav };
try pt.processExportsInner(&symbol_exports, exported, exports_list.items);
}
- for (value_exports.keys(), value_exports.values()) |exported_value, exports_list| {
- const exported: Zcu.Exported = .{ .value = exported_value };
+ for (uav_exports.keys(), uav_exports.values()) |exported_uav, exports_list| {
+ const exported: Zcu.Exported = .{ .uav = exported_uav };
try pt.processExportsInner(&symbol_exports, exported, exports_list.items);
}
}
@@ -2467,20 +2383,31 @@ fn processExportsInner(
}
switch (exported) {
- .decl_index => |idx| if (failed: {
- const decl = zcu.declPtr(idx);
- if (decl.analysis != .complete) break :failed true;
- // Check if has owned function
- if (!decl.owns_tv) break :failed false;
- if (decl.typeOf(zcu).zigTypeTag(zcu) != .Fn) break :failed false;
- // Check if owned function failed
- break :failed zcu.funcInfo(decl.val.toIntern()).analysisUnordered(ip).state != .success;
+ .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 = InternPool.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;
+ }
+ const val = switch (nav.status) {
+ .unresolved => break :failed true,
+ .resolved => |r| Value.fromInterned(r.val),
+ };
+ // If the value is a function, we also need to check if that function succeeded analysis.
+ if (val.typeOf(zcu).zigTypeTag(zcu) == .Fn) {
+ const func_unit = InternPool.AnalUnit.wrap(.{ .func = val.toIntern() });
+ if (zcu.failed_analysis.contains(func_unit)) break :failed true;
+ if (zcu.transitive_failed_analysis.contains(func_unit)) break :failed true;
+ }
+ break :failed false;
}) {
// This `Decl` is failed, so was never sent to codegen.
// TODO: we should probably tell the backend to delete any old exports of this `Decl`?
return;
},
- .value => {},
+ .uav => {},
}
if (zcu.comp.bin_file) |lf| {
@@ -2499,46 +2426,49 @@ pub fn populateTestFunctions(
const ip = &zcu.intern_pool;
const builtin_mod = zcu.root_mod.getBuiltinDependency();
const builtin_file_index = (pt.importPkg(builtin_mod) catch unreachable).file_index;
- const root_decl_index = zcu.fileRootDecl(builtin_file_index);
- const root_decl = zcu.declPtr(root_decl_index.unwrap().?);
- const builtin_namespace = zcu.namespacePtr(root_decl.src_namespace);
- const test_functions_str = try ip.getOrPutString(gpa, pt.tid, "test_functions", .no_embedded_nulls);
- const decl_index = builtin_namespace.decls.getKeyAdapted(
- test_functions_str,
- Zcu.DeclAdapter{ .zcu = zcu },
+ pt.ensureFileAnalyzed(builtin_file_index) catch |err| switch (err) {
+ error.AnalysisFail => unreachable, // builtin module is generated so cannot be corrupt
+ error.OutOfMemory => |e| return e,
+ };
+ const builtin_root_type = Type.fromInterned(zcu.fileRootType(builtin_file_index));
+ const builtin_namespace = builtin_root_type.getNamespace(zcu).unwrap().?;
+ const nav_index = zcu.namespacePtr(builtin_namespace).pub_decls.getKeyAdapted(
+ try ip.getOrPutString(gpa, pt.tid, "test_functions", .no_embedded_nulls),
+ Zcu.Namespace.NameAdapter{ .zcu = zcu },
).?;
{
- // We have to call `ensureDeclAnalyzed` here in case `builtin.test_functions`
+ // We have to call `ensureCauAnalyzed` 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;
}
- try pt.ensureDeclAnalyzed(decl_index);
+ const cau_index = ip.getNav(nav_index).analysis_owner.unwrap().?;
+ try pt.ensureCauAnalyzed(cau_index);
}
- const decl = zcu.declPtr(decl_index);
- const test_fn_ty = decl.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu);
+ const test_fns_val = zcu.navValue(nav_index);
+ const test_fn_ty = test_fns_val.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu);
- const array_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = array: {
+ const array_anon_decl: InternPool.Key.Ptr.BaseAddr.Uav = array: {
// Add zcu.test_functions to an array decl then make the test_functions
// decl reference it as a slice.
const test_fn_vals = try gpa.alloc(InternPool.Index, zcu.test_functions.count());
defer gpa.free(test_fn_vals);
- for (test_fn_vals, zcu.test_functions.keys()) |*test_fn_val, test_decl_index| {
- const test_decl = zcu.declPtr(test_decl_index);
- const test_decl_name = test_decl.fqn;
- const test_decl_name_len = test_decl_name.length(ip);
- const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = n: {
+ for (test_fn_vals, zcu.test_functions.keys()) |*test_fn_val, test_nav_index| {
+ const test_nav = ip.getNav(test_nav_index);
+ const test_nav_name = test_nav.fqn;
+ const test_nav_name_len = test_nav_name.length(ip);
+ const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.Uav = n: {
const test_name_ty = try pt.arrayType(.{
- .len = test_decl_name_len,
+ .len = test_nav_name_len,
.child = .u8_type,
});
const test_name_val = try pt.intern(.{ .aggregate = .{
.ty = test_name_ty.toIntern(),
- .storage = .{ .bytes = test_decl_name.toString() },
+ .storage = .{ .bytes = test_nav_name.toString() },
} });
break :n .{
.orig_ty = (try pt.singleConstPtrType(test_name_ty)).toIntern(),
@@ -2552,23 +2482,18 @@ pub fn populateTestFunctions(
.ty = .slice_const_u8_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_type,
- .base_addr = .{ .anon_decl = test_name_anon_decl },
+ .base_addr = .{ .uav = test_name_anon_decl },
.byte_offset = 0,
} }),
.len = try pt.intern(.{ .int = .{
.ty = .usize_type,
- .storage = .{ .u64 = test_decl_name_len },
+ .storage = .{ .u64 = test_nav_name_len },
} }),
} }),
// func
try pt.intern(.{ .ptr = .{
- .ty = try pt.intern(.{ .ptr_type = .{
- .child = test_decl.typeOf(zcu).toIntern(),
- .flags = .{
- .is_const = true,
- },
- } }),
- .base_addr = .{ .decl = test_decl_index },
+ .ty = (try pt.navPtrType(test_nav_index)).toIntern(),
+ .base_addr = .{ .nav = test_nav_index },
.byte_offset = 0,
} }),
};
@@ -2601,22 +2526,16 @@ pub fn populateTestFunctions(
.size = .Slice,
},
});
- const new_val = decl.val;
const new_init = try pt.intern(.{ .slice = .{
.ty = new_ty.toIntern(),
.ptr = try pt.intern(.{ .ptr = .{
.ty = new_ty.slicePtrFieldType(zcu).toIntern(),
- .base_addr = .{ .anon_decl = array_anon_decl },
+ .base_addr = .{ .uav = array_anon_decl },
.byte_offset = 0,
} }),
.len = (try pt.intValue(Type.usize, zcu.test_functions.count())).toIntern(),
} });
- ip.mutateVarInit(decl.val.toIntern(), new_init);
-
- // Since we are replacing the Decl's value we must perform cleanup on the
- // previous value.
- decl.val = new_val;
- decl.has_tv = true;
+ ip.mutateVarInit(test_fns_val.toIntern(), new_init);
}
{
zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0);
@@ -2625,40 +2544,45 @@ pub fn populateTestFunctions(
zcu.codegen_prog_node = std.Progress.Node.none;
}
- try pt.linkerUpdateDecl(decl_index);
+ try pt.linkerUpdateNav(nav_index);
}
}
-pub fn linkerUpdateDecl(pt: Zcu.PerThread, decl_index: Zcu.Decl.Index) !void {
+pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !void {
const zcu = pt.zcu;
const comp = zcu.comp;
- const decl = zcu.declPtr(decl_index);
-
- const codegen_prog_node = zcu.codegen_prog_node.start(decl.fqn.toSlice(&zcu.intern_pool), 0);
+ const nav = zcu.intern_pool.getNav(nav_index);
+ const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(&zcu.intern_pool), 0);
defer codegen_prog_node.end();
if (comp.bin_file) |lf| {
- lf.updateDecl(pt, decl_index) catch |err| switch (err) {
+ lf.updateNav(pt, nav_index) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {
- decl.analysis = .codegen_failure;
+ assert(zcu.failed_codegen.contains(nav_index));
},
else => {
const gpa = zcu.gpa;
- try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
- zcu.failed_analysis.putAssumeCapacityNoClobber(InternPool.AnalUnit.wrap(.{ .decl = decl_index }), try Zcu.ErrorMsg.create(
+ try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1);
+ zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, try Zcu.ErrorMsg.create(
gpa,
- decl.navSrcLoc(zcu),
+ zcu.navSrcLoc(nav_index),
"unable to codegen: {s}",
.{@errorName(err)},
));
- decl.analysis = .codegen_failure;
- try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index }));
+ if (nav.analysis_owner.unwrap()) |cau| {
+ try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .cau = cau }));
+ } 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.
+ // But perhaps we can do better...
+ @panic("TODO: retryable failure codegenning non-declaration Nav");
+ }
},
};
} else if (zcu.llvm_object) |llvm_object| {
- llvm_object.updateDecl(pt, decl_index) catch |err| switch (err) {
+ llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
};
}
@@ -2750,9 +2674,30 @@ pub fn intern(pt: Zcu.PerThread, key: InternPool.Key) Allocator.Error!InternPool
return pt.zcu.intern_pool.get(pt.zcu.gpa, pt.tid, key);
}
-/// Shortcut for calling `intern_pool.getCoerced`.
+/// Essentially a shortcut for calling `intern_pool.getCoerced`.
+/// However, this function also allows coercing `extern`s. The `InternPool` function can't do
+/// this because it requires potentially pushing to the job queue.
pub fn getCoerced(pt: Zcu.PerThread, val: Value, new_ty: Type) Allocator.Error!Value {
- return Value.fromInterned(try pt.zcu.intern_pool.getCoerced(pt.zcu.gpa, pt.tid, val.toIntern(), new_ty.toIntern()));
+ const ip = &pt.zcu.intern_pool;
+ switch (ip.indexToKey(val.toIntern())) {
+ .@"extern" => |e| {
+ const coerced = try pt.getExtern(.{
+ .name = e.name,
+ .ty = new_ty.toIntern(),
+ .lib_name = e.lib_name,
+ .is_const = e.is_const,
+ .is_threadlocal = e.is_threadlocal,
+ .is_weak_linkage = e.is_weak_linkage,
+ .alignment = e.alignment,
+ .@"addrspace" = e.@"addrspace",
+ .zir_index = e.zir_index,
+ .owner_nav = undefined, // ignored by `getExtern`.
+ });
+ return Value.fromInterned(coerced);
+ },
+ else => {},
+ }
+ return Value.fromInterned(try ip.getCoerced(pt.zcu.gpa, pt.tid, val.toIntern(), new_ty.toIntern()));
}
pub fn intType(pt: Zcu.PerThread, signedness: std.builtin.Signedness, bits: u16) Allocator.Error!Type {
@@ -3237,24 +3182,28 @@ pub fn structPackedFieldBitOffset(
}
pub fn getBuiltin(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Air.Inst.Ref {
- const decl_index = try pt.getBuiltinDecl(name);
- pt.ensureDeclAnalyzed(decl_index) catch @panic("std.builtin is corrupt");
- return Air.internedToRef(pt.zcu.declPtr(decl_index).val.toIntern());
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+ const nav = try pt.getBuiltinNav(name);
+ pt.ensureCauAnalyzed(ip.getNav(nav).analysis_owner.unwrap().?) catch @panic("std.builtin is corrupt");
+ return Air.internedToRef(ip.getNav(nav).status.resolved.val);
}
-pub fn getBuiltinDecl(pt: Zcu.PerThread, name: []const u8) Allocator.Error!InternPool.DeclIndex {
+pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!InternPool.Nav.Index {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const std_file_imported = pt.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig");
- const std_file_root_decl = zcu.fileRootDecl(std_file_imported.file_index).unwrap().?;
- const std_namespace = zcu.declPtr(std_file_root_decl).getOwnedInnerNamespace(zcu).?;
+ const std_type = Type.fromInterned(zcu.fileRootType(std_file_imported.file_index));
+ const std_namespace = zcu.namespacePtr(std_type.getNamespace(zcu).unwrap().?);
const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
- const builtin_decl = std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'");
- pt.ensureDeclAnalyzed(builtin_decl) catch @panic("std.builtin is corrupt");
- const builtin_namespace = zcu.declPtr(builtin_decl).getInnerNamespace(zcu) orelse @panic("std.builtin is corrupt");
+ 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");
+ 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);
- return builtin_namespace.decls.getKeyAdapted(name_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt");
+ return builtin_namespace.pub_decls.getKeyAdapted(name_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt");
}
pub fn getBuiltinType(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Type {
@@ -3264,6 +3213,47 @@ pub fn getBuiltinType(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Type
return ty;
}
+pub fn navPtrType(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) Allocator.Error!Type {
+ const zcu = pt.zcu;
+ const ip = &zcu.intern_pool;
+ const r = ip.getNav(nav_index).status.resolved;
+ const ty = Value.fromInterned(r.val).typeOf(zcu);
+ return pt.ptrType(.{
+ .child = ty.toIntern(),
+ .flags = .{
+ .alignment = if (r.alignment == ty.abiAlignment(pt))
+ .none
+ else
+ r.alignment,
+ .address_space = r.@"addrspace",
+ .is_const = switch (ip.indexToKey(r.val)) {
+ .variable => false,
+ .@"extern" => |e| e.is_const,
+ else => true,
+ },
+ },
+ });
+}
+
+/// Intern an `.@"extern"`, creating a corresponding owner `Nav` if necessary.
+/// If necessary, the new `Nav` is queued for codegen.
+/// `key.owner_nav` is ignored and may be `undefined`.
+pub fn getExtern(pt: Zcu.PerThread, key: InternPool.Key.Extern) Allocator.Error!InternPool.Index {
+ const result = try pt.zcu.intern_pool.getExtern(pt.zcu.gpa, pt.tid, key);
+ if (result.new_nav.unwrap()) |nav| {
+ try pt.zcu.comp.queueJob(.{ .codegen_nav = nav });
+ }
+ return result.index;
+}
+
+// TODO: this shouldn't need a `PerThread`! Fix the signature of `Type.abiAlignment`.
+pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPool.Alignment {
+ const zcu = pt.zcu;
+ const r = zcu.intern_pool.getNav(nav_index).status.resolved;
+ if (r.alignment != .none) return r.alignment;
+ return Value.fromInterned(r.val).typeOf(zcu).abiAlignment(pt);
+}
+
const Air = @import("../Air.zig");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;