Commit 40aafcd6a8

mlugg <mlugg@mlugg.co.uk>
2024-12-22 22:16:29
compiler: remove Cau
The `Cau` abstraction originated from noting that one of the two primary roles of the legacy `Decl` type was to be the subject of comptime semantic analysis. However, the data stored in `Cau` has always had some level of redundancy. While preparing for #131, I went to remove that redundany, and realised that `Cau` now had exactly one field: `owner`. This led me to conclude that `Cau` is, in fact, an unnecessary level of abstraction over what are in reality *fundamentally different* kinds of analysis unit (`AnalUnit`). Types, `Nav` vals, and `comptime` declarations are all analyzed in different ways, and trying to treat them as the same thing is counterproductive! So, these 3 cases are now different alternatives in `AnalUnit`. To avoid stealing bits from `InternPool`-based IDs, which are already a little starved for bits due to the sharding datastructures, `AnalUnit` is expanded to 64 bits (30 of which are currently unused). This doesn't impact memory usage too much by default, because we don't store `AnalUnit`s all too often; however, we do store them a lot under `-fincremental`, so a non-trivial bump to peak RSS can be observed there. This will be improved in the future when I made `InternPool.DepEntry` less memory-inefficient. `Zcu.PerThread.ensureCauAnalyzed` is split into 3 functions, for each of the 3 new types of `AnalUnit`. The new logic is much easier to understand, because it avoids conflating the logic of these fundamentally different cases.
1 parent 18362eb
src/link/Dwarf.zig
@@ -2261,8 +2261,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In
             assert(file.zir_loaded);
             const decl = file.zir.getDeclaration(inst_info.inst);
 
-            const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: {
-                const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace);
+            const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: {
+                const parent_namespace_ptr = ip.namespacePtr(a.namespace);
                 break :parent .{
                     parent_namespace_ptr.owner_type,
                     if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
@@ -2292,8 +2292,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In
             assert(file.zir_loaded);
             const decl = file.zir.getDeclaration(inst_info.inst);
 
-            const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: {
-                const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace);
+            const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: {
+                const parent_namespace_ptr = ip.namespacePtr(a.namespace);
                 break :parent .{
                     parent_namespace_ptr.owner_type,
                     if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
@@ -2321,8 +2321,8 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In
             assert(file.zir_loaded);
             const decl = file.zir.getDeclaration(inst_info.inst);
 
-            const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: {
-                const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace);
+            const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: {
+                const parent_namespace_ptr = ip.namespacePtr(a.namespace);
                 break :parent .{
                     parent_namespace_ptr.owner_type,
                     if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
@@ -2563,8 +2563,8 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool
         return;
     }
 
-    const parent_type, const accessibility: u8 = if (nav.analysis_owner.unwrap()) |cau| parent: {
-        const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace);
+    const parent_type, const accessibility: u8 = if (nav.analysis) |a| parent: {
+        const parent_namespace_ptr = ip.namespacePtr(a.namespace);
         break :parent .{
             parent_namespace_ptr.owner_type,
             if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
src/Zcu/PerThread.zig
@@ -545,144 +545,173 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
 pub fn ensureFileAnalyzed(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
     const file_root_type = pt.zcu.fileRootType(file_index);
     if (file_root_type != .none) {
-        _ = try pt.ensureTypeUpToDate(file_root_type, false);
+        _ = try pt.ensureTypeUpToDate(file_root_type);
     } else {
         return pt.semaFile(file_index);
     }
 }
 
-/// This ensures that the state of the `Cau`, and of its corresponding `Nav` or type,
-/// is fully up-to-date. Note that the type of the `Nav` may not be fully resolved.
-/// Returns `error.AnalysisFail` if the `Cau` has an error.
-pub fn ensureCauAnalyzed(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) Zcu.SemaError!void {
+/// Ensures that the state of the given `ComptimeUnit` is fully up-to-date, performing re-analysis
+/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is
+/// free to ignore this, since the error is already registered.
+pub fn ensureComptimeUnitUpToDate(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.SemaError!void {
     const tracy = trace(@src());
     defer tracy.end();
 
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
-    const ip = &zcu.intern_pool;
 
-    const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
-    const cau = ip.getCau(cau_index);
+    const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id });
 
-    log.debug("ensureCauAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
+    log.debug("ensureComptimeUnitUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
 
     assert(!zcu.analysis_in_progress.contains(anal_unit));
 
-    // Determine whether or not this Cau is outdated, i.e. requires re-analysis
-    // even if `complete`. If a Cau is PO, we pessismistically assume that it
-    // *does* require re-analysis, to ensure that the Cau is definitely
-    // up-to-date when this function returns.
-
-    // If analysis occurs in a poor order, this could result in over-analysis.
-    // We do our best to avoid this by the other dependency logic in this file
-    // which tries to limit re-analysis to Caus whose previously listed
-    // dependencies are all up-to-date.
+    // Determine whether or not this `ComptimeUnit` is outdated. For this kind of `AnalUnit`, that's
+    // the only indicator as to whether or not analysis is required; when a `ComptimeUnit` is first
+    // created, it's marked as outdated.
+    //
+    // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to
+    // ensure that the unit is definitely up-to-date when this function returns. This mechanism could
+    // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by
+    // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`.
 
-    const cau_outdated = zcu.outdated.swapRemove(anal_unit) or
+    const was_outdated = zcu.outdated.swapRemove(anal_unit) or
         zcu.potentially_outdated.swapRemove(anal_unit);
 
-    const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit);
-
-    if (cau_outdated) {
+    if (was_outdated) {
         _ = zcu.outdated_ready.swapRemove(anal_unit);
-    } else {
-        // We can trust the current information about this `Cau`.
-        if (prev_failed) {
-            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,
+        // `was_outdated` can be true in the initial update for comptime units, so this isn't a `dev.check`.
+        if (dev.env.supports(.incremental)) {
+            zcu.deleteUnitExports(anal_unit);
+            zcu.deleteUnitReferences(anal_unit);
+            if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
+                kv.value.destroy(gpa);
+            }
+            _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
         }
+    } else {
+        // We can trust the current information about this unit.
+        if (zcu.failed_analysis.contains(anal_unit)) return error.AnalysisFail;
+        if (zcu.transitive_failed_analysis.contains(anal_unit)) return error.AnalysisFail;
+        return;
     }
 
-    const sema_result: SemaCauResult, const analysis_fail = if (pt.ensureCauAnalyzedInner(cau_index, cau_outdated)) |result|
-        // This `Cau` has gone from failed to success, so even if the value of the owner `Nav` didn't actually
-        // change, we need to invalidate the dependencies anyway.
-        .{ .{
-            .invalidate_decl_val = result.invalidate_decl_val or prev_failed,
-            .invalidate_decl_ref = result.invalidate_decl_ref or prev_failed,
-        }, false }
-    else |err| switch (err) {
-        error.AnalysisFail => res: {
+    const unit_prog_node = zcu.sema_prog_node.start("comptime", 0);
+    defer unit_prog_node.end();
+
+    return pt.analyzeComptimeUnit(cu_id) catch |err| switch (err) {
+        error.AnalysisFail => {
             if (!zcu.failed_analysis.contains(anal_unit)) {
-                // If this `Cau` caused the error, it would have an entry in `failed_analysis`.
+                // If this unit caused the error, it would have an entry in `failed_analysis`.
                 // Since it does not, this must be a transitive failure.
                 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
                 log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
             }
-            // We consider this `Cau` to be outdated if:
-            // * Previous analysis succeeded; in this case, we need to re-analyze dependants to ensure
-            //   they hit a transitive error here, rather than reporting a different error later (which
-            //   may now be invalid).
-            // * The `Cau` is a type; in this case, the declaration site may require re-analysis to
-            //   construct a valid type.
-            const outdated = !prev_failed or cau.owner.unwrap() == .type;
-            break :res .{ .{
-                .invalidate_decl_val = outdated,
-                .invalidate_decl_ref = outdated,
-            }, true };
+            return error.AnalysisFail;
         },
-        error.OutOfMemory => res: {
-            try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1);
-            try zcu.retryable_failures.ensureUnusedCapacity(gpa, 1);
-            const msg = try Zcu.ErrorMsg.create(
-                gpa,
-                .{ .base_node_inst = cau.zir_index, .offset = Zcu.LazySrcLoc.Offset.nodeOffset(0) },
-                "unable to analyze: OutOfMemory",
-                .{},
-            );
-            zcu.retryable_failures.appendAssumeCapacity(anal_unit);
-            zcu.failed_analysis.putAssumeCapacityNoClobber(anal_unit, msg);
-            break :res .{ .{
-                .invalidate_decl_val = true,
-                .invalidate_decl_ref = true,
-            }, true };
+        error.OutOfMemory => {
+            // TODO: it's unclear how to gracefully handle this.
+            // To report the error cleanly, we need to add a message to `failed_analysis` and a
+            // corresponding entry to `retryable_failures`; but either of these things is quite
+            // likely to OOM at this point.
+            // If that happens, what do we do? Perhaps we could have a special field on `Zcu`
+            // for reporting OOM errors without allocating.
+            return error.OutOfMemory;
         },
+        error.GenericPoison => unreachable,
+        error.ComptimeReturn => unreachable,
+        error.ComptimeBreak => unreachable,
     };
-
-    if (cau_outdated) {
-        // TODO: we do not yet have separate dependencies for decl values vs types.
-        const invalidate = sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref;
-        const dependee: InternPool.Dependee = switch (cau.owner.unwrap()) {
-            .none => return, // there are no dependencies on a `comptime` decl!
-            .nav => |nav_index| .{ .nav_val = nav_index },
-            .type => |ty| .{ .interned = ty },
-        };
-
-        if (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(.marked_po, 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);
-        }
-    }
-
-    if (analysis_fail) return error.AnalysisFail;
 }
 
-fn ensureCauAnalyzedInner(
-    pt: Zcu.PerThread,
-    cau_index: InternPool.Cau.Index,
-    cau_outdated: bool,
-) Zcu.SemaError!SemaCauResult {
+/// Re-analyzes a `ComptimeUnit`. The unit has already been determined to be out-of-date, and old
+/// side effects (exports/references/etc) have been dropped. If semantic analysis fails, this
+/// function will return `error.AnalysisFail`, and it is the caller's reponsibility to add an entry
+/// to `transitive_failed_analysis` if necessary.
+fn analyzeComptimeUnit(pt: Zcu.PerThread, cu_id: InternPool.ComptimeUnit.Id) Zcu.CompileError!void {
     const zcu = pt.zcu;
+    const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
-    const cau = ip.getCau(cau_index);
-    const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
+    const anal_unit: AnalUnit = .wrap(.{ .@"comptime" = cu_id });
+    const comptime_unit = ip.getComptimeUnit(cu_id);
+
+    log.debug("analyzeComptimeUnit {}", .{zcu.fmtAnalUnit(anal_unit)});
+
+    const inst_resolved = comptime_unit.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+    const file = zcu.fileByIndex(inst_resolved.file);
+    // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is
+    // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends
+    // in `ensureComptimeUnitUpToDate`.
+    if (file.status != .success_zir) return error.AnalysisFail;
+    const zir = file.zir;
+
+    // We are about to re-analyze this unit; drop its depenndencies.
+    zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
+
+    try zcu.analysis_in_progress.put(gpa, anal_unit, {});
+    defer assert(zcu.analysis_in_progress.swapRemove(anal_unit));
+
+    var analysis_arena: std.heap.ArenaAllocator = .init(gpa);
+    defer analysis_arena.deinit();
+
+    var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa);
+    defer comptime_err_ret_trace.deinit();
+
+    var sema: Sema = .{
+        .pt = pt,
+        .gpa = gpa,
+        .arena = analysis_arena.allocator(),
+        .code = zir,
+        .owner = anal_unit,
+        .func_index = .none,
+        .func_is_naked = false,
+        .fn_ret_ty = .void,
+        .fn_ret_ty_ies = null,
+        .comptime_err_ret_trace = &comptime_err_ret_trace,
+    };
+    defer sema.deinit();
+
+    // The comptime unit declares on the source of the corresponding `comptime` declaration.
+    try sema.declareDependency(.{ .src_hash = comptime_unit.zir_index });
+
+    var block: Sema.Block = .{
+        .parent = null,
+        .sema = &sema,
+        .namespace = comptime_unit.namespace,
+        .instructions = .{},
+        .inlining = null,
+        .is_comptime = true,
+        .src_base_inst = comptime_unit.zir_index,
+        .type_name_ctx = try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{
+            Type.fromInterned(zcu.namespacePtr(comptime_unit.namespace).owner_type).containerTypeName(ip).fmt(ip),
+        }, .no_embedded_nulls),
+    };
+    defer block.instructions.deinit(gpa);
+
+    const zir_decl = zir.getDeclaration(inst_resolved.inst);
+    assert(zir_decl.kind == .@"comptime");
+    assert(zir_decl.type_body == null);
+    assert(zir_decl.align_body == null);
+    assert(zir_decl.linksection_body == null);
+    assert(zir_decl.addrspace_body == null);
+    const value_body = zir_decl.value_body.?;
+
+    const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
+    assert(result_ref == .void_value); // AstGen should always uphold this
+
+    // Nothing else to do -- for a comptime decl, all we care about are the side effects.
+    // Just make sure to `flushExports`.
+    try sema.flushExports();
+}
 
-    const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+/// Ensures that the resolved value of the given `Nav` is fully up-to-date, performing re-analysis
+/// if necessary. Returns `error.AnalysisFail` if an analysis error is encountered; the caller is
+/// free to ignore this, since the error is already registered.
+pub fn ensureNavValUpToDate(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.SemaError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
     // TODO: document this elsewhere mlugg!
     // For my own benefit, here's how a namespace update for a normal (non-file-root) type works:
@@ -692,821 +721,826 @@ fn ensureCauAnalyzedInner(
     // * 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)
+    //   * the struct's resolution is up-to-date (because nothing about the fields changed)
     // * so, it uses the same `struct`
     // * but this doesn't stop it from updating the namespace!
     //   * we basically do `scanDecls`, updating the namespace as needed
     // * so everyone lived happily ever after
 
-    if (zcu.fileByIndex(inst_info.file).status != .success_zir) {
-        return error.AnalysisFail;
-    }
-
-    // `cau_outdated` can be true in the initial update for `comptime` declarations,
-    // so this isn't a `dev.check`.
-    if (cau_outdated and dev.env.supports(.incremental)) {
-        // The exports this `Cau` performs will be re-discovered, so we remove them here
-        // prior to re-analysis.
-        zcu.deleteUnitExports(anal_unit);
-        zcu.deleteUnitReferences(anal_unit);
-        if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
-            kv.value.destroy(zcu.gpa);
-        }
-        _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
-    }
-
-    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();
-
-    return pt.semaCau(cau_index) catch |err| switch (err) {
-        error.GenericPoison, error.ComptimeBreak, error.ComptimeReturn => unreachable,
-        error.AnalysisFail, error.OutOfMemory => |e| return e,
-    };
-}
-
-pub fn ensureFuncBodyAnalyzed(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void {
-    dev.check(.sema);
-
-    const tracy = trace(@src());
-    defer tracy.end();
-
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
-    // We only care about the uncoerced function.
-    const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
-    const anal_unit = AnalUnit.wrap(.{ .func = func_index });
+    const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id });
+    const nav = ip.getNav(nav_id);
 
-    log.debug("ensureFuncBodyAnalyzed {}", .{zcu.fmtAnalUnit(anal_unit)});
+    log.debug("ensureNavUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
 
-    const func = zcu.funcInfo(maybe_coerced_func_index);
+    // Determine whether or not this `Nav`'s value is outdated. This also includes checking if the
+    // status is `.unresolved`, which indicates that the value is outdated because it has *never*
+    // been analyzed so far.
+    //
+    // Note that if the unit is PO, we pessimistically assume that it *does* require re-analysis, to
+    // ensure that the unit is definitely up-to-date when this function returns. This mechanism could
+    // result in over-analysis if analysis occurs in a poor order; we do our best to avoid this by
+    // carefully choosing which units to re-analyze. See `Zcu.findOutdatedToAnalyze`.
 
-    const func_outdated = zcu.outdated.swapRemove(anal_unit) or
+    const was_outdated = zcu.outdated.swapRemove(anal_unit) or
         zcu.potentially_outdated.swapRemove(anal_unit);
 
-    const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit);
+    const prev_failed = zcu.failed_analysis.contains(anal_unit) or
+        zcu.transitive_failed_analysis.contains(anal_unit);
 
-    if (func_outdated) {
+    if (was_outdated) {
+        dev.check(.incremental);
         _ = zcu.outdated_ready.swapRemove(anal_unit);
-    } else {
-        // We can trust the current information about this function.
-        if (prev_failed) {
-            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
+        zcu.deleteUnitExports(anal_unit);
+        zcu.deleteUnitReferences(anal_unit);
+        if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
+            kv.value.destroy(gpa);
         }
+        _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
+    } else {
+        // We can trust the current information about this unit.
+        if (prev_failed) return error.AnalysisFail;
+        if (nav.status == .resolved) return;
     }
 
-    const ies_outdated, const analysis_fail = if (pt.ensureFuncBodyAnalyzedInner(func_index, func_outdated)) |result|
-        .{ result.ies_outdated, false }
-    else |err| switch (err) {
+    const unit_prog_node = zcu.sema_prog_node.start(nav.fqn.toSlice(ip), 0);
+    defer unit_prog_node.end();
+
+    const sema_result: SemaNavResult, const new_failed: bool = if (pt.analyzeNavVal(nav_id)) |result| res: {
+        break :res .{
+            .{
+                // If the unit has gone from failed to success, we still need to invalidate the dependencies.
+                .invalidate_nav_val = result.invalidate_nav_val or prev_failed,
+                .invalidate_nav_ref = result.invalidate_nav_ref or prev_failed,
+            },
+            false,
+        };
+    } else |err| switch (err) {
         error.AnalysisFail => res: {
             if (!zcu.failed_analysis.contains(anal_unit)) {
-                // If this function caused the error, it would have an entry in `failed_analysis`.
+                // If this unit caused the error, it would have an entry in `failed_analysis`.
                 // Since it does not, this must be a transitive failure.
                 try zcu.transitive_failed_analysis.put(gpa, anal_unit, {});
                 log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
             }
-            // We consider the IES to be outdated if the function previously succeeded analysis; in this case,
-            // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting
-            // a different error later (which may now be invalid).
-            break :res .{ !prev_failed, true };
+            break :res .{ .{
+                .invalidate_nav_val = !prev_failed,
+                .invalidate_nav_ref = !prev_failed,
+            }, true };
         },
-        error.OutOfMemory => return error.OutOfMemory, // TODO: graceful handling like `ensureCauAnalyzed`
+        error.OutOfMemory => {
+            // TODO: it's unclear how to gracefully handle this.
+            // To report the error cleanly, we need to add a message to `failed_analysis` and a
+            // corresponding entry to `retryable_failures`; but either of these things is quite
+            // likely to OOM at this point.
+            // If that happens, what do we do? Perhaps we could have a special field on `Zcu`
+            // for reporting OOM errors without allocating.
+            return error.OutOfMemory;
+        },
+        error.GenericPoison => unreachable,
+        error.ComptimeReturn => unreachable,
+        error.ComptimeBreak => unreachable,
     };
 
-    if (func_outdated) {
-        if (ies_outdated) {
-            try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index });
+    if (was_outdated) {
+        // TODO: we do not yet have separate dependencies for Nav values vs types.
+        const invalidate = sema_result.invalidate_nav_val or sema_result.invalidate_nav_ref;
+        const dependee: InternPool.Dependee = .{ .nav_val = nav_id };
+        if (invalidate) {
+            // This dependency was marked as PO, meaning dependees were waiting
+            // on its analysis result, and it has turned out to be outdated.
+            // Update dependees accordingly.
+            try zcu.markDependeeOutdated(.marked_po, dependee);
         } else {
-            try zcu.markPoDependeeUpToDate(.{ .interned = func_index });
+            // 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);
         }
     }
 
-    if (analysis_fail) return error.AnalysisFail;
+    if (new_failed) return error.AnalysisFail;
 }
 
-fn ensureFuncBodyAnalyzedInner(
-    pt: Zcu.PerThread,
-    func_index: InternPool.Index,
-    func_outdated: bool,
-) Zcu.SemaError!struct { ies_outdated: bool } {
+const SemaNavResult = packed struct {
+    /// Whether the value of a `decl_val` of the corresponding Nav changed.
+    invalidate_nav_val: bool,
+    /// Whether the type of a `decl_ref` of the corresponding Nav changed.
+    invalidate_nav_ref: bool,
+};
+
+fn analyzeNavVal(pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) Zcu.CompileError!SemaNavResult {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
-    const func = zcu.funcInfo(func_index);
-    const anal_unit = AnalUnit.wrap(.{ .func = func_index });
+    const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_id });
+    const old_nav = ip.getNav(nav_id);
 
-    // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing
-    // it would be a waste of time in the best case, and could cause codegen to give bogus
-    // results in the worst case.
+    log.debug("analyzeNavVal {}", .{zcu.fmtAnalUnit(anal_unit)});
 
-    if (func.generic_owner == .none) {
-        // Among another things, this ensures that the function's `zir_body_inst` is correct.
-        try pt.ensureCauAnalyzed(ip.getNav(func.owner_nav).analysis_owner.unwrap().?);
-        if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {
-            // This function is no longer referenced! There's no point in re-analyzing it.
-            // Just mark a transitive failure and move on.
-            return error.AnalysisFail;
-        }
-    } else {
-        const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
-        // Among another things, this ensures that the function's `zir_body_inst` is correct.
-        try pt.ensureCauAnalyzed(ip.getNav(go_nav).analysis_owner.unwrap().?);
-        if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {
-            // The generic owner is no longer referenced, so this function is also unreferenced.
-            // There's no point in re-analyzing it. Just mark a transitive failure and move on.
-            return error.AnalysisFail;
-        }
-    }
+    const inst_resolved = old_nav.analysis.?.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
+    const file = zcu.fileByIndex(inst_resolved.file);
+    // TODO: stop the compiler ever reaching Sema if there are failed files. That way, this check is
+    // unnecessary, and we can move the below `removeDependenciesForDepender` call up with its friends
+    // in `ensureComptimeUnitUpToDate`.
+    if (file.status != .success_zir) return error.AnalysisFail;
+    const zir = file.zir;
 
-    // We'll want to remember what the IES used to be before the update for
-    // dependency invalidation purposes.
-    const old_resolved_ies = if (func.analysisUnordered(ip).inferred_error_set)
-        func.resolvedErrorSetUnordered(ip)
-    else
-        .none;
+    // We are about to re-analyze this unit; drop its depenndencies.
+    zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
 
-    if (func_outdated) {
-        dev.check(.incremental);
-        zcu.deleteUnitExports(anal_unit);
-        zcu.deleteUnitReferences(anal_unit);
-        if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
-            kv.value.destroy(gpa);
-        }
-        _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
-    }
+    try zcu.analysis_in_progress.put(gpa, anal_unit, {});
+    errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
 
-    if (!func_outdated) {
-        // We can trust the current information about this function.
-        if (zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit)) {
-            return error.AnalysisFail;
-        }
-        switch (func.analysisUnordered(ip).state) {
-            .unreferenced => {}, // this is the first reference
-            .queued => {}, // we're waiting on first-time analysis
-            .analyzed => return .{ .ies_outdated = false }, // up-to-date
-        }
-    }
+    var analysis_arena: std.heap.ArenaAllocator = .init(gpa);
+    defer analysis_arena.deinit();
 
-    log.debug("analyze and generate fn body {}; reason='{s}'", .{
-        zcu.fmtAnalUnit(anal_unit),
-        if (func_outdated) "outdated" else "never analyzed",
-    });
+    var comptime_err_ret_trace: std.ArrayList(Zcu.LazySrcLoc) = .init(gpa);
+    defer comptime_err_ret_trace.deinit();
 
-    var air = try pt.analyzeFnBody(func_index);
-    errdefer air.deinit(gpa);
+    var sema: Sema = .{
+        .pt = pt,
+        .gpa = gpa,
+        .arena = analysis_arena.allocator(),
+        .code = zir,
+        .owner = anal_unit,
+        .func_index = .none,
+        .func_is_naked = false,
+        .fn_ret_ty = .void,
+        .fn_ret_ty_ies = null,
+        .comptime_err_ret_trace = &comptime_err_ret_trace,
+    };
+    defer sema.deinit();
 
-    const ies_outdated = func_outdated and
-        (!func.analysisUnordered(ip).inferred_error_set or func.resolvedErrorSetUnordered(ip) != old_resolved_ies);
+    // The comptime unit declares on the source of the corresponding declaration.
+    try sema.declareDependency(.{ .src_hash = old_nav.analysis.?.zir_index });
 
-    const comp = zcu.comp;
+    var block: Sema.Block = .{
+        .parent = null,
+        .sema = &sema,
+        .namespace = old_nav.analysis.?.namespace,
+        .instructions = .{},
+        .inlining = null,
+        .is_comptime = true,
+        .src_base_inst = old_nav.analysis.?.zir_index,
+        .type_name_ctx = old_nav.fqn,
+    };
+    defer block.instructions.deinit(gpa);
 
-    const dump_air = build_options.enable_debug_extensions and comp.verbose_air;
-    const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null);
+    const zir_decl = zir.getDeclaration(inst_resolved.inst);
 
-    if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) {
-        air.deinit(gpa);
-        return .{ .ies_outdated = ies_outdated };
-    }
+    assert(old_nav.is_usingnamespace == (zir_decl.kind == .@"usingnamespace"));
 
-    // This job depends on any resolve_type_fully jobs queued up before it.
-    try comp.queueJob(.{ .codegen_func = .{
-        .func = func_index,
-        .air = air,
-    } });
+    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 });
 
-    return .{ .ies_outdated = ies_outdated };
-}
+    // First, we must resolve the declaration's type. To do this, we analyze the type body if available,
+    // or otherwise, we analyze the value body, populating `early_val` in the process.
 
-/// Takes ownership of `air`, even on error.
-/// If any types referenced by `air` are unresolved, marks the codegen as failed.
-pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Air) Allocator.Error!void {
-    const zcu = pt.zcu;
-    const gpa = zcu.gpa;
-    const ip = &zcu.intern_pool;
-    const comp = zcu.comp;
+    const nav_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: {
+        // We evaluate only the type now; no need for the value yet.
+        const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_resolved.inst);
+        const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src);
+        break :ty .{ .fromInterned(type_ref.toInterned().?), null };
+    } else ty: {
+        // We don't have a type body, so we need to evaluate the value immediately.
+        const value_body = zir_decl.value_body.?;
+        const result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
+        const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref);
+        break :ty .{ val.typeOf(zcu), val };
+    };
 
-    defer {
-        var air_mut = air;
-        air_mut.deinit(gpa);
+    switch (zir_decl.kind) {
+        .@"comptime" => unreachable, // this is not a Nav
+        .unnamed_test, .@"test", .decltest => assert(nav_ty.zigTypeTag(zcu) == .@"fn"),
+        .@"usingnamespace" => {},
+        .@"const" => {},
+        .@"var" => try sema.validateVarType(
+            &block,
+            if (zir_decl.type_body != null) ty_src else init_src,
+            nav_ty,
+            zir_decl.linkage == .@"extern",
+        ),
     }
 
-    const func = zcu.funcInfo(func_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);
+    // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine
+    // the full pointer type of this declaration.
 
-    if (build_options.enable_debug_extensions and comp.verbose_air) {
-        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", .{nav.fqn.fmt(ip)});
-    }
+    const alignment: InternPool.Alignment = a: {
+        const align_body = zir_decl.align_body orelse break :a .none;
+        const align_ref = try sema.resolveInlineBody(&block, align_body, inst_resolved.inst);
+        break :a try sema.analyzeAsAlign(&block, align_src, align_ref);
+    };
 
-    if (std.debug.runtime_safety) {
-        var verify: Liveness.Verify = .{
-            .gpa = gpa,
-            .air = air,
-            .liveness = liveness,
-            .intern_pool = ip,
-        };
-        defer verify.deinit();
+    const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
+        const linksection_body = zir_decl.linksection_body orelse break :ls .none;
+        const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_resolved.inst);
+        const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{
+            .needed_comptime_reason = "linksection must be comptime-known",
+        });
+        if (std.mem.indexOfScalar(u8, bytes, 0) != null) {
+            return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{});
+        } else if (bytes.len == 0) {
+            return sema.fail(&block, section_src, "linksection cannot be empty", .{});
+        }
+        break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
+    };
 
-        verify.verify() catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
-            else => {
-                try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
-                    gpa,
-                    zcu.navSrcLoc(nav_index),
-                    "invalid liveness: {s}",
-                    .{@errorName(err)},
-                ));
-                return;
+    const @"addrspace": std.builtin.AddressSpace = as: {
+        const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) {
+            .@"var" => .variable,
+            else => switch (nav_ty.zigTypeTag(zcu)) {
+                .@"fn" => .function,
+                else => .constant,
             },
         };
+        const target = zcu.getTarget();
+        const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) {
+            .function => target_util.defaultAddressSpace(target, .function),
+            .variable => target_util.defaultAddressSpace(target, .global_mutable),
+            .constant => target_util.defaultAddressSpace(target, .global_constant),
+            else => unreachable,
+        };
+        const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_resolved.inst);
+        break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx);
+    };
+
+    // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations
+    // don't have an associated value body.
+
+    const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: {
+        // Put the resolved type into `inst_map` to be used as the result type of the init.
+        try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_resolved.inst});
+        sema.inst_map.putAssumeCapacity(inst_resolved.inst, Air.internedToRef(nav_ty.toIntern()));
+        const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_resolved.inst);
+        assert(sema.inst_map.remove(inst_resolved.inst));
+
+        const result_ref = try sema.coerce(&block, nav_ty, uncoerced_result_ref, init_src);
+        break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref);
+    } else null;
+
+    const nav_val: Value = switch (zir_decl.linkage) {
+        .normal, .@"export" => switch (zir_decl.kind) {
+            .@"var" => .fromInterned(try pt.intern(.{ .variable = .{
+                .ty = nav_ty.toIntern(),
+                .init = final_val.?.toIntern(),
+                .owner_nav = nav_id,
+                .is_threadlocal = zir_decl.is_threadlocal,
+                .is_weak_linkage = false,
+            } })),
+            else => final_val.?,
+        },
+        .@"extern" => val: {
+            assert(final_val == null); // extern decls do not have a value body
+            const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: {
+                break :l zir.nullTerminatedString(zir_decl.lib_name);
+            } else null;
+            if (lib_name) |l| {
+                const lib_name_src = block.src(.{ .node_offset_lib_name = 0 });
+                try sema.handleExternLibName(&block, lib_name_src, l);
+            }
+            break :val .fromInterned(try pt.getExtern(.{
+                .name = old_nav.name,
+                .ty = nav_ty.toIntern(),
+                .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls),
+                .is_const = zir_decl.kind == .@"const",
+                .is_threadlocal = zir_decl.is_threadlocal,
+                .is_weak_linkage = false,
+                .is_dll_import = false,
+                .alignment = alignment,
+                .@"addrspace" = @"addrspace",
+                .zir_index = old_nav.analysis.?.zir_index, // `declaration` instruction
+                .owner_nav = undefined, // ignored by `getExtern`
+            }));
+        },
+    };
+
+    switch (nav_val.toIntern()) {
+        .generic_poison => unreachable, // assertion failure
+        .unreachable_value => unreachable, // assertion failure
+        else => {},
     }
 
-    const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0);
-    defer codegen_prog_node.end();
+    // This resolves the type of the resolved value, not that value itself. If `nav_val` is a struct type,
+    // this resolves the type `type` (which needs no resolution), not the struct itself.
+    try nav_ty.resolveLayout(pt);
 
-    if (!air.typesFullyResolved(zcu)) {
-        // A type we depend on failed to resolve. This is a transitive failure.
-        // 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.
-        // 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 => {
-                assert(zcu.failed_codegen.contains(nav_index));
-            },
-            else => {
-                try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
-                    gpa,
-                    zcu.navSrcLoc(nav_index),
-                    "unable to codegen: {s}",
-                    .{@errorName(err)},
-                ));
-                try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index }));
-            },
-        };
-    } else if (zcu.llvm_object) |llvm_object| {
-        llvm_object.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) {
-            error.OutOfMemory => return error.OutOfMemory,
+    // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`.
+    if (zir_decl.kind == .@"usingnamespace") {
+        if (nav_ty.toIntern() != .type_type) {
+            return sema.fail(&block, ty_src, "expected type, found {}", .{nav_ty.fmt(pt)});
+        }
+        if (nav_val.toType().getNamespace(zcu) == .none) {
+            return sema.fail(&block, ty_src, "type {} has no namespace", .{nav_val.toType().fmt(pt)});
+        }
+        ip.resolveNavValue(nav_id, .{
+            .val = nav_val.toIntern(),
+            .alignment = .none,
+            .@"linksection" = .none,
+            .@"addrspace" = .generic,
+        });
+        // TODO: usingnamespace cannot participate in incremental compilation
+        assert(zcu.analysis_in_progress.swapRemove(anal_unit));
+        return .{
+            .invalidate_nav_val = true,
+            .invalidate_nav_ref = true,
         };
     }
-}
 
-/// https://github.com/ziglang/zig/issues/14307
-pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void {
-    dev.check(.sema);
-    const import_file_result = try pt.importPkg(pkg);
-    const root_type = pt.zcu.fileRootType(import_file_result.file_index);
-    if (root_type == .none) {
-        return pt.semaFile(import_file_result.file_index);
-    }
-}
+    const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(nav_val.toIntern())) {
+        .func => |f| .{ true, f.owner_nav == nav_id }, // note that this lets function aliases reach codegen
+        .variable => |v| .{ v.owner_nav == nav_id, false },
+        .@"extern" => |e| .{
+            false,
+            Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" and zir_decl.linkage == .@"extern",
+        },
+        else => .{ true, false },
+    };
 
-fn createFileRootStruct(
-    pt: Zcu.PerThread,
-    file_index: Zcu.File.Index,
-    namespace_index: Zcu.Namespace.Index,
-    replace_existing: bool,
-) Allocator.Error!InternPool.Index {
-    const zcu = pt.zcu;
-    const gpa = zcu.gpa;
-    const ip = &zcu.intern_pool;
-    const file = zcu.fileByIndex(file_index);
-    const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
-    assert(extended.opcode == .struct_decl);
-    const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
-    assert(!small.has_captures_len);
-    assert(!small.has_backing_int);
-    assert(small.layout == .auto);
-    var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len;
-    const fields_len = if (small.has_fields_len) blk: {
-        const fields_len = file.zir.extra[extra_index];
-        extra_index += 1;
-        break :blk fields_len;
-    } else 0;
-    const decls_len = if (small.has_decls_len) blk: {
-        const decls_len = file.zir.extra[extra_index];
-        extra_index += 1;
-        break :blk decls_len;
-    } else 0;
-    const decls = file.zir.bodySlice(extra_index, decls_len);
-    extra_index += decls_len;
+    if (is_owned_fn) {
+        // linksection etc are legal, except some targets do not support function alignment.
+        if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) {
+            return sema.fail(&block, align_src, "target does not support function alignment", .{});
+        }
+    } else if (try nav_ty.comptimeOnlySema(pt)) {
+        // alignment, linksection, addrspace annotations are not allowed for comptime-only types.
+        const reason: []const u8 = switch (ip.indexToKey(nav_val.toIntern())) {
+            .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations*
+            else => "comptime-only type",
+        };
+        if (zir_decl.align_body != null) {
+            return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason});
+        }
+        if (zir_decl.linksection_body != null) {
+            return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason});
+        }
+        if (zir_decl.addrspace_body != null) {
+            return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason});
+        }
+    }
 
-    const tracked_inst = try ip.trackZir(gpa, pt.tid, .{
-        .file = file_index,
-        .inst = .main_struct_inst,
+    ip.resolveNavValue(nav_id, .{
+        .val = nav_val.toIntern(),
+        .alignment = alignment,
+        .@"linksection" = @"linksection",
+        .@"addrspace" = @"addrspace",
     });
-    const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
-        .layout = .auto,
-        .fields_len = fields_len,
-        .known_non_opv = small.known_non_opv,
-        .requires_comptime = if (small.known_comptime_only) .yes else .unknown,
-        .any_comptime_fields = small.any_comptime_fields,
-        .any_default_inits = small.any_default_inits,
-        .inits_resolved = false,
-        .any_aligned_fields = small.any_aligned_fields,
-        .key = .{ .declared = .{
-            .zir_index = tracked_inst,
-            .captures = &.{},
-        } },
-    }, replace_existing)) {
-        .existing => unreachable, // we wouldn't be analysing the file root if this type existed
-        .wip => |wip| wip,
-    };
-    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,
-            AnalUnit.wrap(.{ .cau = new_cau_index }),
-            .{ .src_hash = tracked_inst },
-        );
-    }
+    // Mark the unit as completed before evaluating the export!
+    assert(zcu.analysis_in_progress.swapRemove(anal_unit));
 
-    try pt.scanNamespace(namespace_index, decls);
-    try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
-    codegen_type: {
-        if (zcu.comp.config.use_llvm) break :codegen_type;
-        if (file.mod.strip) break :codegen_type;
-        // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
+    if (zir_decl.linkage == .@"export") {
+        const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) });
+        const name_slice = zir.nullTerminatedString(zir_decl.name);
+        const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls);
+        try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_id);
     }
-    zcu.setFileRootType(file_index, wip_ty.index);
-    return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
-}
 
-/// Re-scan the namespace of a file's root struct type on an incremental update.
-/// The file must have successfully populated ZIR.
-/// If the file's root struct type is not populated (the file is unreferenced), nothing is done.
-/// This is called by `updateZirRefs` for all updated files before the main work loop.
-/// This function does not perform any semantic analysis.
-fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {
-    const zcu = pt.zcu;
+    try sema.flushExports();
 
-    const file = zcu.fileByIndex(file_index);
-    assert(file.status == .success_zir);
-    const file_root_type = zcu.fileRootType(file_index);
-    if (file_root_type == .none) return;
+    queue_codegen: {
+        if (!queue_linker_work) break :queue_codegen;
 
-    log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{
-        file.mod.fully_qualified_name,
-        file.sub_file_path,
-    });
+        if (!try nav_ty.hasRuntimeBitsSema(pt)) {
+            if (zcu.comp.config.use_llvm) break :queue_codegen;
+            if (file.mod.strip) break :queue_codegen;
+        }
 
-    const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);
-    const decls = decls: {
-        const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
-        const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
+        // This job depends on any resolve_type_fully jobs queued up before it.
+        try zcu.comp.queueJob(.{ .codegen_nav = nav_id });
+    }
 
-        var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len;
-        extra_index += @intFromBool(small.has_fields_len);
-        const decls_len = if (small.has_decls_len) blk: {
-            const decls_len = file.zir.extra[extra_index];
-            extra_index += 1;
-            break :blk decls_len;
-        } else 0;
-        break :decls file.zir.bodySlice(extra_index, decls_len);
-    };
-    try pt.scanNamespace(namespace_index, decls);
-    zcu.namespacePtr(namespace_index).generation = zcu.generation;
+    switch (old_nav.status) {
+        .unresolved => return .{
+            .invalidate_nav_val = true,
+            .invalidate_nav_ref = true,
+        },
+        .resolved => |old| {
+            const new = ip.getNav(nav_id).status.resolved;
+            return .{
+                .invalidate_nav_val = new.val != old.val,
+                .invalidate_nav_ref = ip.typeOf(new.val) != ip.typeOf(old.val) or
+                    new.alignment != old.alignment or
+                    new.@"linksection" != old.@"linksection" or
+                    new.@"addrspace" != old.@"addrspace",
+            };
+        },
+    }
 }
 
-fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
+pub fn ensureFuncBodyUpToDate(pt: Zcu.PerThread, maybe_coerced_func_index: InternPool.Index) Zcu.SemaError!void {
+    dev.check(.sema);
+
     const tracy = trace(@src());
     defer tracy.end();
 
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
-    const file = zcu.fileByIndex(file_index);
-    assert(zcu.fileRootType(file_index) == .none);
+    const ip = &zcu.intern_pool;
 
-    if (file.status != .success_zir) {
-        return error.AnalysisFail;
-    }
-    assert(file.zir_loaded);
+    // We only care about the uncoerced function.
+    const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index);
+    const anal_unit: AnalUnit = .wrap(.{ .func = func_index });
 
-    const new_namespace_index = try pt.createNamespace(.{
-        .parent = .none,
-        .owner_type = undefined, // set in `createFileRootStruct`
-        .file_scope = file_index,
-        .generation = zcu.generation,
-    });
-    const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false);
-    errdefer zcu.intern_pool.remove(pt.tid, struct_ty);
+    log.debug("ensureFuncBodyUpToDate {}", .{zcu.fmtAnalUnit(anal_unit)});
 
-    switch (zcu.comp.cache_use) {
-        .whole => |whole| if (whole.cache_manifest) |man| {
-            const source = file.getSource(gpa) catch |err| {
-                try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)});
-                return error.AnalysisFail;
-            };
+    const func = zcu.funcInfo(maybe_coerced_func_index);
 
-            const resolved_path = std.fs.path.resolve(gpa, &.{
-                file.mod.root.root_dir.path orelse ".",
-                file.mod.root.sub_path,
-                file.sub_file_path,
-            }) catch |err| {
-                try pt.reportRetryableFileError(file_index, "unable to resolve path: {s}", .{@errorName(err)});
-                return error.AnalysisFail;
-            };
-            errdefer gpa.free(resolved_path);
+    const was_outdated = zcu.outdated.swapRemove(anal_unit) or
+        zcu.potentially_outdated.swapRemove(anal_unit);
 
-            whole.cache_manifest_mutex.lock();
-            defer whole.cache_manifest_mutex.unlock();
-            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;
-                },
-            };
+    const prev_failed = zcu.failed_analysis.contains(anal_unit) or zcu.transitive_failed_analysis.contains(anal_unit);
+
+    if (was_outdated) {
+        dev.check(.incremental);
+        _ = zcu.outdated_ready.swapRemove(anal_unit);
+        zcu.deleteUnitExports(anal_unit);
+        zcu.deleteUnitReferences(anal_unit);
+        if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
+            kv.value.destroy(gpa);
+        }
+        _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
+    } else {
+        // We can trust the current information about this function.
+        if (prev_failed) {
+            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
+        }
+    }
+
+    const func_prog_node = zcu.sema_prog_node.start(ip.getNav(func.owner_nav).fqn.toSlice(ip), 0);
+    defer func_prog_node.end();
+
+    const ies_outdated, const new_failed = if (pt.analyzeFuncBody(func_index)) |result|
+        .{ prev_failed or result.ies_outdated, false }
+    else |err| switch (err) {
+        error.AnalysisFail => res: {
+            if (!zcu.failed_analysis.contains(anal_unit)) {
+                // 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, {});
+                log.debug("mark transitive analysis failure for {}", .{zcu.fmtAnalUnit(anal_unit)});
+            }
+            // We consider the IES to be outdated if the function previously succeeded analysis; in this case,
+            // we need to re-analyze dependants to ensure they hit a transitive error here, rather than reporting
+            // a different error later (which may now be invalid).
+            break :res .{ !prev_failed, true };
         },
-        .incremental => {},
+        error.OutOfMemory => {
+            // TODO: it's unclear how to gracefully handle this.
+            // To report the error cleanly, we need to add a message to `failed_analysis` and a
+            // corresponding entry to `retryable_failures`; but either of these things is quite
+            // likely to OOM at this point.
+            // If that happens, what do we do? Perhaps we could have a special field on `Zcu`
+            // for reporting OOM errors without allocating.
+            return error.OutOfMemory;
+        },
+    };
+
+    if (was_outdated) {
+        if (ies_outdated) {
+            try zcu.markDependeeOutdated(.marked_po, .{ .interned = func_index });
+        } else {
+            try zcu.markPoDependeeUpToDate(.{ .interned = func_index });
+        }
     }
-}
 
-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,
-};
+    if (new_failed) return error.AnalysisFail;
+}
 
-/// 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 {
+fn analyzeFuncBody(
+    pt: Zcu.PerThread,
+    func_index: InternPool.Index,
+) Zcu.SemaError!struct { ies_outdated: bool } {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
-    const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
-
-    const cau = ip.getCau(cau_index);
-    const inst_info = cau.zir_index.resolveFull(ip) orelse return error.AnalysisFail;
-    const file = zcu.fileByIndex(inst_info.file);
-    const zir = file.zir;
-
-    if (file.status != .success_zir) {
-        return error.AnalysisFail;
-    }
+    const func = zcu.funcInfo(func_index);
+    const anal_unit = AnalUnit.wrap(.{ .func = func_index });
 
-    // We are about to re-analyze this `Cau`; drop its depenndencies.
-    zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
+    // Make sure that this function is still owned by the same `Nav`. Otherwise, analyzing
+    // it would be a waste of time in the best case, and could cause codegen to give bogus
+    // results in the worst case.
 
-    switch (cau.owner.unwrap()) {
-        .none => {}, // `comptime` decl -- we will re-analyze its body.
-        .nav => {}, // Other decl -- we will re-analyze its value.
-        .type => |ty| {
-            // This is an incremental update, and this type is being re-analyzed because it is outdated.
-            // Create a new type in its place, and mark the old one as outdated so that use sites will
-            // be re-analyzed and discover an up-to-date type.
-            const new_ty = try pt.ensureTypeUpToDate(ty, true);
-            assert(new_ty != ty);
-            return .{
-                .invalidate_decl_val = true,
-                .invalidate_decl_ref = true,
-            };
-        },
+    if (func.generic_owner == .none) {
+        // Among another things, this ensures that the function's `zir_body_inst` is correct.
+        try pt.ensureNavValUpToDate(func.owner_nav);
+        if (ip.getNav(func.owner_nav).status.resolved.val != func_index) {
+            // This function is no longer referenced! There's no point in re-analyzing it.
+            // Just mark a transitive failure and move on.
+            return error.AnalysisFail;
+        }
+    } else {
+        const go_nav = zcu.funcInfo(func.generic_owner).owner_nav;
+        // Among another things, this ensures that the function's `zir_body_inst` is correct.
+        try pt.ensureNavValUpToDate(go_nav);
+        if (ip.getNav(go_nav).status.resolved.val != func.generic_owner) {
+            // The generic owner is no longer referenced, so this function is also unreferenced.
+            // There's no point in re-analyzing it. Just mark a transitive failure and move on.
+            return error.AnalysisFail;
+        }
     }
 
-    const is_usingnamespace = switch (cau.owner.unwrap()) {
-        .nav => |nav| ip.getNav(nav).is_usingnamespace,
-        .none, .type => false,
-    };
+    // We'll want to remember what the IES used to be before the update for
+    // dependency invalidation purposes.
+    const old_resolved_ies = if (func.analysisUnordered(ip).inferred_error_set)
+        func.resolvedErrorSetUnordered(ip)
+    else
+        .none;
 
-    log.debug("semaCau {}", .{zcu.fmtAnalUnit(anal_unit)});
+    log.debug("analyze and generate fn body {}", .{zcu.fmtAnalUnit(anal_unit)});
 
-    try zcu.analysis_in_progress.put(gpa, anal_unit, {});
-    errdefer _ = zcu.analysis_in_progress.swapRemove(anal_unit);
+    var air = try pt.analyzeFnBodyInner(func_index);
+    errdefer air.deinit(gpa);
 
-    var analysis_arena = std.heap.ArenaAllocator.init(gpa);
-    defer analysis_arena.deinit();
+    const ies_outdated = !func.analysisUnordered(ip).inferred_error_set or
+        func.resolvedErrorSetUnordered(ip) != old_resolved_ies;
 
-    var comptime_err_ret_trace = std.ArrayList(Zcu.LazySrcLoc).init(gpa);
-    defer comptime_err_ret_trace.deinit();
+    const comp = zcu.comp;
 
-    var sema: Sema = .{
-        .pt = pt,
-        .gpa = gpa,
-        .arena = analysis_arena.allocator(),
-        .code = zir,
-        .owner = anal_unit,
-        .func_index = .none,
-        .func_is_naked = false,
-        .fn_ret_ty = Type.void,
-        .fn_ret_ty_ies = null,
-        .comptime_err_ret_trace = &comptime_err_ret_trace,
-    };
-    defer sema.deinit();
+    const dump_air = build_options.enable_debug_extensions and comp.verbose_air;
+    const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null);
 
-    // Every `Cau` has a dependency on the source of its own ZIR instruction.
-    try sema.declareDependency(.{ .src_hash = cau.zir_index });
+    if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) {
+        air.deinit(gpa);
+        return .{ .ies_outdated = ies_outdated };
+    }
 
-    var block: Sema.Block = .{
-        .parent = null,
-        .sema = &sema,
-        .namespace = cau.namespace,
-        .instructions = .{},
-        .inlining = null,
-        .is_comptime = true,
-        .src_base_inst = cau.zir_index,
-        .type_name_ctx = switch (cau.owner.unwrap()) {
-            .nav => |nav| ip.getNav(nav).fqn,
-            .type => |ty| Type.fromInterned(ty).containerTypeName(ip),
-            .none => try ip.getOrPutStringFmt(gpa, pt.tid, "{}.comptime", .{
-                Type.fromInterned(zcu.namespacePtr(cau.namespace).owner_type).containerTypeName(ip).fmt(ip),
-            }, .no_embedded_nulls),
-        },
-    };
-    defer block.instructions.deinit(gpa);
+    // This job depends on any resolve_type_fully jobs queued up before it.
+    try comp.queueJob(.{ .codegen_func = .{
+        .func = func_index,
+        .air = air,
+    } });
 
-    const zir_decl = zir.getDeclaration(inst_info.inst);
+    return .{ .ies_outdated = ies_outdated };
+}
 
-    // 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),
-    };
+/// Takes ownership of `air`, even on error.
+/// If any types referenced by `air` are unresolved, marks the codegen as failed.
+pub fn linkerUpdateFunc(pt: Zcu.PerThread, func_index: InternPool.Index, air: Air) Allocator.Error!void {
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
+    const comp = zcu.comp;
 
-    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 });
+    defer {
+        var air_mut = air;
+        air_mut.deinit(gpa);
+    }
 
-    // First, we must resolve the declaration's type. To do this, we analyze the type body if available,
-    // or otherwise, we analyze the value body, populating `early_val` in the process.
+    const func = zcu.funcInfo(func_index);
+    const nav_index = func.owner_nav;
+    const nav = ip.getNav(nav_index);
 
-    const decl_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: {
-        // We evaluate only the type now; no need for the value yet.
-        const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_info.inst);
-        const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src);
-        break :ty .{ .fromInterned(type_ref.toInterned().?), null };
-    } else ty: {
-        // We don't have a type body, so we need to evaluate the value immediately.
-        const value_body = zir_decl.value_body.?;
-        const result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst);
-        const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref);
-        break :ty .{ val.typeOf(zcu), val };
-    };
+    var liveness = try Liveness.analyze(gpa, air, ip);
+    defer liveness.deinit(gpa);
 
-    switch (zir_decl.kind) {
-        .unnamed_test, .@"test", .decltest => assert(decl_ty.zigTypeTag(zcu) == .@"fn"),
-        .@"comptime" => assert(decl_ty.toIntern() == .void_type),
-        .@"usingnamespace" => {},
-        .@"const" => {},
-        .@"var" => try sema.validateVarType(
-            &block,
-            if (zir_decl.type_body != null) ty_src else init_src,
-            decl_ty,
-            zir_decl.linkage == .@"extern",
-        ),
+    if (build_options.enable_debug_extensions and comp.verbose_air) {
+        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", .{nav.fqn.fmt(ip)});
     }
 
-    // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine
-    // the full pointer type of this declaration.
+    if (std.debug.runtime_safety) {
+        var verify: Liveness.Verify = .{
+            .gpa = gpa,
+            .air = air,
+            .liveness = liveness,
+            .intern_pool = ip,
+        };
+        defer verify.deinit();
 
-    const alignment: InternPool.Alignment = a: {
-        const align_body = zir_decl.align_body orelse break :a .none;
-        const align_ref = try sema.resolveInlineBody(&block, align_body, inst_info.inst);
-        break :a try sema.analyzeAsAlign(&block, align_src, align_ref);
-    };
+        verify.verify() catch |err| switch (err) {
+            error.OutOfMemory => return error.OutOfMemory,
+            else => {
+                try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
+                    gpa,
+                    zcu.navSrcLoc(nav_index),
+                    "invalid liveness: {s}",
+                    .{@errorName(err)},
+                ));
+                return;
+            },
+        };
+    }
 
-    const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
-        const linksection_body = zir_decl.linksection_body orelse break :ls .none;
-        const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_info.inst);
-        const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{
-            .needed_comptime_reason = "linksection must be comptime-known",
-        });
-        if (std.mem.indexOfScalar(u8, bytes, 0) != null) {
-            return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{});
-        } else if (bytes.len == 0) {
-            return sema.fail(&block, section_src, "linksection cannot be empty", .{});
-        }
-        break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
-    };
+    const codegen_prog_node = zcu.codegen_prog_node.start(nav.fqn.toSlice(ip), 0);
+    defer codegen_prog_node.end();
 
-    const @"addrspace": std.builtin.AddressSpace = as: {
-        const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) {
-            .@"var" => .variable,
-            else => switch (decl_ty.zigTypeTag(zcu)) {
-                .@"fn" => .function,
-                else => .constant,
+    if (!air.typesFullyResolved(zcu)) {
+        // A type we depend on failed to resolve. This is a transitive failure.
+        // 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.
+        // 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 => {
+                assert(zcu.failed_codegen.contains(nav_index));
+            },
+            else => {
+                try zcu.failed_codegen.putNoClobber(gpa, nav_index, try Zcu.ErrorMsg.create(
+                    gpa,
+                    zcu.navSrcLoc(nav_index),
+                    "unable to codegen: {s}",
+                    .{@errorName(err)},
+                ));
+                try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index }));
             },
         };
-        const target = zcu.getTarget();
-        const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) {
-            .function => target_util.defaultAddressSpace(target, .function),
-            .variable => target_util.defaultAddressSpace(target, .global_mutable),
-            .constant => target_util.defaultAddressSpace(target, .global_constant),
-            else => unreachable,
+    } else if (zcu.llvm_object) |llvm_object| {
+        llvm_object.updateFunc(pt, func_index, air, liveness) catch |err| switch (err) {
+            error.OutOfMemory => return error.OutOfMemory,
         };
-        const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst);
-        break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx);
-    };
-
-    // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations
-    // don't have an associated value body.
-
-    const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: {
-        // Put the resolved type into `inst_map` to be used as the result type of the init.
-        try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_info.inst});
-        sema.inst_map.putAssumeCapacity(inst_info.inst, Air.internedToRef(decl_ty.toIntern()));
-        const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst);
-        assert(sema.inst_map.remove(inst_info.inst));
+    }
+}
 
-        const result_ref = try sema.coerce(&block, decl_ty, uncoerced_result_ref, init_src);
-        break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref);
-    } else null;
+/// https://github.com/ziglang/zig/issues/14307
+pub fn semaPkg(pt: Zcu.PerThread, pkg: *Module) !void {
+    dev.check(.sema);
+    const import_file_result = try pt.importPkg(pkg);
+    const root_type = pt.zcu.fileRootType(import_file_result.file_index);
+    if (root_type == .none) {
+        return pt.semaFile(import_file_result.file_index);
+    }
+}
 
-    // TODO: missing validation?
+fn createFileRootStruct(
+    pt: Zcu.PerThread,
+    file_index: Zcu.File.Index,
+    namespace_index: Zcu.Namespace.Index,
+    replace_existing: bool,
+) Allocator.Error!InternPool.Index {
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const ip = &zcu.intern_pool;
+    const file = zcu.fileByIndex(file_index);
+    const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
+    assert(extended.opcode == .struct_decl);
+    const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
+    assert(!small.has_captures_len);
+    assert(!small.has_backing_int);
+    assert(small.layout == .auto);
+    var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len;
+    const fields_len = if (small.has_fields_len) blk: {
+        const fields_len = file.zir.extra[extra_index];
+        extra_index += 1;
+        break :blk fields_len;
+    } else 0;
+    const decls_len = if (small.has_decls_len) blk: {
+        const decls_len = file.zir.extra[extra_index];
+        extra_index += 1;
+        break :blk decls_len;
+    } else 0;
+    const decls = file.zir.bodySlice(extra_index, decls_len);
+    extra_index += decls_len;
 
-    const decl_val: Value = switch (zir_decl.linkage) {
-        .normal, .@"export" => switch (zir_decl.kind) {
-            .@"var" => .fromInterned(try pt.intern(.{ .variable = .{
-                .ty = decl_ty.toIntern(),
-                .init = final_val.?.toIntern(),
-                .owner_nav = cau.owner.unwrap().nav,
-                .is_threadlocal = zir_decl.is_threadlocal,
-                .is_weak_linkage = false,
-            } })),
-            else => final_val.?,
-        },
-        .@"extern" => val: {
-            assert(final_val == null); // extern decls do not have a value body
-            const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: {
-                break :l zir.nullTerminatedString(zir_decl.lib_name);
-            } else null;
-            if (lib_name) |l| {
-                const lib_name_src = block.src(.{ .node_offset_lib_name = 0 });
-                try sema.handleExternLibName(&block, lib_name_src, l);
-            }
-            break :val .fromInterned(try pt.getExtern(.{
-                .name = old_nav_info.name,
-                .ty = decl_ty.toIntern(),
-                .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls),
-                .is_const = zir_decl.kind == .@"const",
-                .is_threadlocal = zir_decl.is_threadlocal,
-                .is_weak_linkage = false,
-                .is_dll_import = false,
-                .alignment = alignment,
-                .@"addrspace" = @"addrspace",
-                .zir_index = cau.zir_index, // `declaration` instruction
-                .owner_nav = undefined, // ignored by `getExtern`
-            }));
-        },
+    const tracked_inst = try ip.trackZir(gpa, pt.tid, .{
+        .file = file_index,
+        .inst = .main_struct_inst,
+    });
+    const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
+        .layout = .auto,
+        .fields_len = fields_len,
+        .known_non_opv = small.known_non_opv,
+        .requires_comptime = if (small.known_comptime_only) .yes else .unknown,
+        .any_comptime_fields = small.any_comptime_fields,
+        .any_default_inits = small.any_default_inits,
+        .inits_resolved = false,
+        .any_aligned_fields = small.any_aligned_fields,
+        .key = .{ .declared = .{
+            .zir_index = tracked_inst,
+            .captures = &.{},
+        } },
+    }, replace_existing)) {
+        .existing => unreachable, // we wouldn't be analysing the file root if this type existed
+        .wip => |wip| wip,
     };
+    errdefer wip_ty.cancel(ip, pt.tid);
 
-    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.
-    };
+    wip_ty.setName(ip, try file.internFullyQualifiedName(pt));
+    ip.namespacePtr(namespace_index).owner_type = wip_ty.index;
 
-    switch (decl_val.toIntern()) {
-        .generic_poison => unreachable, // assertion failure
-        .unreachable_value => unreachable, // assertion failure
-        else => {},
+    if (zcu.comp.incremental) {
+        try ip.addDependency(
+            gpa,
+            .wrap(.{ .type = wip_ty.index }),
+            .{ .src_hash = tracked_inst },
+        );
     }
 
-    // This resolves the type of the resolved value, not that value itself. If `decl_val` is a struct type,
-    // this resolves the type `type` (which needs no resolution), not the struct itself.
-    try decl_ty.resolveLayout(pt);
-
-    // TODO: this is jank. If #20663 is rejected, let's think about how to better model `usingnamespace`.
-    if (is_usingnamespace) {
-        if (decl_ty.toIntern() != .type_type) {
-            return sema.fail(&block, ty_src, "expected type, found {}", .{decl_ty.fmt(pt)});
-        }
-        if (decl_val.toType().getNamespace(zcu) == .none) {
-            return sema.fail(&block, ty_src, "type {} has no namespace", .{decl_val.toType().fmt(pt)});
-        }
-        ip.resolveNavValue(nav_index, .{
-            .val = decl_val.toIntern(),
-            .alignment = .none,
-            .@"linksection" = .none,
-            .@"addrspace" = .generic,
-        });
-        // TODO: usingnamespace cannot participate in incremental compilation
-        assert(zcu.analysis_in_progress.swapRemove(anal_unit));
-        return .{
-            .invalidate_decl_val = true,
-            .invalidate_decl_ref = true,
-        };
+    try pt.scanNamespace(namespace_index, decls);
+    try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
+    codegen_type: {
+        if (zcu.comp.config.use_llvm) break :codegen_type;
+        if (file.mod.strip) break :codegen_type;
+        // This job depends on any resolve_type_fully jobs queued up before it.
+        try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
     }
+    zcu.setFileRootType(file_index, wip_ty.index);
+    return wip_ty.finish(ip, namespace_index);
+}
 
-    const queue_linker_work, const is_owned_fn = switch (ip.indexToKey(decl_val.toIntern())) {
-        .func => |f| .{ true, f.owner_nav == nav_index }, // note that this lets function aliases reach codegen
-        .variable => |v| .{ v.owner_nav == nav_index, false },
-        .@"extern" => |e| .{ false, Type.fromInterned(e.ty).zigTypeTag(zcu) == .@"fn" },
-        else => .{ true, false },
-    };
-
-    // Keep in sync with logic in `Sema.zirVarExtended`.
+/// Re-scan the namespace of a file's root struct type on an incremental update.
+/// The file must have successfully populated ZIR.
+/// If the file's root struct type is not populated (the file is unreferenced), nothing is done.
+/// This is called by `updateZirRefs` for all updated files before the main work loop.
+/// This function does not perform any semantic analysis.
+fn updateFileNamespace(pt: Zcu.PerThread, file_index: Zcu.File.Index) Allocator.Error!void {
+    const zcu = pt.zcu;
 
-    if (is_owned_fn) {
-        // linksection etc are legal, except some targets do not support function alignment.
-        if (zir_decl.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) {
-            return sema.fail(&block, align_src, "target does not support function alignment", .{});
-        }
-    } else if (try decl_ty.comptimeOnlySema(pt)) {
-        // alignment, linksection, addrspace annotations are not allowed for comptime-only types.
-        const reason: []const u8 = switch (ip.indexToKey(decl_val.toIntern())) {
-            .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations*
-            else => "comptime-only type",
-        };
-        if (zir_decl.align_body != null) {
-            return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason});
-        }
-        if (zir_decl.linksection_body != null) {
-            return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason});
-        }
-        if (zir_decl.addrspace_body != null) {
-            return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason});
-        }
-    }
+    const file = zcu.fileByIndex(file_index);
+    assert(file.status == .success_zir);
+    const file_root_type = zcu.fileRootType(file_index);
+    if (file_root_type == .none) return;
 
-    ip.resolveNavValue(nav_index, .{
-        .val = decl_val.toIntern(),
-        .alignment = alignment,
-        .@"linksection" = @"linksection",
-        .@"addrspace" = @"addrspace",
+    log.debug("updateFileNamespace mod={s} sub_file_path={s}", .{
+        file.mod.fully_qualified_name,
+        file.sub_file_path,
     });
 
-    // Mark the `Cau` as completed before evaluating the export!
-    assert(zcu.analysis_in_progress.swapRemove(anal_unit));
-
-    if (zir_decl.linkage == .@"export") {
-        const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.is_pub) });
-        const name_slice = zir.nullTerminatedString(zir_decl.name);
-        const name_ip = try ip.getOrPutString(gpa, pt.tid, name_slice, .no_embedded_nulls);
-        try sema.analyzeExport(&block, export_src, .{ .name = name_ip }, nav_index);
-    }
+    const namespace_index = Type.fromInterned(file_root_type).getNamespaceIndex(zcu);
+    const decls = decls: {
+        const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended;
+        const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
 
-    try sema.flushExports();
+        var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len;
+        extra_index += @intFromBool(small.has_fields_len);
+        const decls_len = if (small.has_decls_len) blk: {
+            const decls_len = file.zir.extra[extra_index];
+            extra_index += 1;
+            break :blk decls_len;
+        } else 0;
+        break :decls file.zir.bodySlice(extra_index, decls_len);
+    };
+    try pt.scanNamespace(namespace_index, decls);
+    zcu.namespacePtr(namespace_index).generation = zcu.generation;
+}
 
-    queue_codegen: {
-        if (!queue_linker_work) break :queue_codegen;
+fn semaFile(pt: Zcu.PerThread, file_index: Zcu.File.Index) Zcu.SemaError!void {
+    const tracy = trace(@src());
+    defer tracy.end();
 
-        if (!try decl_ty.hasRuntimeBitsSema(pt)) {
-            if (zcu.comp.config.use_llvm) break :queue_codegen;
-            if (file.mod.strip) break :queue_codegen;
-        }
+    const zcu = pt.zcu;
+    const gpa = zcu.gpa;
+    const file = zcu.fileByIndex(file_index);
+    assert(zcu.fileRootType(file_index) == .none);
 
-        // This job depends on any resolve_type_fully jobs queued up before it.
-        try zcu.comp.queueJob(.{ .codegen_nav = nav_index });
+    if (file.status != .success_zir) {
+        return error.AnalysisFail;
     }
+    assert(file.zir_loaded);
 
-    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",
+    const new_namespace_index = try pt.createNamespace(.{
+        .parent = .none,
+        .owner_type = undefined, // set in `createFileRootStruct`
+        .file_scope = file_index,
+        .generation = zcu.generation,
+    });
+    const struct_ty = try pt.createFileRootStruct(file_index, new_namespace_index, false);
+    errdefer zcu.intern_pool.remove(pt.tid, struct_ty);
+
+    switch (zcu.comp.cache_use) {
+        .whole => |whole| if (whole.cache_manifest) |man| {
+            const source = file.getSource(gpa) catch |err| {
+                try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)});
+                return error.AnalysisFail;
+            };
+
+            const resolved_path = std.fs.path.resolve(gpa, &.{
+                file.mod.root.root_dir.path orelse ".",
+                file.mod.root.sub_path,
+                file.sub_file_path,
+            }) catch |err| {
+                try pt.reportRetryableFileError(file_index, "unable to resolve path: {s}", .{@errorName(err)});
+                return error.AnalysisFail;
+            };
+            errdefer gpa.free(resolved_path);
+
+            whole.cache_manifest_mutex.lock();
+            defer whole.cache_manifest_mutex.unlock();
+            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 => {},
     }
 }
 
@@ -1880,45 +1914,42 @@ pub fn scanNamespace(
 
     // For incremental updates, `scanDecl` wants to look up existing decls by their ZIR index rather
     // than their name. We'll build an efficient mapping now, then discard the current `decls`.
-    // We map to the `Cau`, since not every declaration has a `Nav`.
-    var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index) = .empty;
+    // We map to the `AnalUnit`, since not every declaration has a `Nav`.
+    var existing_by_inst: std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit) = .empty;
     defer existing_by_inst.deinit(gpa);
 
     try existing_by_inst.ensureTotalCapacity(gpa, @intCast(
         namespace.pub_decls.count() + namespace.priv_decls.count() +
             namespace.pub_usingnamespace.items.len + namespace.priv_usingnamespace.items.len +
-            namespace.other_decls.items.len,
+            namespace.comptime_decls.items.len +
+            namespace.test_decls.items.len,
     ));
 
     for (namespace.pub_decls.keys()) |nav| {
-        const cau_index = ip.getNav(nav).analysis_owner.unwrap().?;
-        const zir_index = ip.getCau(cau_index).zir_index;
-        existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index);
+        const zir_index = ip.getNav(nav).analysis.?.zir_index;
+        existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav }));
     }
     for (namespace.priv_decls.keys()) |nav| {
-        const cau_index = ip.getNav(nav).analysis_owner.unwrap().?;
-        const zir_index = ip.getCau(cau_index).zir_index;
-        existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index);
+        const zir_index = ip.getNav(nav).analysis.?.zir_index;
+        existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav }));
     }
     for (namespace.pub_usingnamespace.items) |nav| {
-        const cau_index = ip.getNav(nav).analysis_owner.unwrap().?;
-        const zir_index = ip.getCau(cau_index).zir_index;
-        existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index);
+        const zir_index = ip.getNav(nav).analysis.?.zir_index;
+        existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav }));
     }
     for (namespace.priv_usingnamespace.items) |nav| {
-        const cau_index = ip.getNav(nav).analysis_owner.unwrap().?;
-        const zir_index = ip.getCau(cau_index).zir_index;
-        existing_by_inst.putAssumeCapacityNoClobber(zir_index, cau_index);
-    }
-    for (namespace.other_decls.items) |cau_index| {
-        const cau = ip.getCau(cau_index);
-        existing_by_inst.putAssumeCapacityNoClobber(cau.zir_index, cau_index);
-        // If this is a test, it'll be re-added to `test_functions` later on
-        // if still alive. Remove it for now.
-        switch (cau.owner.unwrap()) {
-            .none, .type => {},
-            .nav => |nav| _ = zcu.test_functions.swapRemove(nav),
-        }
+        const zir_index = ip.getNav(nav).analysis.?.zir_index;
+        existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav }));
+    }
+    for (namespace.comptime_decls.items) |cu| {
+        const zir_index = ip.getComptimeUnit(cu).zir_index;
+        existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .@"comptime" = cu }));
+    }
+    for (namespace.test_decls.items) |nav| {
+        const zir_index = ip.getNav(nav).analysis.?.zir_index;
+        existing_by_inst.putAssumeCapacityNoClobber(zir_index, .wrap(.{ .nav_val = nav }));
+        // This test will be re-added to `test_functions` later on if it's still alive. Remove it for now.
+        _ = zcu.test_functions.swapRemove(nav);
     }
 
     var seen_decls: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;
@@ -1928,7 +1959,8 @@ pub fn scanNamespace(
     namespace.priv_decls.clearRetainingCapacity();
     namespace.pub_usingnamespace.clearRetainingCapacity();
     namespace.priv_usingnamespace.clearRetainingCapacity();
-    namespace.other_decls.clearRetainingCapacity();
+    namespace.comptime_decls.clearRetainingCapacity();
+    namespace.test_decls.clearRetainingCapacity();
 
     var scan_decl_iter: ScanDeclIter = .{
         .pt = pt,
@@ -1950,7 +1982,7 @@ const ScanDeclIter = struct {
     pt: Zcu.PerThread,
     namespace_index: Zcu.Namespace.Index,
     seen_decls: *std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
-    existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.Cau.Index),
+    existing_by_inst: *const std.AutoHashMapUnmanaged(InternPool.TrackedInst.Index, InternPool.AnalUnit),
     /// Decl scanning is run in two passes, so that we can detect when a generated
     /// name would clash with an explicit name and use a different one.
     pass: enum { named, unnamed },
@@ -1988,48 +2020,30 @@ const ScanDeclIter = struct {
 
         const decl = zir.getDeclaration(decl_inst);
 
-        const Kind = enum { @"comptime", @"usingnamespace", @"test", named };
-
-        const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (decl.kind) {
-            .@"comptime" => info: {
+        const maybe_name: InternPool.OptionalNullTerminatedString = switch (decl.kind) {
+            .@"comptime" => name: {
                 if (iter.pass != .unnamed) return;
-                break :info .{
-                    .none,
-                    .@"comptime",
-                    false,
-                };
+                break :name .none;
             },
-            .@"usingnamespace" => info: {
+            .@"usingnamespace" => name: {
                 if (iter.pass != .unnamed) return;
                 const i = iter.usingnamespace_index;
                 iter.usingnamespace_index += 1;
-                break :info .{
-                    (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional(),
-                    .@"usingnamespace",
-                    false,
-                };
+                break :name (try iter.avoidNameConflict("usingnamespace_{d}", .{i})).toOptional();
             },
-            .unnamed_test => info: {
+            .unnamed_test => name: {
                 if (iter.pass != .unnamed) return;
                 const i = iter.unnamed_test_index;
                 iter.unnamed_test_index += 1;
-                break :info .{
-                    (try iter.avoidNameConflict("test_{d}", .{i})).toOptional(),
-                    .@"test",
-                    false,
-                };
+                break :name (try iter.avoidNameConflict("test_{d}", .{i})).toOptional();
             },
-            .@"test", .decltest => |kind| info: {
+            .@"test", .decltest => |kind| name: {
                 // We consider these to be unnamed since the decl name can be adjusted to avoid conflicts if necessary.
                 if (iter.pass != .unnamed) return;
                 const prefix = @tagName(kind);
-                break :info .{
-                    (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(),
-                    .@"test",
-                    true,
-                };
+                break :name (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional();
             },
-            .@"const", .@"var" => info: {
+            .@"const", .@"var" => name: {
                 if (iter.pass != .named) return;
                 const name = try ip.getOrPutString(
                     gpa,
@@ -2038,11 +2052,7 @@ const ScanDeclIter = struct {
                     .no_embedded_nulls,
                 );
                 try iter.seen_decls.putNoClobber(gpa, name, {});
-                break :info .{
-                    name.toOptional(),
-                    .named,
-                    false,
-                };
+                break :name name.toOptional();
             },
         };
 
@@ -2051,46 +2061,44 @@ const ScanDeclIter = struct {
             .inst = decl_inst,
         });
 
-        const existing_cau = iter.existing_by_inst.get(tracked_inst);
+        const existing_unit = iter.existing_by_inst.get(tracked_inst);
 
-        const cau, const want_analysis = switch (kind) {
-            .@"comptime" => cau: {
-                const cau = existing_cau orelse try ip.createComptimeCau(gpa, pt.tid, tracked_inst, namespace_index);
+        const unit, const want_analysis = switch (decl.kind) {
+            .@"comptime" => unit: {
+                const cu = if (existing_unit) |eu|
+                    eu.unwrap().@"comptime"
+                else
+                    try ip.createComptimeUnit(gpa, pt.tid, tracked_inst, namespace_index);
 
-                try namespace.other_decls.append(gpa, cau);
+                const unit: AnalUnit = .wrap(.{ .@"comptime" = cu });
 
-                if (existing_cau == null) {
-                    // For a `comptime` declaration, whether to analyze is based solely on whether the
-                    // `Cau` is outdated. So, add this one to `outdated` and `outdated_ready` if not already.
-                    const unit = AnalUnit.wrap(.{ .cau = cau });
-                    if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| {
-                        try zcu.outdated.ensureUnusedCapacity(gpa, 1);
-                        try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
-                        zcu.outdated.putAssumeCapacityNoClobber(unit, kv.value);
-                        if (kv.value == 0) { // no PO deps
-                            zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
-                        }
-                    } else if (!zcu.outdated.contains(unit)) {
-                        try zcu.outdated.ensureUnusedCapacity(gpa, 1);
-                        try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
-                        zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
-                        zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
-                    }
+                try namespace.comptime_decls.append(gpa, cu);
+
+                if (existing_unit == null) {
+                    // For a `comptime` declaration, whether to analyze is based solely on whether the unit
+                    // is outdated. So, add this fresh one to `outdated` and `outdated_ready`.
+                    try zcu.outdated.ensureUnusedCapacity(gpa, 1);
+                    try zcu.outdated_ready.ensureUnusedCapacity(gpa, 1);
+                    zcu.outdated.putAssumeCapacityNoClobber(unit, 0);
+                    zcu.outdated_ready.putAssumeCapacityNoClobber(unit, {});
                 }
 
-                break :cau .{ cau, true };
+                break :unit .{ unit, true };
             },
-            else => cau: {
+            else => unit: {
                 const name = maybe_name.unwrap().?;
                 const fqn = try namespace.internFullyQualifiedName(ip, gpa, pt.tid, name);
-                const cau, const nav = if (existing_cau) |cau_index| cau_nav: {
-                    const nav_index = ip.getCau(cau_index).owner.unwrap().nav;
-                    const nav = ip.getNav(nav_index);
-                    assert(nav.name == name);
-                    assert(nav.fqn == fqn);
-                    break :cau_nav .{ cau_index, nav_index };
-                } else try ip.createPairedCauNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, kind == .@"usingnamespace");
-                const want_analysis = switch (kind) {
+                const nav = if (existing_unit) |eu|
+                    eu.unwrap().nav_val
+                else
+                    try ip.createDeclNav(gpa, pt.tid, name, fqn, tracked_inst, namespace_index, decl.kind == .@"usingnamespace");
+
+                const unit: AnalUnit = .wrap(.{ .nav_val = nav });
+
+                assert(ip.getNav(nav).name == name);
+                assert(ip.getNav(nav).fqn == fqn);
+
+                const want_analysis = switch (decl.kind) {
                     .@"comptime" => unreachable,
                     .@"usingnamespace" => a: {
                         if (comp.incremental) {
@@ -2103,8 +2111,9 @@ const ScanDeclIter = struct {
                         }
                         break :a true;
                     },
-                    .@"test" => a: {
-                        try namespace.other_decls.append(gpa, cau);
+                    .unnamed_test, .@"test", .decltest => a: {
+                        const is_named = decl.kind != .unnamed_test;
+                        try namespace.test_decls.append(gpa, nav);
                         // TODO: incremental compilation!
                         // * remove from `test_functions` if no longer matching filter
                         // * add to `test_functions` if newly passing filter
@@ -2112,7 +2121,7 @@ const ScanDeclIter = struct {
                         // Perhaps we should add all test indiscriminately and filter at the end of the update.
                         if (!comp.config.is_test) break :a false;
                         if (file.mod != zcu.main_mod) break :a false;
-                        if (is_named_test and comp.test_filters.len > 0) {
+                        if (is_named and comp.test_filters.len > 0) {
                             const fqn_slice = fqn.toSlice(ip);
                             for (comp.test_filters) |test_filter| {
                                 if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break;
@@ -2121,7 +2130,7 @@ const ScanDeclIter = struct {
                         try zcu.test_functions.put(gpa, nav, {});
                         break :a true;
                     },
-                    .named => a: {
+                    .@"const", .@"var" => a: {
                         if (decl.is_pub) {
                             try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
                         } else {
@@ -2130,23 +2139,23 @@ const ScanDeclIter = struct {
                         break :a false;
                     },
                 };
-                break :cau .{ cau, want_analysis };
+                break :unit .{ unit, want_analysis };
             },
         };
 
-        if (existing_cau == null and (want_analysis or decl.linkage == .@"export")) {
+        if (existing_unit == null and (want_analysis or decl.linkage == .@"export")) {
             log.debug(
-                "scanDecl queue analyze_cau file='{s}' cau_index={d}",
-                .{ namespace.fileScope(zcu).sub_file_path, cau },
+                "scanDecl queue analyze_comptime_unit file='{s}' unit={}",
+                .{ namespace.fileScope(zcu).sub_file_path, zcu.fmtAnalUnit(unit) },
             );
-            try comp.queueJob(.{ .analyze_cau = cau });
+            try comp.queueJob(.{ .analyze_comptime_unit = unit });
         }
 
         // TODO: we used to do line number updates here, but this is an inappropriate place for this logic to live.
     }
 };
 
-fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air {
+fn analyzeFnBodyInner(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!Air {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -2168,21 +2177,14 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
         func.setResolvedErrorSet(ip, .none);
     }
 
-    // This is the `Cau` corresponding to the `declaration` instruction which the function or its generic owner originates from.
-    const decl_cau = ip.getCau(cau: {
-        const orig_nav = if (func.generic_owner == .none)
-            func.owner_nav
-        else
-            zcu.funcInfo(func.generic_owner).owner_nav;
-
-        break :cau ip.getNav(orig_nav).analysis_owner.unwrap().?;
-    });
+    // This is the `Nau` corresponding to the `declaration` instruction which the function or its generic owner originates from.
+    const decl_nav = ip.getNav(if (func.generic_owner == .none)
+        func.owner_nav
+    else
+        zcu.funcInfo(func.generic_owner).owner_nav);
 
     const func_nav = ip.getNav(func.owner_nav);
 
-    const decl_prog_node = zcu.sema_prog_node.start(func_nav.fqn.toSlice(ip), 0);
-    defer decl_prog_node.end();
-
     zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
 
     var analysis_arena = std.heap.ArenaAllocator.init(gpa);
@@ -2216,7 +2218,7 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
 
     // Every runtime function has a dependency on the source of the Decl it originates from.
     // It also depends on the value of its owner Decl.
-    try sema.declareDependency(.{ .src_hash = decl_cau.zir_index });
+    try sema.declareDependency(.{ .src_hash = decl_nav.analysis.?.zir_index });
     try sema.declareDependency(.{ .nav_val = func.owner_nav });
 
     if (func.analysisUnordered(ip).inferred_error_set) {
@@ -2236,11 +2238,11 @@ fn analyzeFnBody(pt: Zcu.PerThread, func_index: InternPool.Index) Zcu.SemaError!
     var inner_block: Sema.Block = .{
         .parent = null,
         .sema = &sema,
-        .namespace = decl_cau.namespace,
+        .namespace = decl_nav.analysis.?.namespace,
         .instructions = .{},
         .inlining = null,
         .is_comptime = false,
-        .src_base_inst = decl_cau.zir_index,
+        .src_base_inst = decl_nav.analysis.?.zir_index,
         .type_name_ctx = func_nav.fqn,
     };
     defer inner_block.instructions.deinit(gpa);
@@ -2542,10 +2544,10 @@ fn processExportsInner(
         .nav => |nav_index| if (failed: {
             const nav = ip.getNav(nav_index);
             if (zcu.failed_codegen.contains(nav_index)) break :failed true;
-            if (nav.analysis_owner.unwrap()) |cau| {
-                const cau_unit = AnalUnit.wrap(.{ .cau = cau });
-                if (zcu.failed_analysis.contains(cau_unit)) break :failed true;
-                if (zcu.transitive_failed_analysis.contains(cau_unit)) break :failed true;
+            if (nav.analysis != null) {
+                const unit: AnalUnit = .wrap(.{ .nav_val = nav_index });
+                if (zcu.failed_analysis.contains(unit)) break :failed true;
+                if (zcu.transitive_failed_analysis.contains(unit)) break :failed true;
             }
             const val = switch (nav.status) {
                 .unresolved => break :failed true,
@@ -2593,15 +2595,14 @@ pub fn populateTestFunctions(
         Zcu.Namespace.NameAdapter{ .zcu = zcu },
     ).?;
     {
-        // We have to call `ensureCauAnalyzed` here in case `builtin.test_functions`
+        // We have to call `ensureNavValUpToDate` here in case `builtin.test_functions`
         // was not referenced by start code.
         zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0);
         defer {
             zcu.sema_prog_node.end();
             zcu.sema_prog_node = std.Progress.Node.none;
         }
-        const cau_index = ip.getNav(nav_index).analysis_owner.unwrap().?;
-        pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) {
+        pt.ensureNavValUpToDate(nav_index) catch |err| switch (err) {
             error.AnalysisFail => return,
             error.OutOfMemory => return error.OutOfMemory,
         };
@@ -2622,8 +2623,7 @@ pub fn populateTestFunctions(
             {
                 // The test declaration might have failed; if that's the case, just return, as we'll
                 // be emitting a compile error anyway.
-                const cau = test_nav.analysis_owner.unwrap().?;
-                const anal_unit: AnalUnit = .wrap(.{ .cau = cau });
+                const anal_unit: AnalUnit = .wrap(.{ .nav_val = test_nav_index });
                 if (zcu.failed_analysis.contains(anal_unit) or
                     zcu.transitive_failed_analysis.contains(anal_unit))
                 {
@@ -2748,8 +2748,8 @@ pub fn linkerUpdateNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) error
                     "unable to codegen: {s}",
                     .{@errorName(err)},
                 ));
-                if (nav.analysis_owner.unwrap()) |cau| {
-                    try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .cau = cau }));
+                if (nav.analysis != null) {
+                    try zcu.retryable_failures.append(zcu.gpa, .wrap(.{ .nav_val = nav_index }));
                 } else {
                     // TODO: we don't have a way to indicate that this failure is retryable!
                     // Since these are really rare, we could as a cop-out retry the whole build next update.
@@ -3255,7 +3255,7 @@ pub fn getBuiltinNav(pt: Zcu.PerThread, name: []const u8) Allocator.Error!Intern
     const builtin_str = try ip.getOrPutString(gpa, pt.tid, "builtin", .no_embedded_nulls);
     const builtin_nav = std_namespace.pub_decls.getKeyAdapted(builtin_str, Zcu.Namespace.NameAdapter{ .zcu = zcu }) orelse
         @panic("lib/std.zig is corrupt and missing 'builtin'");
-    pt.ensureCauAnalyzed(ip.getNav(builtin_nav).analysis_owner.unwrap().?) catch @panic("std.builtin is corrupt");
+    pt.ensureNavValUpToDate(builtin_nav) catch @panic("std.builtin is corrupt");
     const builtin_type = Type.fromInterned(ip.getNav(builtin_nav).status.resolved.val);
     const builtin_namespace = zcu.namespacePtr(builtin_type.getNamespace(zcu).unwrap() orelse @panic("std.builtin is corrupt"));
     const name_str = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
@@ -3307,68 +3307,45 @@ pub fn navAlignment(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) InternPo
 /// Given a container type requiring resolution, ensures that it is up-to-date.
 /// If not, the type is recreated at a new `InternPool.Index`.
 /// The new index is returned. This is the same as the old index if the fields were up-to-date.
-/// If `already_updating` is set, assumes the type is already outdated and undergoing re-analysis rather than checking `zcu.outdated`.
-pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index, already_updating: bool) Zcu.SemaError!InternPool.Index {
+pub fn ensureTypeUpToDate(pt: Zcu.PerThread, ty: InternPool.Index) Zcu.SemaError!InternPool.Index {
     const zcu = pt.zcu;
+    const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
+
+    const anal_unit: AnalUnit = .wrap(.{ .type = ty });
+    const outdated = zcu.outdated.swapRemove(anal_unit) or
+        zcu.potentially_outdated.swapRemove(anal_unit);
+
+    if (!outdated) return ty;
+
+    // We will recreate the type at a new `InternPool.Index`.
+
+    _ = zcu.outdated_ready.swapRemove(anal_unit);
+    try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
+
+    // Delete old state which is no longer in use. Technically, this is not necessary: these exports,
+    // references, etc, will be ignored because the type itself is unreferenced. However, it allows
+    // reusing the memory which is currently being used to track this state.
+    zcu.deleteUnitExports(anal_unit);
+    zcu.deleteUnitReferences(anal_unit);
+    if (zcu.failed_analysis.fetchSwapRemove(anal_unit)) |kv| {
+        kv.value.destroy(gpa);
+    }
+    _ = zcu.transitive_failed_analysis.swapRemove(anal_unit);
+    zcu.intern_pool.removeDependenciesForDepender(gpa, anal_unit);
+
     switch (ip.indexToKey(ty)) {
-        .struct_type => |key| {
-            const struct_obj = ip.loadStructType(ty);
-            const outdated = already_updating or o: {
-                const anal_unit = AnalUnit.wrap(.{ .cau = struct_obj.cau });
-                const o = zcu.outdated.swapRemove(anal_unit) or
-                    zcu.potentially_outdated.swapRemove(anal_unit);
-                if (o) {
-                    _ = zcu.outdated_ready.swapRemove(anal_unit);
-                    try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
-                }
-                break :o o;
-            };
-            if (!outdated) return ty;
-            return pt.recreateStructType(key, struct_obj);
-        },
-        .union_type => |key| {
-            const union_obj = ip.loadUnionType(ty);
-            const outdated = already_updating or o: {
-                const anal_unit = AnalUnit.wrap(.{ .cau = union_obj.cau });
-                const o = zcu.outdated.swapRemove(anal_unit) or
-                    zcu.potentially_outdated.swapRemove(anal_unit);
-                if (o) {
-                    _ = zcu.outdated_ready.swapRemove(anal_unit);
-                    try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
-                }
-                break :o o;
-            };
-            if (!outdated) return ty;
-            return pt.recreateUnionType(key, union_obj);
-        },
-        .enum_type => |key| {
-            const enum_obj = ip.loadEnumType(ty);
-            const outdated = already_updating or o: {
-                const anal_unit = AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? });
-                const o = zcu.outdated.swapRemove(anal_unit) or
-                    zcu.potentially_outdated.swapRemove(anal_unit);
-                if (o) {
-                    _ = zcu.outdated_ready.swapRemove(anal_unit);
-                    try zcu.markDependeeOutdated(.marked_po, .{ .interned = ty });
-                }
-                break :o o;
-            };
-            if (!outdated) return ty;
-            return pt.recreateEnumType(key, enum_obj);
-        },
-        .opaque_type => {
-            assert(!already_updating);
-            return ty;
-        },
+        .struct_type => |key| return pt.recreateStructType(ty, key),
+        .union_type => |key| return pt.recreateUnionType(ty, key),
+        .enum_type => |key| return pt.recreateEnumType(ty, key),
         else => unreachable,
     }
 }
 
 fn recreateStructType(
     pt: Zcu.PerThread,
+    old_ty: InternPool.Index,
     full_key: InternPool.Key.NamespaceType,
-    struct_obj: InternPool.LoadedStructType,
 ) Zcu.SemaError!InternPool.Index {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
@@ -3405,8 +3382,7 @@ fn recreateStructType(
 
     if (captures_len != key.captures.owned.len) return error.AnalysisFail;
 
-    // The old type will be unused, so drop its dependency information.
-    ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = struct_obj.cau }));
+    const struct_obj = ip.loadStructType(old_ty);
 
     const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
         .layout = small.layout,
@@ -3428,17 +3404,16 @@ fn recreateStructType(
     errdefer wip_ty.cancel(ip, pt.tid);
 
     wip_ty.setName(ip, struct_obj.name);
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, struct_obj.namespace, wip_ty.index);
     try ip.addDependency(
         gpa,
-        AnalUnit.wrap(.{ .cau = new_cau_index }),
+        .wrap(.{ .type = wip_ty.index }),
         .{ .src_hash = key.zir_index },
     );
     zcu.namespacePtr(struct_obj.namespace).owner_type = wip_ty.index;
     // No need to re-scan the namespace -- `zirStructDecl` will ultimately do that if the type is still alive.
     try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
 
-    const new_ty = wip_ty.finish(ip, new_cau_index.toOptional(), struct_obj.namespace);
+    const new_ty = wip_ty.finish(ip, struct_obj.namespace);
     if (inst_info.inst == .main_struct_inst) {
         // This is the root type of a file! Update the reference.
         zcu.setFileRootType(inst_info.file, new_ty);
@@ -3448,8 +3423,8 @@ fn recreateStructType(
 
 fn recreateUnionType(
     pt: Zcu.PerThread,
+    old_ty: InternPool.Index,
     full_key: InternPool.Key.NamespaceType,
-    union_obj: InternPool.LoadedUnionType,
 ) Zcu.SemaError!InternPool.Index {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
@@ -3488,8 +3463,7 @@ fn recreateUnionType(
 
     if (captures_len != key.captures.owned.len) return error.AnalysisFail;
 
-    // The old type will be unused, so drop its dependency information.
-    ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = union_obj.cau }));
+    const union_obj = ip.loadUnionType(old_ty);
 
     const namespace_index = union_obj.namespace;
 
@@ -3526,22 +3500,21 @@ fn recreateUnionType(
     errdefer wip_ty.cancel(ip, pt.tid);
 
     wip_ty.setName(ip, union_obj.name);
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index);
     try ip.addDependency(
         gpa,
-        AnalUnit.wrap(.{ .cau = new_cau_index }),
+        .wrap(.{ .type = wip_ty.index }),
         .{ .src_hash = key.zir_index },
     );
     zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
     // No need to re-scan the namespace -- `zirUnionDecl` will ultimately do that if the type is still alive.
     try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
-    return wip_ty.finish(ip, new_cau_index.toOptional(), namespace_index);
+    return wip_ty.finish(ip, namespace_index);
 }
 
 fn recreateEnumType(
     pt: Zcu.PerThread,
+    old_ty: InternPool.Index,
     full_key: InternPool.Key.NamespaceType,
-    enum_obj: InternPool.LoadedEnumType,
 ) Zcu.SemaError!InternPool.Index {
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
@@ -3610,8 +3583,7 @@ fn recreateEnumType(
         if (bag != 0) break true;
     } else false;
 
-    // The old type will be unused, so drop its dependency information.
-    ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .cau = enum_obj.cau.unwrap().? }));
+    const enum_obj = ip.loadEnumType(old_ty);
 
     const namespace_index = enum_obj.namespace;
 
@@ -3637,12 +3609,10 @@ fn recreateEnumType(
 
     wip_ty.setName(ip, enum_obj.name);
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, key.zir_index, namespace_index, wip_ty.index);
-
     zcu.namespacePtr(namespace_index).owner_type = wip_ty.index;
     // No need to re-scan the namespace -- `zirEnumDecl` will ultimately do that if the type is still alive.
 
-    wip_ty.prepare(ip, new_cau_index, namespace_index);
+    wip_ty.prepare(ip, namespace_index);
     done = true;
 
     Sema.resolveDeclaredEnum(
@@ -3652,7 +3622,6 @@ fn recreateEnumType(
         key.zir_index,
         namespace_index,
         enum_obj.name,
-        new_cau_index,
         small,
         body,
         tag_type_ref,
src/Compilation.zig
@@ -348,12 +348,15 @@ const Job = union(enum) {
     /// Corresponds to the task in `link.Task`.
     /// Only needed for backends that haven't yet been updated to not race against Sema.
     codegen_type: InternPool.Index,
-    /// The `Cau` must be semantically analyzed (and possibly export itself).
+    /// The `AnalUnit`, which is *not* a `func`, must be semantically analyzed.
+    /// This may be its first time being analyzed, or it may be outdated.
+    /// If the unit is a function, a `codegen_func` job will then be queued.
+    analyze_comptime_unit: InternPool.AnalUnit,
+    /// This function must be semantically analyzed.
     /// This may be its first time being analyzed, or it may be outdated.
-    analyze_cau: InternPool.Cau.Index,
-    /// Analyze the body of a runtime function.
     /// After analysis, a `codegen_func` job will be queued.
     /// These must be separate jobs to ensure any needed type resolution occurs *before* codegen.
+    /// This job is separate from `analyze_comptime_unit` because it has a different priority.
     analyze_func: InternPool.Index,
     /// The main source file for the module needs to be analyzed.
     analyze_mod: *Package.Module,
@@ -3141,8 +3144,10 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle {
             }
 
             const file_index = switch (anal_unit.unwrap()) {
-                .cau => |cau| zcu.namespacePtr(ip.getCau(cau).namespace).file_scope,
-                .func => |ip_index| (zcu.funcInfo(ip_index).zir_body_inst.resolveFull(ip) orelse continue).file,
+                .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index.resolveFile(ip),
+                .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index.resolveFile(ip),
+                .type => |ty| Type.fromInterned(ty).typeDeclInst(zcu).?.resolveFile(ip),
+                .func => |ip_index| zcu.funcInfo(ip_index).zir_body_inst.resolveFile(ip),
             };
 
             // Skip errors for AnalUnits within files that had a parse failure.
@@ -3374,11 +3379,9 @@ pub fn addModuleErrorMsg(
                 const rt_file_path = try src.file_scope.fullPath(gpa);
                 defer gpa.free(rt_file_path);
                 const name = switch (ref.referencer.unwrap()) {
-                    .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) {
-                        .nav => |nav| ip.getNav(nav).name.toSlice(ip),
-                        .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
-                        .none => "comptime",
-                    },
+                    .@"comptime" => "comptime",
+                    .nav_val => |nav| ip.getNav(nav).name.toSlice(ip),
+                    .type => |ty| Type.fromInterned(ty).containerTypeName(ip).toSlice(ip),
                     .func => |f| ip.getNav(zcu.funcInfo(f).owner_nav).name.toSlice(ip),
                 };
                 try ref_traces.append(gpa, .{
@@ -3641,10 +3644,13 @@ fn performAllTheWorkInner(
             // If there's no work queued, check if there's anything outdated
             // which we need to work on, and queue it if so.
             if (try zcu.findOutdatedToAnalyze()) |outdated| {
-                switch (outdated.unwrap()) {
-                    .cau => |cau| try comp.queueJob(.{ .analyze_cau = cau }),
-                    .func => |func| try comp.queueJob(.{ .analyze_func = func }),
-                }
+                try comp.queueJob(switch (outdated.unwrap()) {
+                    .func => |f| .{ .analyze_func = f },
+                    .@"comptime",
+                    .nav_val,
+                    .type,
+                    => .{ .analyze_comptime_unit = outdated },
+                });
                 continue;
             }
         }
@@ -3667,8 +3673,8 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
         .codegen_nav => |nav_index| {
             const zcu = comp.zcu.?;
             const nav = zcu.intern_pool.getNav(nav_index);
-            if (nav.analysis_owner.unwrap()) |cau| {
-                const unit = InternPool.AnalUnit.wrap(.{ .cau = cau });
+            if (nav.analysis != null) {
+                const unit: InternPool.AnalUnit = .wrap(.{ .nav_val = nav_index });
                 if (zcu.failed_analysis.contains(unit) or zcu.transitive_failed_analysis.contains(unit)) {
                     return;
                 }
@@ -3688,36 +3694,47 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre
 
             const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
             defer pt.deactivate();
-            pt.ensureFuncBodyAnalyzed(func) catch |err| switch (err) {
-                error.OutOfMemory => return error.OutOfMemory,
+
+            pt.ensureFuncBodyUpToDate(func) catch |err| switch (err) {
+                error.OutOfMemory => |e| return e,
                 error.AnalysisFail => return,
             };
         },
-        .analyze_cau => |cau_index| {
+        .analyze_comptime_unit => |unit| {
+            const named_frame = tracy.namedFrame("analyze_comptime_unit");
+            defer named_frame.end();
+
             const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid));
             defer pt.deactivate();
-            pt.ensureCauAnalyzed(cau_index) catch |err| switch (err) {
-                error.OutOfMemory => return error.OutOfMemory,
+
+            const maybe_err: Zcu.SemaError!void = switch (unit.unwrap()) {
+                .@"comptime" => |cu| pt.ensureComptimeUnitUpToDate(cu),
+                .nav_val => |nav| pt.ensureNavValUpToDate(nav),
+                .type => |ty| if (pt.ensureTypeUpToDate(ty)) |_| {} else |err| err,
+                .func => unreachable,
+            };
+            maybe_err catch |err| switch (err) {
+                error.OutOfMemory => |e| return e,
                 error.AnalysisFail => return,
             };
+
             queue_test_analysis: {
                 if (!comp.config.is_test) break :queue_test_analysis;
+                const nav = switch (unit.unwrap()) {
+                    .nav_val => |nav| nav,
+                    else => break :queue_test_analysis,
+                };
 
                 // Check if this is a test function.
                 const ip = &pt.zcu.intern_pool;
-                const cau = ip.getCau(cau_index);
-                const nav_index = switch (cau.owner.unwrap()) {
-                    .none, .type => break :queue_test_analysis,
-                    .nav => |nav| nav,
-                };
-                if (!pt.zcu.test_functions.contains(nav_index)) {
+                if (!pt.zcu.test_functions.contains(nav)) {
                     break :queue_test_analysis;
                 }
 
                 // Tests are always emitted in test binaries. The decl_refs are created by
                 // Zcu.populateTestFunctions, but this will not queue body analysis, so do
                 // that now.
-                try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav_index).status.resolved.val);
+                try pt.zcu.ensureFuncBodyAnalysisQueued(ip.getNav(nav).status.resolved.val);
             }
         },
         .resolve_type_fully => |ty| {
src/InternPool.zig
@@ -363,33 +363,53 @@ pub fn rehashTrackedInsts(
 }
 
 /// Analysis Unit. Represents a single entity which undergoes semantic analysis.
-/// This is either a `Cau` or a runtime function.
-/// The LSB is used as a tag bit.
 /// This is the "source" of an incremental dependency edge.
-pub const AnalUnit = packed struct(u32) {
-    kind: enum(u1) { cau, func },
-    index: u31,
-    pub const Unwrapped = union(enum) {
-        cau: Cau.Index,
+pub const AnalUnit = packed struct(u64) {
+    kind: Kind,
+    id: u32,
+
+    pub const Kind = enum(u32) {
+        @"comptime",
+        nav_val,
+        type,
+        func,
+    };
+
+    pub const Unwrapped = union(Kind) {
+        /// This `AnalUnit` analyzes the body of the given `comptime` declaration.
+        @"comptime": ComptimeUnit.Id,
+        /// This `AnalUnit` resolves the value of the given `Nav`.
+        nav_val: Nav.Index,
+        /// This `AnalUnit` resolves the given `struct`/`union`/`enum` type.
+        /// Generated tag enums are never used here (they do not undergo type resolution).
+        type: InternPool.Index,
+        /// This `AnalUnit` analyzes the body of the given runtime function.
         func: InternPool.Index,
     };
-    pub fn unwrap(as: AnalUnit) Unwrapped {
-        return switch (as.kind) {
-            .cau => .{ .cau = @enumFromInt(as.index) },
-            .func => .{ .func = @enumFromInt(as.index) },
+
+    pub fn unwrap(au: AnalUnit) Unwrapped {
+        return switch (au.kind) {
+            inline else => |tag| @unionInit(
+                Unwrapped,
+                @tagName(tag),
+                @enumFromInt(au.id),
+            ),
         };
     }
     pub fn wrap(raw: Unwrapped) AnalUnit {
         return switch (raw) {
-            .cau => |cau| .{ .kind = .cau, .index = @intCast(@intFromEnum(cau)) },
-            .func => |func| .{ .kind = .func, .index = @intCast(@intFromEnum(func)) },
+            inline else => |id, tag| .{
+                .kind = tag,
+                .id = @intFromEnum(id),
+            },
         };
     }
+
     pub fn toOptional(as: AnalUnit) Optional {
-        return @enumFromInt(@as(u32, @bitCast(as)));
+        return @enumFromInt(@as(u64, @bitCast(as)));
     }
-    pub const Optional = enum(u32) {
-        none = std.math.maxInt(u32),
+    pub const Optional = enum(u64) {
+        none = std.math.maxInt(u64),
         _,
         pub fn unwrap(opt: Optional) ?AnalUnit {
             return switch (opt) {
@@ -400,97 +420,30 @@ pub const AnalUnit = packed struct(u32) {
     };
 };
 
-/// Comptime Analysis Unit. This is the "subject" of semantic analysis where the root context is
-/// comptime; every `Sema` is owned by either a `Cau` or a runtime function (see `AnalUnit`).
-/// The state stored here is immutable.
-///
-/// * Every ZIR `declaration` has a `Cau` (post-instantiation) to analyze the declaration body.
-/// * Every `struct`, `union`, and `enum` has a `Cau` for type resolution.
-///
-/// The analysis status of a `Cau` is known only from state in `Zcu`.
-/// An entry in `Zcu.failed_analysis` indicates an analysis failure with associated error message.
-/// An entry in `Zcu.transitive_failed_analysis` indicates a transitive analysis failure.
-///
-/// 12 bytes.
-pub const Cau = struct {
-    /// The `declaration`, `struct_decl`, `enum_decl`, or `union_decl` instruction which this `Cau` analyzes.
+pub const ComptimeUnit = extern struct {
     zir_index: TrackedInst.Index,
-    /// The namespace which this `Cau` should be analyzed within.
     namespace: NamespaceIndex,
-    /// This field essentially tells us what to do with the information resulting from
-    /// semantic analysis. See `Owner.Unwrapped` for details.
-    owner: Owner,
-
-    /// See `Owner.Unwrapped` for details. In terms of representation, the `InternPool.Index`
-    /// or `Nav.Index` is cast to a `u31` and stored in `index`. As a special case, if
-    /// `@as(u32, @bitCast(owner)) == 0xFFFF_FFFF`, then the value is treated as `.none`.
-    pub const Owner = packed struct(u32) {
-        kind: enum(u1) { type, nav },
-        index: u31,
-
-        pub const Unwrapped = union(enum) {
-            /// This `Cau` exists in isolation. It is a global `comptime` declaration, or (TODO ANYTHING ELSE?).
-            /// After semantic analysis completes, the result is discarded.
-            none,
-            /// This `Cau` is owned by the given type for type resolution.
-            /// This is a `struct`, `union`, or `enum` type.
-            type: InternPool.Index,
-            /// This `Cau` is owned by the given `Nav` to resolve its value.
-            /// When analyzing the `Cau`, the resulting value is stored as the value of this `Nav`.
-            nav: Nav.Index,
-        };
 
-        pub fn unwrap(owner: Owner) Unwrapped {
-            if (@as(u32, @bitCast(owner)) == std.math.maxInt(u32)) {
-                return .none;
-            }
-            return switch (owner.kind) {
-                .type => .{ .type = @enumFromInt(owner.index) },
-                .nav => .{ .nav = @enumFromInt(owner.index) },
-            };
-        }
-
-        fn wrap(raw: Unwrapped) Owner {
-            return switch (raw) {
-                .none => @bitCast(@as(u32, std.math.maxInt(u32))),
-                .type => |ty| .{ .kind = .type, .index = @intCast(@intFromEnum(ty)) },
-                .nav => |nav| .{ .kind = .nav, .index = @intCast(@intFromEnum(nav)) },
-            };
-        }
-    };
+    comptime {
+        assert(std.meta.hasUniqueRepresentation(ComptimeUnit));
+    }
 
-    pub const Index = enum(u32) {
+    pub const Id = enum(u32) {
         _,
-        pub const Optional = enum(u32) {
-            none = std.math.maxInt(u32),
-            _,
-            pub fn unwrap(opt: Optional) ?Cau.Index {
-                return switch (opt) {
-                    .none => null,
-                    _ => @enumFromInt(@intFromEnum(opt)),
-                };
-            }
-
-            const debug_state = InternPool.debug_state;
-        };
-        pub fn toOptional(i: Cau.Index) Optional {
-            return @enumFromInt(@intFromEnum(i));
-        }
         const Unwrapped = struct {
             tid: Zcu.PerThread.Id,
             index: u32,
-
-            fn wrap(unwrapped: Unwrapped, ip: *const InternPool) Cau.Index {
+            fn wrap(unwrapped: Unwrapped, ip: *const InternPool) ComptimeUnit.Id {
                 assert(@intFromEnum(unwrapped.tid) <= ip.getTidMask());
-                assert(unwrapped.index <= ip.getIndexMask(u31));
-                return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_31 |
+                assert(unwrapped.index <= ip.getIndexMask(u32));
+                return @enumFromInt(@as(u32, @intFromEnum(unwrapped.tid)) << ip.tid_shift_32 |
                     unwrapped.index);
             }
         };
-        fn unwrap(cau_index: Cau.Index, ip: *const InternPool) Unwrapped {
+        fn unwrap(id: Id, ip: *const InternPool) Unwrapped {
             return .{
-                .tid = @enumFromInt(@intFromEnum(cau_index) >> ip.tid_shift_31 & ip.getTidMask()),
-                .index = @intFromEnum(cau_index) & ip.getIndexMask(u31),
+                .tid = @enumFromInt(@intFromEnum(id) >> ip.tid_shift_32 & ip.getTidMask()),
+                .index = @intFromEnum(id) & ip.getIndexMask(u31),
             };
         }
 
@@ -507,6 +460,11 @@ pub const Cau = struct {
 /// * Generic instances have a `Nav` corresponding to the instantiated function.
 /// * `@extern` calls create a `Nav` whose value is a `.@"extern"`.
 ///
+/// This data structure is optimized for the `analysis_info != null` case, because this is much more
+/// common in practice; the other case is used only for externs and for generic instances. At the time
+/// of writing, in the compiler itself, around 74% of all `Nav`s have `analysis_info != null`.
+/// (Specifically, 104225 / 140923)
+///
 /// `Nav.Repr` is the in-memory representation.
 pub const Nav = struct {
     /// The unqualified name of this `Nav`. Namespace lookups use this name, and error messages may use it.
@@ -514,13 +472,16 @@ pub const Nav = struct {
     name: NullTerminatedString,
     /// The fully-qualified name of this `Nav`.
     fqn: NullTerminatedString,
-    /// If the value of this `Nav` is resolved by semantic analysis, it is within this `Cau`.
-    /// If this is `.none`, then `status == .resolved` always.
-    analysis_owner: Cau.Index.Optional,
+    /// This field is populated iff this `Nav` is resolved by semantic analysis.
+    /// If this is `null`, then `status == .resolved` always.
+    analysis: ?struct {
+        namespace: NamespaceIndex,
+        zir_index: TrackedInst.Index,
+    },
     /// TODO: this is a hack! If #20663 isn't accepted, let's figure out something a bit better.
     is_usingnamespace: bool,
     status: union(enum) {
-        /// This `Nav` is pending semantic analysis through `analysis_owner`.
+        /// This `Nav` is pending semantic analysis.
         unresolved,
         /// The value of this `Nav` is resolved.
         resolved: struct {
@@ -544,17 +505,16 @@ pub const Nav = struct {
     /// Get the ZIR instruction corresponding to this `Nav`, used to resolve source locations.
     /// This is a `declaration`.
     pub fn srcInst(nav: Nav, ip: *const InternPool) TrackedInst.Index {
-        if (nav.analysis_owner.unwrap()) |cau| {
-            return ip.getCau(cau).zir_index;
+        if (nav.analysis) |a| {
+            return a.zir_index;
         }
-        // A `Nav` with no corresponding `Cau` always has a resolved value.
+        // A `Nav` which does not undergo analysis always has a resolved value.
         return switch (ip.indexToKey(nav.status.resolved.val)) {
             .func => |func| {
-                // Since there was no `analysis_owner`, this must be an instantiation.
-                // Go up to the generic owner and consult *its* `analysis_owner`.
+                // Since `analysis` was not populated, this must be an instantiation.
+                // Go up to the generic owner and consult *its* `analysis` field.
                 const go_nav = ip.getNav(ip.indexToKey(func.generic_owner).func.owner_nav);
-                const go_cau = ip.getCau(go_nav.analysis_owner.unwrap().?);
-                return go_cau.zir_index;
+                return go_nav.analysis.?.zir_index;
             },
             .@"extern" => |@"extern"| @"extern".zir_index, // extern / @extern
             else => unreachable,
@@ -600,11 +560,13 @@ pub const Nav = struct {
     };
 
     /// The compact in-memory representation of a `Nav`.
-    /// 18 bytes.
+    /// 26 bytes.
     const Repr = struct {
         name: NullTerminatedString,
         fqn: NullTerminatedString,
-        analysis_owner: Cau.Index.Optional,
+        // The following 1 fields are either both populated, or both `.none`.
+        analysis_namespace: OptionalNamespaceIndex,
+        analysis_zir_index: TrackedInst.Index.Optional,
         /// Populated only if `bits.status == .resolved`.
         val: InternPool.Index,
         /// Populated only if `bits.status == .resolved`.
@@ -625,7 +587,13 @@ pub const Nav = struct {
             return .{
                 .name = repr.name,
                 .fqn = repr.fqn,
-                .analysis_owner = repr.analysis_owner,
+                .analysis = if (repr.analysis_namespace.unwrap()) |namespace| .{
+                    .namespace = namespace,
+                    .zir_index = repr.analysis_zir_index.unwrap().?,
+                } else a: {
+                    assert(repr.analysis_zir_index == .none);
+                    break :a null;
+                },
                 .is_usingnamespace = repr.bits.is_usingnamespace,
                 .status = switch (repr.bits.status) {
                     .unresolved => .unresolved,
@@ -646,7 +614,8 @@ pub const Nav = struct {
         return .{
             .name = nav.name,
             .fqn = nav.fqn,
-            .analysis_owner = nav.analysis_owner,
+            .analysis_namespace = if (nav.analysis) |a| a.namespace.toOptional() else .none,
+            .analysis_zir_index = if (nav.analysis) |a| a.zir_index.toOptional() else .none,
             .val = switch (nav.status) {
                 .unresolved => .none,
                 .resolved => |r| r.val,
@@ -862,8 +831,8 @@ const Local = struct {
         tracked_insts: ListMutate,
         files: ListMutate,
         maps: ListMutate,
-        caus: ListMutate,
         navs: ListMutate,
+        comptime_units: ListMutate,
 
         namespaces: BucketListMutate,
     } align(std.atomic.cache_line),
@@ -876,8 +845,8 @@ const Local = struct {
         tracked_insts: TrackedInsts,
         files: List(File),
         maps: Maps,
-        caus: Caus,
         navs: Navs,
+        comptime_units: ComptimeUnits,
 
         namespaces: Namespaces,
 
@@ -899,8 +868,8 @@ const Local = struct {
     const Strings = List(struct { u8 });
     const TrackedInsts = List(struct { TrackedInst.MaybeLost });
     const Maps = List(struct { FieldMap });
-    const Caus = List(struct { Cau });
     const Navs = List(Nav.Repr);
+    const ComptimeUnits = List(struct { ComptimeUnit });
 
     const namespaces_bucket_width = 8;
     const namespaces_bucket_mask = (1 << namespaces_bucket_width) - 1;
@@ -1275,21 +1244,21 @@ const Local = struct {
         };
     }
 
-    pub fn getMutableCaus(local: *Local, gpa: Allocator) Caus.Mutable {
+    pub fn getMutableNavs(local: *Local, gpa: Allocator) Navs.Mutable {
         return .{
             .gpa = gpa,
             .arena = &local.mutate.arena,
-            .mutate = &local.mutate.caus,
-            .list = &local.shared.caus,
+            .mutate = &local.mutate.navs,
+            .list = &local.shared.navs,
         };
     }
 
-    pub fn getMutableNavs(local: *Local, gpa: Allocator) Navs.Mutable {
+    pub fn getMutableComptimeUnits(local: *Local, gpa: Allocator) ComptimeUnits.Mutable {
         return .{
             .gpa = gpa,
             .arena = &local.mutate.arena,
-            .mutate = &local.mutate.navs,
-            .list = &local.shared.navs,
+            .mutate = &local.mutate.comptime_units,
+            .list = &local.shared.comptime_units,
         };
     }
 
@@ -3052,8 +3021,6 @@ pub const LoadedUnionType = struct {
     // TODO: the non-fqn will be needed by the new dwarf structure
     /// The name of this union type.
     name: NullTerminatedString,
-    /// The `Cau` within which type resolution occurs.
-    cau: Cau.Index,
     /// Represents the declarations inside this union.
     namespace: NamespaceIndex,
     /// The enum tag type.
@@ -3370,7 +3337,6 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType {
         .tid = unwrapped_index.tid,
         .extra_index = data,
         .name = type_union.data.name,
-        .cau = type_union.data.cau,
         .namespace = type_union.data.namespace,
         .enum_tag_ty = type_union.data.tag_ty,
         .field_types = field_types,
@@ -3387,8 +3353,6 @@ pub const LoadedStructType = struct {
     // TODO: the non-fqn will be needed by the new dwarf structure
     /// The name of this struct type.
     name: NullTerminatedString,
-    /// The `Cau` within which type resolution occurs.
-    cau: Cau.Index,
     namespace: NamespaceIndex,
     /// Index of the `struct_decl` or `reify` ZIR instruction.
     zir_index: TrackedInst.Index,
@@ -3979,7 +3943,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
     switch (item.tag) {
         .type_struct => {
             const name: NullTerminatedString = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "name").?]);
-            const cau: Cau.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "cau").?]);
             const namespace: NamespaceIndex = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "namespace").?]);
             const zir_index: TrackedInst.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "zir_index").?]);
             const fields_len = extra_items[item.data + std.meta.fieldIndex(Tag.TypeStruct, "fields_len").?];
@@ -4066,7 +4029,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
                 .tid = unwrapped_index.tid,
                 .extra_index = item.data,
                 .name = name,
-                .cau = cau,
                 .namespace = namespace,
                 .zir_index = zir_index,
                 .layout = if (flags.is_extern) .@"extern" else .auto,
@@ -4083,7 +4045,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
         },
         .type_struct_packed, .type_struct_packed_inits => {
             const name: NullTerminatedString = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "name").?]);
-            const cau: Cau.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "cau").?]);
             const zir_index: TrackedInst.Index = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "zir_index").?]);
             const fields_len = extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "fields_len").?];
             const namespace: NamespaceIndex = @enumFromInt(extra_items[item.data + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?]);
@@ -4130,7 +4091,6 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType {
                 .tid = unwrapped_index.tid,
                 .extra_index = item.data,
                 .name = name,
-                .cau = cau,
                 .namespace = namespace,
                 .zir_index = zir_index,
                 .layout = .@"packed",
@@ -4153,9 +4113,6 @@ pub const LoadedEnumType = struct {
     // TODO: the non-fqn will be needed by the new dwarf structure
     /// The name of this enum type.
     name: NullTerminatedString,
-    /// The `Cau` within which type resolution occurs.
-    /// `null` if this is a generated tag type.
-    cau: Cau.Index.Optional,
     /// Represents the declarations inside this enum.
     namespace: NamespaceIndex,
     /// An integer type which is used for the numerical value of the enum.
@@ -4232,21 +4189,15 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
         .type_enum_auto => {
             const extra = extraDataTrail(extra_list, EnumAuto, item.data);
             var extra_index: u32 = @intCast(extra.end);
-            const cau: Cau.Index.Optional = if (extra.data.zir_index == .none) cau: {
+            if (extra.data.zir_index == .none) {
                 extra_index += 1; // owner_union
-                break :cau .none;
-            } else cau: {
-                const cau: Cau.Index = @enumFromInt(extra_list.view().items(.@"0")[extra_index]);
-                extra_index += 1; // cau
-                break :cau cau.toOptional();
-            };
+            }
             const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: {
                 extra_index += 2; // type_hash: PackedU64
                 break :c 0;
             } else extra.data.captures_len;
             return .{
                 .name = extra.data.name,
-                .cau = cau,
                 .namespace = extra.data.namespace,
                 .tag_ty = extra.data.int_tag_type,
                 .names = .{
@@ -4272,21 +4223,15 @@ pub fn loadEnumType(ip: *const InternPool, index: Index) LoadedEnumType {
     };
     const extra = extraDataTrail(extra_list, EnumExplicit, item.data);
     var extra_index: u32 = @intCast(extra.end);
-    const cau: Cau.Index.Optional = if (extra.data.zir_index == .none) cau: {
+    if (extra.data.zir_index == .none) {
         extra_index += 1; // owner_union
-        break :cau .none;
-    } else cau: {
-        const cau: Cau.Index = @enumFromInt(extra_list.view().items(.@"0")[extra_index]);
-        extra_index += 1; // cau
-        break :cau cau.toOptional();
-    };
+    }
     const captures_len = if (extra.data.captures_len == std.math.maxInt(u32)) c: {
         extra_index += 2; // type_hash: PackedU64
         break :c 0;
     } else extra.data.captures_len;
     return .{
         .name = extra.data.name,
-        .cau = cau,
         .namespace = extra.data.namespace,
         .tag_ty = extra.data.int_tag_type,
         .names = .{
@@ -5256,7 +5201,6 @@ pub const Tag = enum(u8) {
         .payload = EnumExplicit,
         .trailing = struct {
             owner_union: Index,
-            cau: ?Cau.Index,
             captures: ?[]CaptureValue,
             type_hash: ?u64,
             field_names: []NullTerminatedString,
@@ -5302,7 +5246,6 @@ pub const Tag = enum(u8) {
             .payload = EnumAuto,
             .trailing = struct {
                 owner_union: ?Index,
-                cau: ?Cau.Index,
                 captures: ?[]CaptureValue,
                 type_hash: ?u64,
                 field_names: []NullTerminatedString,
@@ -5679,7 +5622,6 @@ pub const Tag = enum(u8) {
         size: u32,
         /// Only valid after .have_layout
         padding: u32,
-        cau: Cau.Index,
         namespace: NamespaceIndex,
         /// The enum that provides the list of field names and values.
         tag_ty: Index,
@@ -5710,7 +5652,6 @@ pub const Tag = enum(u8) {
     /// 5. init: Index for each fields_len // if tag is type_struct_packed_inits
     pub const TypeStructPacked = struct {
         name: NullTerminatedString,
-        cau: Cau.Index,
         zir_index: TrackedInst.Index,
         fields_len: u32,
         namespace: NamespaceIndex,
@@ -5758,7 +5699,6 @@ pub const Tag = enum(u8) {
     /// 8. field_offset: u32 // for each field in declared order, undef until layout_resolved
     pub const TypeStruct = struct {
         name: NullTerminatedString,
-        cau: Cau.Index,
         zir_index: TrackedInst.Index,
         namespace: NamespaceIndex,
         fields_len: u32,
@@ -6088,11 +6028,10 @@ pub const Array = struct {
 
 /// Trailing:
 /// 0. owner_union: Index // if `zir_index == .none`
-/// 1. cau: Cau.Index // if `zir_index != .none`
-/// 2. capture: CaptureValue // for each `captures_len`
-/// 3. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`)
-/// 4. field name: NullTerminatedString for each fields_len; declaration order
-/// 5. tag value: Index for each fields_len; declaration order
+/// 1. capture: CaptureValue // for each `captures_len`
+/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`)
+/// 3. field name: NullTerminatedString for each fields_len; declaration order
+/// 4. tag value: Index for each fields_len; declaration order
 pub const EnumExplicit = struct {
     name: NullTerminatedString,
     /// `std.math.maxInt(u32)` indicates this type is reified.
@@ -6115,10 +6054,9 @@ pub const EnumExplicit = struct {
 
 /// Trailing:
 /// 0. owner_union: Index // if `zir_index == .none`
-/// 1. cau: Cau.Index // if `zir_index != .none`
-/// 2. capture: CaptureValue // for each `captures_len`
-/// 3. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`)
-/// 4. field name: NullTerminatedString for each fields_len; declaration order
+/// 1. capture: CaptureValue // for each `captures_len`
+/// 2. type_hash: PackedU64 // if reified (`captures_len == std.math.maxInt(u32)`)
+/// 3. field name: NullTerminatedString for each fields_len; declaration order
 pub const EnumAuto = struct {
     name: NullTerminatedString,
     /// `std.math.maxInt(u32)` indicates this type is reified.
@@ -6408,32 +6346,32 @@ pub fn init(ip: *InternPool, gpa: Allocator, available_threads: usize) !void {
     ip.locals = try gpa.alloc(Local, used_threads);
     @memset(ip.locals, .{
         .shared = .{
-            .items = Local.List(Item).empty,
-            .extra = Local.Extra.empty,
-            .limbs = Local.Limbs.empty,
-            .strings = Local.Strings.empty,
-            .tracked_insts = Local.TrackedInsts.empty,
-            .files = Local.List(File).empty,
-            .maps = Local.Maps.empty,
-            .caus = Local.Caus.empty,
-            .navs = Local.Navs.empty,
-
-            .namespaces = Local.Namespaces.empty,
+            .items = .empty,
+            .extra = .empty,
+            .limbs = .empty,
+            .strings = .empty,
+            .tracked_insts = .empty,
+            .files = .empty,
+            .maps = .empty,
+            .navs = .empty,
+            .comptime_units = .empty,
+
+            .namespaces = .empty,
         },
         .mutate = .{
             .arena = .{},
 
-            .items = Local.ListMutate.empty,
-            .extra = Local.ListMutate.empty,
-            .limbs = Local.ListMutate.empty,
-            .strings = Local.ListMutate.empty,
-            .tracked_insts = Local.ListMutate.empty,
-            .files = Local.ListMutate.empty,
-            .maps = Local.ListMutate.empty,
-            .caus = Local.ListMutate.empty,
-            .navs = Local.ListMutate.empty,
+            .items = .empty,
+            .extra = .empty,
+            .limbs = .empty,
+            .strings = .empty,
+            .tracked_insts = .empty,
+            .files = .empty,
+            .maps = .empty,
+            .navs = .empty,
+            .comptime_units = .empty,
 
-            .namespaces = Local.BucketListMutate.empty,
+            .namespaces = .empty,
         },
     });
 
@@ -6506,7 +6444,8 @@ pub fn deinit(ip: *InternPool, gpa: Allocator) void {
                 namespace.priv_decls.deinit(gpa);
                 namespace.pub_usingnamespace.deinit(gpa);
                 namespace.priv_usingnamespace.deinit(gpa);
-                namespace.other_decls.deinit(gpa);
+                namespace.comptime_decls.deinit(gpa);
+                namespace.test_decls.deinit(gpa);
             }
         };
         const maps = local.getMutableMaps(gpa);
@@ -6525,8 +6464,6 @@ pub fn activate(ip: *const InternPool) void {
     _ = OptionalString.debug_state;
     _ = NullTerminatedString.debug_state;
     _ = OptionalNullTerminatedString.debug_state;
-    _ = Cau.Index.debug_state;
-    _ = Cau.Index.Optional.debug_state;
     _ = Nav.Index.debug_state;
     _ = Nav.Index.Optional.debug_state;
     std.debug.assert(debug_state.intern_pool == null);
@@ -6711,14 +6648,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
             if (extra.data.captures_len == std.math.maxInt(u32)) {
                 break :ns .{ .reified = .{
                     .zir_index = zir_index,
-                    .type_hash = extraData(extra_list, PackedU64, extra.end + 1).get(),
+                    .type_hash = extraData(extra_list, PackedU64, extra.end).get(),
                 } };
             }
             break :ns .{ .declared = .{
                 .zir_index = zir_index,
                 .captures = .{ .owned = .{
                     .tid = unwrapped_index.tid,
-                    .start = extra.end + 1,
+                    .start = extra.end,
                     .len = extra.data.captures_len,
                 } },
             } };
@@ -6735,14 +6672,14 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
             if (extra.data.captures_len == std.math.maxInt(u32)) {
                 break :ns .{ .reified = .{
                     .zir_index = zir_index,
-                    .type_hash = extraData(extra_list, PackedU64, extra.end + 1).get(),
+                    .type_hash = extraData(extra_list, PackedU64, extra.end).get(),
                 } };
             }
             break :ns .{ .declared = .{
                 .zir_index = zir_index,
                 .captures = .{ .owned = .{
                     .tid = unwrapped_index.tid,
-                    .start = extra.end + 1,
+                    .start = extra.end,
                     .len = extra.data.captures_len,
                 } },
             } };
@@ -8323,7 +8260,6 @@ pub fn getUnionType(
         .size = std.math.maxInt(u32),
         .padding = std.math.maxInt(u32),
         .name = undefined, // set by `finish`
-        .cau = undefined, // set by `finish`
         .namespace = undefined, // set by `finish`
         .tag_ty = ini.enum_tag_ty,
         .zir_index = switch (ini.key) {
@@ -8375,7 +8311,6 @@ pub fn getUnionType(
         .tid = tid,
         .index = gop.put(),
         .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "name").?,
-        .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "cau").?,
         .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeUnion, "namespace").?,
     } };
 }
@@ -8384,7 +8319,6 @@ pub const WipNamespaceType = struct {
     tid: Zcu.PerThread.Id,
     index: Index,
     type_name_extra_index: u32,
-    cau_extra_index: ?u32,
     namespace_extra_index: u32,
 
     pub fn setName(
@@ -8400,18 +8334,11 @@ pub const WipNamespaceType = struct {
     pub fn finish(
         wip: WipNamespaceType,
         ip: *InternPool,
-        analysis_owner: Cau.Index.Optional,
         namespace: NamespaceIndex,
     ) Index {
         const extra = ip.getLocalShared(wip.tid).extra.acquire();
         const extra_items = extra.view().items(.@"0");
 
-        if (wip.cau_extra_index) |i| {
-            extra_items[i] = @intFromEnum(analysis_owner.unwrap().?);
-        } else {
-            assert(analysis_owner == .none);
-        }
-
         extra_items[wip.namespace_extra_index] = @intFromEnum(namespace);
 
         return wip.index;
@@ -8510,7 +8437,6 @@ pub fn getStructType(
                 ini.fields_len); // inits
             const extra_index = addExtraAssumeCapacity(extra, Tag.TypeStructPacked{
                 .name = undefined, // set by `finish`
-                .cau = undefined, // set by `finish`
                 .zir_index = zir_index,
                 .fields_len = ini.fields_len,
                 .namespace = undefined, // set by `finish`
@@ -8555,7 +8481,6 @@ pub fn getStructType(
                 .tid = tid,
                 .index = gop.put(),
                 .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "name").?,
-                .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "cau").?,
                 .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStructPacked, "namespace").?,
             } };
         },
@@ -8578,7 +8503,6 @@ pub fn getStructType(
         1); // names_map
     const extra_index = addExtraAssumeCapacity(extra, Tag.TypeStruct{
         .name = undefined, // set by `finish`
-        .cau = undefined, // set by `finish`
         .zir_index = zir_index,
         .namespace = undefined, // set by `finish`
         .fields_len = ini.fields_len,
@@ -8647,7 +8571,6 @@ pub fn getStructType(
         .tid = tid,
         .index = gop.put(),
         .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "name").?,
-        .cau_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "cau").?,
         .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeStruct, "namespace").?,
     } };
 }
@@ -9383,7 +9306,7 @@ fn finishFuncInstance(
     func_extra_index: u32,
 ) Allocator.Error!void {
     const fn_owner_nav = ip.getNav(ip.funcDeclInfo(generic_owner).owner_nav);
-    const fn_namespace = ip.getCau(fn_owner_nav.analysis_owner.unwrap().?).namespace;
+    const fn_namespace = fn_owner_nav.analysis.?.namespace;
 
     // TODO: improve this name
     const nav_name = try ip.getOrPutStringFmt(gpa, tid, "{}__anon_{d}", .{
@@ -9429,7 +9352,6 @@ pub const WipEnumType = struct {
     index: Index,
     tag_ty_index: u32,
     type_name_extra_index: u32,
-    cau_extra_index: u32,
     namespace_extra_index: u32,
     names_map: MapIndex,
     names_start: u32,
@@ -9449,13 +9371,11 @@ pub const WipEnumType = struct {
     pub fn prepare(
         wip: WipEnumType,
         ip: *InternPool,
-        analysis_owner: Cau.Index,
         namespace: NamespaceIndex,
     ) void {
         const extra = ip.getLocalShared(wip.tid).extra.acquire();
         const extra_items = extra.view().items(.@"0");
 
-        extra_items[wip.cau_extra_index] = @intFromEnum(analysis_owner);
         extra_items[wip.namespace_extra_index] = @intFromEnum(namespace);
     }
 
@@ -9556,7 +9476,6 @@ pub fn getEnumType(
                     .reified => 2, // type_hash: PackedU64
                 } +
                 // zig fmt: on
-                1 + // cau
                 ini.fields_len); // field types
 
             const extra_index = addExtraAssumeCapacity(extra, EnumAuto{
@@ -9577,8 +9496,6 @@ pub fn getEnumType(
                 .tag = .type_enum_auto,
                 .data = extra_index,
             });
-            const cau_extra_index = extra.view().len;
-            extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish`
             switch (ini.key) {
                 .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}),
                 .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}),
@@ -9591,7 +9508,6 @@ pub fn getEnumType(
                 .index = gop.put(),
                 .tag_ty_index = extra_index + std.meta.fieldIndex(EnumAuto, "int_tag_type").?,
                 .type_name_extra_index = extra_index + std.meta.fieldIndex(EnumAuto, "name").?,
-                .cau_extra_index = @intCast(cau_extra_index),
                 .namespace_extra_index = extra_index + std.meta.fieldIndex(EnumAuto, "namespace").?,
                 .names_map = names_map,
                 .names_start = @intCast(names_start),
@@ -9616,7 +9532,6 @@ pub fn getEnumType(
                     .reified => 2, // type_hash: PackedU64
                 } +
                 // zig fmt: on
-                1 + // cau
                 ini.fields_len + // field types
                 ini.fields_len * @intFromBool(ini.has_values)); // field values
 
@@ -9643,8 +9558,6 @@ pub fn getEnumType(
                 },
                 .data = extra_index,
             });
-            const cau_extra_index = extra.view().len;
-            extra.appendAssumeCapacity(undefined); // `cau` will be set by `finish`
             switch (ini.key) {
                 .declared => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures)}),
                 .declared_owned_captures => |d| extra.appendSliceAssumeCapacity(.{@ptrCast(d.captures.get(ip))}),
@@ -9661,7 +9574,6 @@ pub fn getEnumType(
                 .index = gop.put(),
                 .tag_ty_index = extra_index + std.meta.fieldIndex(EnumExplicit, "int_tag_type").?,
                 .type_name_extra_index = extra_index + std.meta.fieldIndex(EnumExplicit, "name").?,
-                .cau_extra_index = @intCast(cau_extra_index),
                 .namespace_extra_index = extra_index + std.meta.fieldIndex(EnumExplicit, "namespace").?,
                 .names_map = names_map,
                 .names_start = @intCast(names_start),
@@ -9858,7 +9770,6 @@ pub fn getOpaqueType(
             .tid = tid,
             .index = gop.put(),
             .type_name_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "name").?,
-            .cau_extra_index = null, // opaques do not undergo type resolution
             .namespace_extra_index = extra_index + std.meta.fieldIndex(Tag.TypeOpaque, "namespace").?,
         },
     };
@@ -9974,7 +9885,6 @@ fn addExtraAssumeCapacity(extra: Local.Extra.Mutable, item: anytype) u32 {
     inline for (@typeInfo(@TypeOf(item)).@"struct".fields) |field| {
         extra.appendAssumeCapacity(.{switch (field.type) {
             Index,
-            Cau.Index,
             Nav.Index,
             NamespaceIndex,
             OptionalNamespaceIndex,
@@ -10037,7 +9947,6 @@ fn extraDataTrail(extra: Local.Extra, comptime T: type, index: u32) struct { dat
         const extra_item = extra_items[extra_index];
         @field(result, field.name) = switch (field.type) {
             Index,
-            Cau.Index,
             Nav.Index,
             NamespaceIndex,
             OptionalNamespaceIndex,
@@ -11058,12 +10967,6 @@ pub fn dumpGenericInstancesFallible(ip: *const InternPool, allocator: Allocator)
     try bw.flush();
 }
 
-pub fn getCau(ip: *const InternPool, index: Cau.Index) Cau {
-    const unwrapped = index.unwrap(ip);
-    const caus = ip.getLocalShared(unwrapped.tid).caus.acquire();
-    return caus.view().items(.@"0")[unwrapped.index];
-}
-
 pub fn getNav(ip: *const InternPool, index: Nav.Index) Nav {
     const unwrapped = index.unwrap(ip);
     const navs = ip.getLocalShared(unwrapped.tid).navs.acquire();
@@ -11077,51 +10980,34 @@ pub fn namespacePtr(ip: *InternPool, namespace_index: NamespaceIndex) *Zcu.Names
     return &namespaces_bucket[unwrapped_namespace_index.index];
 }
 
-/// Create a `Cau` associated with the type at the given `InternPool.Index`.
-pub fn createTypeCau(
+/// Create a `ComptimeUnit`, forming an `AnalUnit` for a `comptime` declaration.
+pub fn createComptimeUnit(
     ip: *InternPool,
     gpa: Allocator,
     tid: Zcu.PerThread.Id,
     zir_index: TrackedInst.Index,
     namespace: NamespaceIndex,
-    owner_type: InternPool.Index,
-) Allocator.Error!Cau.Index {
-    const caus = ip.getLocal(tid).getMutableCaus(gpa);
-    const index_unwrapped: Cau.Index.Unwrapped = .{
+) Allocator.Error!ComptimeUnit.Id {
+    const comptime_units = ip.getLocal(tid).getMutableComptimeUnits(gpa);
+    const id_unwrapped: ComptimeUnit.Id.Unwrapped = .{
         .tid = tid,
-        .index = caus.mutate.len,
+        .index = comptime_units.mutate.len,
     };
-    try caus.append(.{.{
+    try comptime_units.append(.{.{
         .zir_index = zir_index,
         .namespace = namespace,
-        .owner = Cau.Owner.wrap(.{ .type = owner_type }),
     }});
-    return index_unwrapped.wrap(ip);
+    return id_unwrapped.wrap(ip);
 }
 
-/// Create a `Cau` for a `comptime` declaration.
-pub fn createComptimeCau(
-    ip: *InternPool,
-    gpa: Allocator,
-    tid: Zcu.PerThread.Id,
-    zir_index: TrackedInst.Index,
-    namespace: NamespaceIndex,
-) Allocator.Error!Cau.Index {
-    const caus = ip.getLocal(tid).getMutableCaus(gpa);
-    const index_unwrapped: Cau.Index.Unwrapped = .{
-        .tid = tid,
-        .index = caus.mutate.len,
-    };
-    try caus.append(.{.{
-        .zir_index = zir_index,
-        .namespace = namespace,
-        .owner = Cau.Owner.wrap(.none),
-    }});
-    return index_unwrapped.wrap(ip);
+pub fn getComptimeUnit(ip: *const InternPool, id: ComptimeUnit.Id) ComptimeUnit {
+    const unwrapped = id.unwrap(ip);
+    const comptime_units = ip.getLocalShared(unwrapped.tid).comptime_units.acquire();
+    return comptime_units.view().items(.@"0")[unwrapped.index];
 }
 
-/// Create a `Nav` not associated with any `Cau`.
-/// Since there is no analysis owner, the `Nav`'s value must be known at creation time.
+/// Create a `Nav` which does not undergo semantic analysis.
+/// Since it is never analyzed, the `Nav`'s value must be known at creation time.
 pub fn createNav(
     ip: *InternPool,
     gpa: Allocator,
@@ -11143,7 +11029,7 @@ pub fn createNav(
     try navs.append(Nav.pack(.{
         .name = opts.name,
         .fqn = opts.fqn,
-        .analysis_owner = .none,
+        .analysis = null,
         .status = .{ .resolved = .{
             .val = opts.val,
             .alignment = opts.alignment,
@@ -11155,10 +11041,9 @@ pub fn createNav(
     return index_unwrapped.wrap(ip);
 }
 
-/// Create a `Cau` and `Nav` which are paired. The value of the `Nav` is
-/// determined by semantic analysis of the `Cau`. The value of the `Nav`
-/// is initially unresolved.
-pub fn createPairedCauNav(
+/// Create a `Nav` which undergoes semantic analysis because it corresponds to a source declaration.
+/// The value of the `Nav` is initially unresolved.
+pub fn createDeclNav(
     ip: *InternPool,
     gpa: Allocator,
     tid: Zcu.PerThread.Id,
@@ -11168,36 +11053,28 @@ pub fn createPairedCauNav(
     namespace: NamespaceIndex,
     /// TODO: this is hacky! See `Nav.is_usingnamespace`.
     is_usingnamespace: bool,
-) Allocator.Error!struct { Cau.Index, Nav.Index } {
-    const caus = ip.getLocal(tid).getMutableCaus(gpa);
+) Allocator.Error!Nav.Index {
     const navs = ip.getLocal(tid).getMutableNavs(gpa);
 
-    try caus.ensureUnusedCapacity(1);
     try navs.ensureUnusedCapacity(1);
 
-    const cau = Cau.Index.Unwrapped.wrap(.{
-        .tid = tid,
-        .index = caus.mutate.len,
-    }, ip);
     const nav = Nav.Index.Unwrapped.wrap(.{
         .tid = tid,
         .index = navs.mutate.len,
     }, ip);
 
-    caus.appendAssumeCapacity(.{.{
-        .zir_index = zir_index,
-        .namespace = namespace,
-        .owner = Cau.Owner.wrap(.{ .nav = nav }),
-    }});
     navs.appendAssumeCapacity(Nav.pack(.{
         .name = name,
         .fqn = fqn,
-        .analysis_owner = cau.toOptional(),
+        .analysis = .{
+            .namespace = namespace,
+            .zir_index = zir_index,
+        },
         .status = .unresolved,
         .is_usingnamespace = is_usingnamespace,
     }));
 
-    return .{ cau, nav };
+    return nav;
 }
 
 /// Resolve the value of a `Nav` with an analysis owner.
@@ -11220,12 +11097,14 @@ pub fn resolveNavValue(
 
     const navs = local.shared.navs.view();
 
-    const nav_analysis_owners = navs.items(.analysis_owner);
+    const nav_analysis_namespace = navs.items(.analysis_namespace);
+    const nav_analysis_zir_index = navs.items(.analysis_zir_index);
     const nav_vals = navs.items(.val);
     const nav_linksections = navs.items(.@"linksection");
     const nav_bits = navs.items(.bits);
 
-    assert(nav_analysis_owners[unwrapped.index] != .none);
+    assert(nav_analysis_namespace[unwrapped.index] != .none);
+    assert(nav_analysis_zir_index[unwrapped.index] != .none);
 
     @atomicStore(InternPool.Index, &nav_vals[unwrapped.index], resolved.val, .release);
     @atomicStore(OptionalNullTerminatedString, &nav_linksections[unwrapped.index], resolved.@"linksection", .release);
src/Sema.zig
@@ -2870,7 +2870,7 @@ fn zirStructDecl(
     };
     const wip_ty = switch (try ip.getStructType(gpa, pt.tid, struct_init, false)) {
         .existing => |ty| {
-            const new_ty = try pt.ensureTypeUpToDate(ty, false);
+            const new_ty = try pt.ensureTypeUpToDate(ty);
 
             // Make sure we update the namespace if the declaration is re-analyzed, to pick
             // up on e.g. changed comptime decls.
@@ -2900,12 +2900,10 @@ fn zirStructDecl(
     });
     errdefer pt.destroyNamespace(new_namespace_index);
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index);
-
     if (pt.zcu.comp.incremental) {
         try ip.addDependency(
             sema.gpa,
-            AnalUnit.wrap(.{ .cau = new_cau_index }),
+            AnalUnit.wrap(.{ .type = wip_ty.index }),
             .{ .src_hash = tracked_inst },
         );
     }
@@ -2922,7 +2920,7 @@ fn zirStructDecl(
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index));
+    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
 }
 
 fn createTypeName(
@@ -3100,7 +3098,7 @@ fn zirEnumDecl(
     };
     const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, enum_init, false)) {
         .existing => |ty| {
-            const new_ty = try pt.ensureTypeUpToDate(ty, false);
+            const new_ty = try pt.ensureTypeUpToDate(ty);
 
             // Make sure we update the namespace if the declaration is re-analyzed, to pick
             // up on e.g. changed comptime decls.
@@ -3136,16 +3134,14 @@ fn zirEnumDecl(
     });
     errdefer if (!done) pt.destroyNamespace(new_namespace_index);
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index);
-
     try pt.scanNamespace(new_namespace_index, decls);
 
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
 
     // We've finished the initial construction of this type, and are about to perform analysis.
-    // Set the Cau and namespace appropriately, and don't destroy anything on failure.
-    wip_ty.prepare(ip, new_cau_index, new_namespace_index);
+    // Set the namespace appropriately, and don't destroy anything on failure.
+    wip_ty.prepare(ip, new_namespace_index);
     done = true;
 
     try Sema.resolveDeclaredEnum(
@@ -3155,7 +3151,6 @@ fn zirEnumDecl(
         tracked_inst,
         new_namespace_index,
         type_name,
-        new_cau_index,
         small,
         body,
         tag_type_ref,
@@ -3245,7 +3240,7 @@ fn zirUnionDecl(
     };
     const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, union_init, false)) {
         .existing => |ty| {
-            const new_ty = try pt.ensureTypeUpToDate(ty, false);
+            const new_ty = try pt.ensureTypeUpToDate(ty);
 
             // Make sure we update the namespace if the declaration is re-analyzed, to pick
             // up on e.g. changed comptime decls.
@@ -3275,12 +3270,10 @@ fn zirUnionDecl(
     });
     errdefer pt.destroyNamespace(new_namespace_index);
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index);
-
     if (pt.zcu.comp.incremental) {
         try zcu.intern_pool.addDependency(
             gpa,
-            AnalUnit.wrap(.{ .cau = new_cau_index }),
+            AnalUnit.wrap(.{ .type = wip_ty.index }),
             .{ .src_hash = tracked_inst },
         );
     }
@@ -3297,7 +3290,7 @@ fn zirUnionDecl(
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index));
+    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
 }
 
 fn zirOpaqueDecl(
@@ -3382,7 +3375,7 @@ fn zirOpaqueDecl(
         try zcu.comp.queueJob(.{ .codegen_type = wip_ty.index });
     }
     try sema.addTypeReferenceEntry(src, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index));
+    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
 }
 
 fn zirErrorSetDecl(
@@ -6547,7 +6540,10 @@ fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
     const ip = &zcu.intern_pool;
     const func = switch (sema.owner.unwrap()) {
         .func => |func| func,
-        .cau => return, // does nothing outside a function
+        .@"comptime",
+        .nav_val,
+        .type,
+        => return, // does nothing outside a function
     };
     ip.funcSetDisableInstrumentation(func);
     sema.allow_memoize = false;
@@ -6868,11 +6864,8 @@ fn lookupInNamespace(
 
         ignore_self: {
             const skip_nav = switch (sema.owner.unwrap()) {
-                .func => break :ignore_self,
-                .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) {
-                    .none, .type => break :ignore_self,
-                    .nav => |nav| nav,
-                },
+                .@"comptime", .type, .func => break :ignore_self,
+                .nav_val => |nav| nav,
             };
             var i: usize = 0;
             while (i < candidates.items.len) {
@@ -7132,7 +7125,7 @@ fn zirCall(
     const call_inst = try sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, args_info, call_dbg_node, .call);
 
     switch (sema.owner.unwrap()) {
-        .cau => input_is_error = false,
+        .@"comptime", .type, .nav_val => input_is_error = false,
         .func => |owner_func| if (!zcu.intern_pool.funcAnalysisUnordered(owner_func).calls_or_awaits_errorable_fn) {
             // No errorable fn actually called; we have no error return trace
             input_is_error = false;
@@ -7747,11 +7740,9 @@ fn analyzeCall(
         // The call site definitely depends on the function's signature.
         try sema.declareDependency(.{ .src_hash = module_fn.zir_body_inst });
 
-        // This is not a function instance, so the function's `Nav` has a
-        // `Cau` -- we don't need to check `generic_owner`.
+        // This is not a function instance, so the function's `Nav` has analysis
+        // state -- we don't need to check `generic_owner`.
         const fn_nav = ip.getNav(module_fn.owner_nav);
-        const fn_cau_index = fn_nav.analysis_owner.unwrap().?;
-        const fn_cau = ip.getCau(fn_cau_index);
 
         // We effectively want a child Sema here, but can't literally do that, because we need AIR
         // to be shared. InlineCallSema is a wrapper which handles this for us. While `ics` is in
@@ -7759,7 +7750,7 @@ fn analyzeCall(
         // whenever performing an operation where the difference matters.
         var ics = InlineCallSema.init(
             sema,
-            zcu.cauFileScope(fn_cau_index).zir,
+            zcu.navFileScope(module_fn.owner_nav).zir,
             module_fn_index,
             block.error_return_trace_index,
         );
@@ -7769,7 +7760,7 @@ fn analyzeCall(
             .parent = null,
             .sema = sema,
             // The function body exists in the same namespace as the corresponding function declaration.
-            .namespace = fn_cau.namespace,
+            .namespace = fn_nav.analysis.?.namespace,
             .instructions = .{},
             .label = null,
             .inlining = &inlining,
@@ -7780,7 +7771,7 @@ fn analyzeCall(
             .runtime_cond = block.runtime_cond,
             .runtime_loop = block.runtime_loop,
             .runtime_index = block.runtime_index,
-            .src_base_inst = fn_cau.zir_index,
+            .src_base_inst = fn_nav.analysis.?.zir_index,
             .type_name_ctx = fn_nav.fqn,
         };
 
@@ -7795,7 +7786,7 @@ fn analyzeCall(
         // mutate comptime state.
         // TODO: comptime call memoization is currently not supported under incremental compilation
         // since dependencies are not marked on callers. If we want to keep this around (we should
-        // check that it's worthwhile first!), each memoized call needs a `Cau`.
+        // check that it's worthwhile first!), each memoized call needs an `AnalUnit`.
         var should_memoize = !zcu.comp.incremental;
 
         // If it's a comptime function call, we need to memoize it as long as no external
@@ -7904,7 +7895,7 @@ fn analyzeCall(
 
         // Since we're doing an inline call, we depend on the source code of the whole
         // function declaration.
-        try sema.declareDependency(.{ .src_hash = fn_cau.zir_index });
+        try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index });
 
         new_fn_info.return_type = sema.fn_ret_ty.toIntern();
         if (!is_comptime_call and !block.is_typeof) {
@@ -8016,7 +8007,7 @@ fn analyzeCall(
         if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
 
         switch (sema.owner.unwrap()) {
-            .cau => {},
+            .@"comptime", .nav_val, .type => {},
             .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) {
                 ip.funcSetCallsOrAwaitsErrorableFn(owner_func);
             },
@@ -8268,10 +8259,9 @@ fn instantiateGenericCall(
     // The actual monomorphization happens via adding `func_instance` to
     // `InternPool`.
 
-    // Since we are looking at the generic owner here, it has a `Cau`.
+    // Since we are looking at the generic owner here, it has analysis state.
     const fn_nav = ip.getNav(generic_owner_func.owner_nav);
-    const fn_cau = ip.getCau(fn_nav.analysis_owner.unwrap().?);
-    const fn_zir = zcu.namespacePtr(fn_cau.namespace).fileScope(zcu).zir;
+    const fn_zir = zcu.navFileScope(generic_owner_func.owner_nav).zir;
     const fn_info = fn_zir.getFnInfo(generic_owner_func.zir_body_inst.resolve(ip) orelse return error.AnalysisFail);
 
     const comptime_args = try sema.arena.alloc(InternPool.Index, args_info.count());
@@ -8312,11 +8302,11 @@ fn instantiateGenericCall(
     var child_block: Block = .{
         .parent = null,
         .sema = &child_sema,
-        .namespace = fn_cau.namespace,
+        .namespace = fn_nav.analysis.?.namespace,
         .instructions = .{},
         .inlining = null,
         .is_comptime = true,
-        .src_base_inst = fn_cau.zir_index,
+        .src_base_inst = fn_nav.analysis.?.zir_index,
         .type_name_ctx = fn_nav.fqn,
     };
     defer child_block.instructions.deinit(gpa);
@@ -8481,7 +8471,7 @@ fn instantiateGenericCall(
     if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
 
     switch (sema.owner.unwrap()) {
-        .cau => {},
+        .@"comptime", .nav_val, .type => {},
         .func => |owner_func| if (Type.fromInterned(func_ty_info.return_type).isError(zcu)) {
             ip.funcSetCallsOrAwaitsErrorableFn(owner_func);
         },
@@ -9510,14 +9500,11 @@ fn zirFunc(
     // the callconv based on whether it is exported. Otherwise, the callconv defaults
     // to `.auto`.
     const cc: std.builtin.CallingConvention = if (has_body) cc: {
-        const func_decl_cau = if (sema.generic_owner != .none) cau: {
-            const generic_owner_fn = zcu.funcInfo(sema.generic_owner);
-            // The generic owner definitely has a `Cau` for the corresponding function declaration.
-            const generic_owner_nav = ip.getNav(generic_owner_fn.owner_nav);
-            break :cau generic_owner_nav.analysis_owner.unwrap().?;
-        } else sema.owner.unwrap().cau;
+        const func_decl_nav = if (sema.generic_owner != .none) nav: {
+            break :nav zcu.funcInfo(sema.generic_owner).owner_nav;
+        } else sema.owner.unwrap().nav_val;
         const fn_is_exported = exported: {
-            const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail;
+            const decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(ip) orelse return error.AnalysisFail;
             const zir_decl = sema.code.getDeclaration(decl_inst);
             break :exported zir_decl.linkage == .@"export";
         };
@@ -9991,7 +9978,7 @@ fn funcCommon(
         if (!ret_poison)
             try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src);
         const func_index = try ip.getFuncDeclIes(gpa, pt.tid, .{
-            .owner_nav = sema.getOwnerCauNav(),
+            .owner_nav = sema.owner.unwrap().nav_val,
 
             .param_types = param_types,
             .noalias_bits = noalias_bits,
@@ -10040,7 +10027,7 @@ fn funcCommon(
 
     if (has_body) {
         const func_index = try ip.getFuncDecl(gpa, pt.tid, .{
-            .owner_nav = sema.getOwnerCauNav(),
+            .owner_nav = sema.owner.unwrap().nav_val,
             .ty = func_ty,
             .cc = cc,
             .is_noinline = is_noinline,
@@ -17664,7 +17651,7 @@ fn zirAsm(
         if (is_volatile) {
             return sema.fail(block, src, "volatile keyword is redundant on module-level assembly", .{});
         }
-        try zcu.addGlobalAssembly(sema.owner.unwrap().cau, asm_source);
+        try zcu.addGlobalAssembly(sema.owner, asm_source);
         return .void_value;
     }
 
@@ -18155,7 +18142,7 @@ fn zirThis(
     _ = extended;
     const pt = sema.pt;
     const namespace = pt.zcu.namespacePtr(block.namespace);
-    const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type, false);
+    const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
     switch (pt.zcu.intern_pool.indexToKey(new_ty)) {
         .struct_type, .union_type, .enum_type => try sema.declareDependency(.{ .interned = new_ty }),
         .opaque_type => {},
@@ -19321,10 +19308,8 @@ fn typeInfoNamespaceDecls(
     }
 
     for (namespace.pub_usingnamespace.items) |nav| {
-        if (ip.getNav(nav).analysis_owner.unwrap()) |cau| {
-            if (zcu.analysis_in_progress.contains(AnalUnit.wrap(.{ .cau = cau }))) {
-                continue;
-            }
+        if (zcu.analysis_in_progress.contains(.wrap(.{ .nav_val = nav }))) {
+            continue;
         }
         try sema.ensureNavResolved(src, nav);
         const namespace_ty = Type.fromInterned(ip.getNav(nav).status.resolved.val);
@@ -21187,14 +21172,13 @@ fn structInitAnon(
                 .file_scope = block.getFileScopeIndex(zcu),
                 .generation = zcu.generation,
             });
-            const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip.index);
             try zcu.comp.queueJob(.{ .resolve_type_fully = wip.index });
             codegen_type: {
                 if (zcu.comp.config.use_llvm) break :codegen_type;
                 if (block.ownerModule().strip) break :codegen_type;
                 try zcu.comp.queueJob(.{ .codegen_type = wip.index });
             }
-            break :ty wip.finish(ip, new_cau_index.toOptional(), new_namespace_index);
+            break :ty wip.finish(ip, new_namespace_index);
         },
         .existing => |ty| ty,
     };
@@ -21618,7 +21602,7 @@ fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref {
         .func => |func| if (ip.funcAnalysisUnordered(func).calls_or_awaits_errorable_fn and block.ownerModule().error_tracing) {
             return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty);
         },
-        .cau => {},
+        .@"comptime", .nav_val, .type => {},
     }
     return Air.internedToRef(try pt.intern(.{ .opt = .{
         .ty = opt_ptr_stack_trace_ty.toIntern(),
@@ -22296,7 +22280,7 @@ fn zirReify(
             });
 
             try sema.addTypeReferenceEntry(src, wip_ty.index);
-            return Air.internedToRef(wip_ty.finish(ip, .none, new_namespace_index));
+            return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
         },
         .@"union" => {
             const struct_type = ip.loadStructType(ip.typeOf(union_val.val));
@@ -22505,11 +22489,9 @@ fn reifyEnum(
         .generation = zcu.generation,
     });
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index);
-
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
-    wip_ty.prepare(ip, new_cau_index, new_namespace_index);
+    wip_ty.prepare(ip, new_namespace_index);
     wip_ty.setTagTy(ip, tag_ty.toIntern());
     done = true;
 
@@ -22811,8 +22793,6 @@ fn reifyUnion(
         .generation = zcu.generation,
     });
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index);
-
     try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
     codegen_type: {
         if (zcu.comp.config.use_llvm) break :codegen_type;
@@ -22822,7 +22802,7 @@ fn reifyUnion(
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index));
+    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
 }
 
 fn reifyTuple(
@@ -23170,8 +23150,6 @@ fn reifyStruct(
         .generation = zcu.generation,
     });
 
-    const new_cau_index = try ip.createTypeCau(gpa, pt.tid, tracked_inst, new_namespace_index, wip_ty.index);
-
     try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
     codegen_type: {
         if (zcu.comp.config.use_llvm) break :codegen_type;
@@ -23181,7 +23159,7 @@ fn reifyStruct(
     }
     try sema.declareDependency(.{ .interned = wip_ty.index });
     try sema.addTypeReferenceEntry(src, wip_ty.index);
-    return Air.internedToRef(wip_ty.finish(ip, new_cau_index.toOptional(), new_namespace_index));
+    return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
 }
 
 fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref {
@@ -26713,15 +26691,13 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         break :blk try sema.analyzeValueAsCallconv(block, cc_src, cc_val);
     } else cc: {
         if (has_body) {
-            const decl_inst = if (sema.generic_owner != .none) decl_inst: {
+            const func_decl_nav = if (sema.generic_owner != .none) nav: {
                 // Generic instance -- use the original function declaration to
                 // look for the `export` syntax.
-                const nav = zcu.intern_pool.getNav(zcu.funcInfo(sema.generic_owner).owner_nav);
-                const cau = zcu.intern_pool.getCau(nav.analysis_owner.unwrap().?);
-                break :decl_inst cau.zir_index;
-            } else sema.getOwnerCauDeclInst(); // not an instantiation so we're analyzing a function declaration Cau
-
-            const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&zcu.intern_pool) orelse return error.AnalysisFail);
+                break :nav zcu.funcInfo(sema.generic_owner).owner_nav;
+            } else sema.owner.unwrap().nav_val;
+            const func_decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(&zcu.intern_pool) orelse return error.AnalysisFail;
+            const zir_decl = sema.code.getDeclaration(func_decl_inst);
             if (zir_decl.linkage == .@"export") {
                 break :cc target.cCallingConvention() orelse {
                     // This target has no default C calling convention. We sometimes trigger a similar
@@ -27108,8 +27084,16 @@ fn zirBuiltinExtern(
         // `builtin_extern` doesn't provide enough information, and isn't currently tracked.
         // So, for now, just use our containing `declaration`.
         .zir_index = switch (sema.owner.unwrap()) {
-            .cau => sema.getOwnerCauDeclInst(),
-            .func => sema.getOwnerFuncDeclInst(),
+            .@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index,
+            .type => |owner_ty| Type.fromInterned(owner_ty).typeDeclInst(zcu).?,
+            .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index,
+            .func => |func| zir_index: {
+                const func_info = zcu.funcInfo(func);
+                const owner_func_info = if (func_info.generic_owner != .none) owner: {
+                    break :owner zcu.funcInfo(func_info.generic_owner);
+                } else func_info;
+                break :zir_index ip.getNav(owner_func_info.owner_nav).analysis.?.zir_index;
+            },
         },
         .owner_nav = undefined, // ignored by `getExtern`
     });
@@ -32670,28 +32654,25 @@ pub fn ensureNavResolved(sema: *Sema, src: LazySrcLoc, nav_index: InternPool.Nav
     const ip = &zcu.intern_pool;
 
     const nav = ip.getNav(nav_index);
-
-    const cau_index = nav.analysis_owner.unwrap() orelse {
+    if (nav.analysis == null) {
         assert(nav.status == .resolved);
         return;
-    };
+    }
 
-    // Note that even if `nav.status == .resolved`, we must still trigger `ensureCauAnalyzed`
+    // Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate`
     // to make sure the value is up-to-date on incremental updates.
 
-    assert(ip.getCau(cau_index).owner.unwrap().nav == nav_index);
-
-    const anal_unit = AnalUnit.wrap(.{ .cau = cau_index });
+    const anal_unit: AnalUnit = .wrap(.{ .nav_val = nav_index });
     try sema.addReferenceEntry(src, anal_unit);
 
     if (zcu.analysis_in_progress.contains(anal_unit)) {
         return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{
-            .base_node_inst = ip.getCau(cau_index).zir_index,
+            .base_node_inst = nav.analysis.?.zir_index,
             .offset = LazySrcLoc.Offset.nodeOffset(0),
         }, "dependency loop detected", .{}));
     }
 
-    return pt.ensureCauAnalyzed(cau_index);
+    return pt.ensureNavValUpToDate(nav_index);
 }
 
 fn optRefValue(sema: *Sema, opt_val: ?Value) !Value {
@@ -35641,7 +35622,7 @@ pub fn resolveStructAlignment(
     const ip = &zcu.intern_pool;
     const target = zcu.getTarget();
 
-    assert(sema.owner.unwrap().cau == struct_type.cau);
+    assert(sema.owner.unwrap().type == ty);
 
     assert(struct_type.layout != .@"packed");
     assert(struct_type.flagsUnordered(ip).alignment == .none);
@@ -35684,7 +35665,7 @@ pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void {
     const ip = &zcu.intern_pool;
     const struct_type = zcu.typeToStruct(ty) orelse return;
 
-    assert(sema.owner.unwrap().cau == struct_type.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     if (struct_type.haveLayout(ip))
         return;
@@ -35831,15 +35812,13 @@ fn backingIntType(
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
 
-    const cau_index = struct_type.cau;
-
     var analysis_arena = std.heap.ArenaAllocator.init(gpa);
     defer analysis_arena.deinit();
 
     var block: Block = .{
         .parent = null,
         .sema = sema,
-        .namespace = ip.getCau(cau_index).namespace,
+        .namespace = struct_type.namespace,
         .instructions = .{},
         .inlining = null,
         .is_comptime = true,
@@ -35971,7 +35950,7 @@ pub fn resolveUnionAlignment(
     const ip = &zcu.intern_pool;
     const target = zcu.getTarget();
 
-    assert(sema.owner.unwrap().cau == union_type.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     assert(!union_type.haveLayout(ip));
 
@@ -36011,7 +35990,7 @@ pub fn resolveUnionLayout(sema: *Sema, ty: Type) SemaError!void {
     // Load again, since the tag type might have changed due to resolution.
     const union_type = ip.loadUnionType(ty.ip_index);
 
-    assert(sema.owner.unwrap().cau == union_type.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     const old_flags = union_type.flagsUnordered(ip);
     switch (old_flags.status) {
@@ -36126,7 +36105,7 @@ pub fn resolveStructFully(sema: *Sema, ty: Type) SemaError!void {
     const ip = &zcu.intern_pool;
     const struct_type = zcu.typeToStruct(ty).?;
 
-    assert(sema.owner.unwrap().cau == struct_type.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     if (struct_type.setFullyResolved(ip)) return;
     errdefer struct_type.clearFullyResolved(ip);
@@ -36149,7 +36128,7 @@ pub fn resolveUnionFully(sema: *Sema, ty: Type) SemaError!void {
     const ip = &zcu.intern_pool;
     const union_obj = zcu.typeToUnion(ty).?;
 
-    assert(sema.owner.unwrap().cau == union_obj.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     switch (union_obj.flagsUnordered(ip).status) {
         .none, .have_field_types, .field_types_wip, .layout_wip, .have_layout => {},
@@ -36184,7 +36163,7 @@ pub fn resolveStructFieldTypes(
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
 
-    assert(sema.owner.unwrap().cau == struct_type.cau);
+    assert(sema.owner.unwrap().type == ty);
 
     if (struct_type.haveFieldTypes(ip)) return;
 
@@ -36210,7 +36189,7 @@ pub fn resolveStructFieldInits(sema: *Sema, ty: Type) SemaError!void {
     const ip = &zcu.intern_pool;
     const struct_type = zcu.typeToStruct(ty) orelse return;
 
-    assert(sema.owner.unwrap().cau == struct_type.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     // Inits can start as resolved
     if (struct_type.haveFieldInits(ip)) return;
@@ -36239,7 +36218,7 @@ pub fn resolveUnionFieldTypes(sema: *Sema, ty: Type, union_type: InternPool.Load
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
 
-    assert(sema.owner.unwrap().cau == union_type.cau);
+    assert(sema.owner.unwrap().type == ty.toIntern());
 
     switch (union_type.flagsUnordered(ip).status) {
         .none => {},
@@ -36315,7 +36294,7 @@ fn resolveInferredErrorSet(
         // In this case we are dealing with the actual InferredErrorSet object that
         // corresponds to the function, not one created to track an inline/comptime call.
         try sema.addReferenceEntry(src, AnalUnit.wrap(.{ .func = func_index }));
-        try pt.ensureFuncBodyAnalyzed(func_index);
+        try pt.ensureFuncBodyUpToDate(func_index);
     }
 
     // This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody`
@@ -36472,8 +36451,7 @@ fn structFields(
     const zcu = pt.zcu;
     const gpa = zcu.gpa;
     const ip = &zcu.intern_pool;
-    const cau_index = struct_type.cau;
-    const namespace_index = ip.getCau(cau_index).namespace;
+    const namespace_index = struct_type.namespace;
     const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir;
     const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
 
@@ -36671,8 +36649,7 @@ fn structFieldInits(
 
     assert(!struct_type.haveFieldInits(ip));
 
-    const cau_index = struct_type.cau;
-    const namespace_index = ip.getCau(cau_index).namespace;
+    const namespace_index = struct_type.namespace;
     const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir;
     const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
     const fields_len, _, var extra_index = structZirInfo(zir, zir_index);
@@ -38474,13 +38451,11 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
     // just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve
     // the loop.
     switch (sema.owner.unwrap()) {
-        .cau => |cau| switch (dependee) {
-            .nav_val => |nav| if (zcu.intern_pool.getNav(nav).analysis_owner == cau.toOptional()) {
-                return;
-            },
+        .nav_val => |this_nav| switch (dependee) {
+            .nav_val => |other_nav| if (this_nav == other_nav) return,
             else => {},
         },
-        .func => {},
+        else => {},
     }
 
     try zcu.intern_pool.addDependency(sema.gpa, sema.owner, dependee);
@@ -38659,38 +38634,6 @@ pub fn flushExports(sema: *Sema) !void {
     }
 }
 
-/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches
-/// the corresponding `Nav`.
-fn getOwnerCauNav(sema: *Sema) InternPool.Nav.Index {
-    const cau = sema.owner.unwrap().cau;
-    return sema.pt.zcu.intern_pool.getCau(cau).owner.unwrap().nav;
-}
-
-/// Given that this `Sema` is owned by the `Cau` of a `declaration`, fetches
-/// the `TrackedInst` corresponding to this `declaration` instruction.
-fn getOwnerCauDeclInst(sema: *Sema) InternPool.TrackedInst.Index {
-    const ip = &sema.pt.zcu.intern_pool;
-    const cau = ip.getCau(sema.owner.unwrap().cau);
-    assert(cau.owner.unwrap() == .nav);
-    return cau.zir_index;
-}
-
-/// Given that this `Sema` is owned by a runtime function, fetches the
-/// `TrackedInst` corresponding to its `declaration` instruction.
-fn getOwnerFuncDeclInst(sema: *Sema) InternPool.TrackedInst.Index {
-    const zcu = sema.pt.zcu;
-    const ip = &zcu.intern_pool;
-    const func = sema.owner.unwrap().func;
-    const func_info = zcu.funcInfo(func);
-    const cau = if (func_info.generic_owner == .none) cau: {
-        break :cau ip.getNav(func_info.owner_nav).analysis_owner.unwrap().?;
-    } else cau: {
-        const generic_owner = zcu.funcInfo(func_info.generic_owner);
-        break :cau ip.getNav(generic_owner.owner_nav).analysis_owner.unwrap().?;
-    };
-    return ip.getCau(cau).zir_index;
-}
-
 /// Called as soon as a `declared` enum type is created.
 /// Resolves the tag type and field inits.
 /// Marks the `src_inst` dependency on the enum's declaration, so call sites need not do this.
@@ -38701,7 +38644,6 @@ pub fn resolveDeclaredEnum(
     tracked_inst: InternPool.TrackedInst.Index,
     namespace: InternPool.NamespaceIndex,
     type_name: InternPool.NullTerminatedString,
-    enum_cau: InternPool.Cau.Index,
     small: Zir.Inst.EnumDecl.Small,
     body: []const Zir.Inst.Index,
     tag_type_ref: Zir.Inst.Ref,
@@ -38719,7 +38661,7 @@ pub fn resolveDeclaredEnum(
     const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(0) };
     const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = 0 } };
 
-    const anal_unit = AnalUnit.wrap(.{ .cau = enum_cau });
+    const anal_unit = AnalUnit.wrap(.{ .type = wip_ty.index });
 
     var arena = std.heap.ArenaAllocator.init(gpa);
     defer arena.deinit();
@@ -38943,6 +38885,6 @@ fn getBuiltin(sema: *Sema, name: []const u8) SemaError!Air.Inst.Ref {
     const zcu = pt.zcu;
     const ip = &zcu.intern_pool;
     const nav = try pt.getBuiltinNav(name);
-    try pt.ensureCauAnalyzed(ip.getNav(nav).analysis_owner.unwrap().?);
+    try pt.ensureNavValUpToDate(nav);
     return Air.internedToRef(ip.getNav(nav).status.resolved.val);
 }
src/Type.zig
@@ -3851,7 +3851,7 @@ fn resolveStructInner(
     const gpa = zcu.gpa;
 
     const struct_obj = zcu.typeToStruct(ty).?;
-    const owner = InternPool.AnalUnit.wrap(.{ .cau = struct_obj.cau });
+    const owner: InternPool.AnalUnit = .wrap(.{ .type = ty.toIntern() });
 
     if (zcu.failed_analysis.contains(owner) or zcu.transitive_failed_analysis.contains(owner)) {
         return error.AnalysisFail;
@@ -3905,7 +3905,7 @@ fn resolveUnionInner(
     const gpa = zcu.gpa;
 
     const union_obj = zcu.typeToUnion(ty).?;
-    const owner = InternPool.AnalUnit.wrap(.{ .cau = union_obj.cau });
+    const owner: InternPool.AnalUnit = .wrap(.{ .type = ty.toIntern() });
 
     if (zcu.failed_analysis.contains(owner) or zcu.transitive_failed_analysis.contains(owner)) {
         return error.AnalysisFail;
src/Zcu.zig
@@ -192,7 +192,7 @@ compile_log_text: std.ArrayListUnmanaged(u8) = .empty,
 
 test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty,
 
-global_assembly: std.AutoArrayHashMapUnmanaged(InternPool.Cau.Index, []u8) = .empty,
+global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty,
 
 /// Key is the `AnalUnit` *performing* the reference. This representation allows
 /// incremental updates to quickly delete references caused by a specific `AnalUnit`.
@@ -344,9 +344,12 @@ pub const Namespace = struct {
     pub_usingnamespace: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
     /// All `usingnamespace` declarations in this namespace which are *not* marked `pub`.
     priv_usingnamespace: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
-    /// All `comptime` and `test` declarations in this namespace. We store these purely so that
-    /// incremental compilation can re-use the existing `Cau`s when a namespace changes.
-    other_decls: std.ArrayListUnmanaged(InternPool.Cau.Index) = .empty,
+    /// All `comptime` declarations in this namespace. We store these purely so that incremental
+    /// compilation can re-use the existing `ComptimeUnit`s when a namespace changes.
+    comptime_decls: std.ArrayListUnmanaged(InternPool.ComptimeUnit.Id) = .empty,
+    /// All `test` declarations in this namespace. We store these purely so that incremental
+    /// compilation can re-use the existing `Nav`s when a namespace changes.
+    test_decls: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
 
     pub const Index = InternPool.NamespaceIndex;
     pub const OptionalIndex = InternPool.OptionalNamespaceIndex;
@@ -2436,11 +2439,9 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
         // If this is a Decl, we must recursively mark dependencies on its tyval
         // as no longer PO.
         switch (depender.unwrap()) {
-            .cau => |cau| switch (zcu.intern_pool.getCau(cau).owner.unwrap()) {
-                .nav => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }),
-                .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }),
-                .none => {},
-            },
+            .@"comptime" => {},
+            .nav_val => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }),
+            .type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }),
             .func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }),
         }
     }
@@ -2451,11 +2452,9 @@ pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
 fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void {
     const ip = &zcu.intern_pool;
     const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) {
-        .cau => |cau| switch (ip.getCau(cau).owner.unwrap()) {
-            .nav => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced
-            .type => |ty| .{ .interned = ty },
-            .none => return, // analysis of this `Cau` can't outdate any dependencies
-        },
+        .@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies
+        .nav_val => |nav| .{ .nav_val = nav }, // TODO: also `nav_ref` deps when introduced
+        .type => |ty| .{ .interned = ty },
         .func => |func_index| .{ .interned = func_index }, // IES
     };
     log.debug("potentially outdated dependee: {}", .{zcu.fmtDependee(dependee)});
@@ -2512,14 +2511,14 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
     }
 
     // There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some
-    // Cau with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of
-    // A or B. We should select a Cau, since a Cau is definitely responsible for the loop in the
-    // dependency graph (since IES dependencies can't have loops). We should also, of course, not
-    // select a Cau owned by a `comptime` declaration, since you can't depend on those!
+    // AnalUnit with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of
+    // A or B. We should definitely not select a function, since a function can't be responsible for the
+    // loop (IES dependencies can't have loops). We should also, of course, not select a `comptime`
+    // declaration, since you can't depend on those!
 
-    // The choice of this Cau could have a big impact on how much total analysis we perform, since
+    // The choice of this unit could have a big impact on how much total analysis we perform, since
     // if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit
-    // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a Decl
+    // may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a unit
     // which the most things depend on - the idea is that this will resolve a lot of loops (but this
     // is only a heuristic).
 
@@ -2530,33 +2529,28 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
 
     const ip = &zcu.intern_pool;
 
-    var chosen_cau: ?InternPool.Cau.Index = null;
-    var chosen_cau_dependers: u32 = undefined;
+    var chosen_unit: ?AnalUnit = null;
+    var chosen_unit_dependers: u32 = undefined;
 
     inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| {
         for (outdated_units) |unit| {
-            const cau = switch (unit.unwrap()) {
-                .cau => |cau| cau,
-                .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice
-            };
-            const cau_owner = ip.getCau(cau).owner;
-
             var n: u32 = 0;
-            var it = ip.dependencyIterator(switch (cau_owner.unwrap()) {
-                .none => continue, // there can be no dependencies on this `Cau` so it is a terrible choice
+            var it = ip.dependencyIterator(switch (unit.unwrap()) {
+                .func => continue, // a `func` definitely can't be causing the loop so it is a bad choice
+                .@"comptime" => continue, // a `comptime` block can't even be depended on so it is a terrible choice
                 .type => |ty| .{ .interned = ty },
-                .nav => |nav| .{ .nav_val = nav },
+                .nav_val => |nav| .{ .nav_val = nav },
             });
             while (it.next()) |_| n += 1;
 
-            if (chosen_cau == null or n > chosen_cau_dependers) {
-                chosen_cau = cau;
-                chosen_cau_dependers = n;
+            if (chosen_unit == null or n > chosen_unit_dependers) {
+                chosen_unit = unit;
+                chosen_unit_dependers = n;
             }
         }
     }
 
-    if (chosen_cau == null) {
+    if (chosen_unit == null) {
         for (zcu.outdated.keys(), zcu.outdated.values()) |o, opod| {
             const func = o.unwrap().func;
             const nav = zcu.funcInfo(func).owner_nav;
@@ -2570,11 +2564,11 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
     }
 
     log.debug("findOutdatedToAnalyze: heuristic returned '{}' ({d} dependers)", .{
-        zcu.fmtAnalUnit(AnalUnit.wrap(.{ .cau = chosen_cau.? })),
-        chosen_cau_dependers,
+        zcu.fmtAnalUnit(chosen_unit.?),
+        chosen_unit_dependers,
     });
 
-    return AnalUnit.wrap(.{ .cau = chosen_cau.? });
+    return chosen_unit.?;
 }
 
 /// During an incremental update, before semantic analysis, call this to flush all values from
@@ -3019,9 +3013,9 @@ pub fn handleUpdateExports(
     };
 }
 
-pub fn addGlobalAssembly(zcu: *Zcu, cau: InternPool.Cau.Index, source: []const u8) !void {
+pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void {
     const gpa = zcu.gpa;
-    const gop = try zcu.global_assembly.getOrPut(gpa, cau);
+    const gop = try zcu.global_assembly.getOrPut(gpa, unit);
     if (gop.found_existing) {
         const new_value = try std.fmt.allocPrint(gpa, "{s}\n{s}", .{ gop.value_ptr.*, source });
         gpa.free(gop.value_ptr.*);
@@ -3304,23 +3298,22 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
 
             log.debug("handle type '{}'", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)});
 
-            // If this type has a `Cau` for resolution, it's automatically referenced.
-            const resolution_cau: InternPool.Cau.Index.Optional = switch (ip.indexToKey(ty)) {
-                .struct_type => ip.loadStructType(ty).cau.toOptional(),
-                .union_type => ip.loadUnionType(ty).cau.toOptional(),
-                .enum_type => ip.loadEnumType(ty).cau,
-                .opaque_type => .none,
+            // If this type undergoes type resolution, the corresponding `AnalUnit` is automatically referenced.
+            const has_resolution: bool = switch (ip.indexToKey(ty)) {
+                .struct_type, .union_type => true,
+                .enum_type => |k| k != .generated_tag,
+                .opaque_type => false,
                 else => unreachable,
             };
-            if (resolution_cau.unwrap()) |cau| {
+            if (has_resolution) {
                 // this should only be referenced by the type
-                const unit = AnalUnit.wrap(.{ .cau = cau });
+                const unit: AnalUnit = .wrap(.{ .type = ty });
                 assert(!result.contains(unit));
                 try unit_queue.putNoClobber(gpa, unit, referencer);
             }
 
             // If this is a union with a generated tag, its tag type is automatically referenced.
-            // We don't add this reference for non-generated tags, as those will already be referenced via the union's `Cau`, with a better source location.
+            // We don't add this reference for non-generated tags, as those will already be referenced via the union's type resolution, with a better source location.
             if (zcu.typeToUnion(Type.fromInterned(ty))) |union_obj| {
                 const tag_ty = union_obj.enum_tag_ty;
                 if (tag_ty != .none) {
@@ -3335,24 +3328,35 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
             // Queue any decls within this type which would be automatically analyzed.
             // Keep in sync with analysis queueing logic in `Zcu.PerThread.ScanDeclIter.scanDecl`.
             const ns = Type.fromInterned(ty).getNamespace(zcu).unwrap().?;
-            for (zcu.namespacePtr(ns).other_decls.items) |cau| {
-                // These are `comptime` and `test` declarations.
-                // `comptime` decls are always analyzed; `test` declarations are analyzed depending on the test filter.
-                const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue;
+            for (zcu.namespacePtr(ns).comptime_decls.items) |cu| {
+                // `comptime` decls are always analyzed.
+                const unit: AnalUnit = .wrap(.{ .@"comptime" = cu });
+                if (!result.contains(unit)) {
+                    log.debug("type '{}': ref comptime %{}", .{
+                        Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
+                        @intFromEnum(ip.getComptimeUnit(cu).zir_index.resolve(ip) orelse continue),
+                    });
+                    try unit_queue.put(gpa, unit, referencer);
+                }
+            }
+            for (zcu.namespacePtr(ns).test_decls.items) |nav_id| {
+                const nav = ip.getNav(nav_id);
+                // `test` declarations are analyzed depending on the test filter.
+                const inst_info = nav.analysis.?.zir_index.resolveFull(ip) orelse continue;
                 const file = zcu.fileByIndex(inst_info.file);
                 // If the file failed AstGen, the TrackedInst refers to the old ZIR.
                 const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*;
                 const decl = zir.getDeclaration(inst_info.inst);
+
+                if (!comp.config.is_test or file.mod != zcu.main_mod) continue;
+
                 const want_analysis = switch (decl.kind) {
                     .@"usingnamespace" => unreachable,
                     .@"const", .@"var" => unreachable,
-                    .@"comptime" => true,
-                    .unnamed_test => comp.config.is_test and file.mod == zcu.main_mod,
+                    .@"comptime" => unreachable,
+                    .unnamed_test => true,
                     .@"test", .decltest => a: {
-                        if (!comp.config.is_test) break :a false;
-                        if (file.mod != zcu.main_mod) break :a false;
-                        const nav = ip.getCau(cau).owner.unwrap().nav;
-                        const fqn_slice = ip.getNav(nav).fqn.toSlice(ip);
+                        const fqn_slice = nav.fqn.toSlice(ip);
                         for (comp.test_filters) |test_filter| {
                             if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break;
                         } else break :a false;
@@ -3360,28 +3364,25 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
                     },
                 };
                 if (want_analysis) {
-                    const unit = AnalUnit.wrap(.{ .cau = cau });
-                    if (!result.contains(unit)) {
-                        log.debug("type '{}': ref cau %{}", .{
-                            Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
-                            @intFromEnum(inst_info.inst),
-                        });
-                        try unit_queue.put(gpa, unit, referencer);
-                    }
+                    log.debug("type '{}': ref test %{}", .{
+                        Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
+                        @intFromEnum(inst_info.inst),
+                    });
+                    const unit: AnalUnit = .wrap(.{ .nav_val = nav_id });
+                    try unit_queue.put(gpa, unit, referencer);
                 }
             }
             for (zcu.namespacePtr(ns).pub_decls.keys()) |nav| {
                 // These are named declarations. They are analyzed only if marked `export`.
-                const cau = ip.getNav(nav).analysis_owner.unwrap().?;
-                const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue;
+                const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue;
                 const file = zcu.fileByIndex(inst_info.file);
                 // If the file failed AstGen, the TrackedInst refers to the old ZIR.
                 const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*;
                 const decl = zir.getDeclaration(inst_info.inst);
                 if (decl.linkage == .@"export") {
-                    const unit = AnalUnit.wrap(.{ .cau = cau });
+                    const unit: AnalUnit = .wrap(.{ .nav_val = nav });
                     if (!result.contains(unit)) {
-                        log.debug("type '{}': ref cau %{}", .{
+                        log.debug("type '{}': ref named %{}", .{
                             Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
                             @intFromEnum(inst_info.inst),
                         });
@@ -3391,16 +3392,15 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
             }
             for (zcu.namespacePtr(ns).priv_decls.keys()) |nav| {
                 // These are named declarations. They are analyzed only if marked `export`.
-                const cau = ip.getNav(nav).analysis_owner.unwrap().?;
-                const inst_info = ip.getCau(cau).zir_index.resolveFull(ip) orelse continue;
+                const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue;
                 const file = zcu.fileByIndex(inst_info.file);
                 // If the file failed AstGen, the TrackedInst refers to the old ZIR.
                 const zir = if (file.status == .success_zir) file.zir else file.prev_zir.?.*;
                 const decl = zir.getDeclaration(inst_info.inst);
                 if (decl.linkage == .@"export") {
-                    const unit = AnalUnit.wrap(.{ .cau = cau });
+                    const unit: AnalUnit = .wrap(.{ .nav_val = nav });
                     if (!result.contains(unit)) {
-                        log.debug("type '{}': ref cau %{}", .{
+                        log.debug("type '{}': ref named %{}", .{
                             Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
                             @intFromEnum(inst_info.inst),
                         });
@@ -3411,13 +3411,11 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
             // Incremental compilation does not support `usingnamespace`.
             // These are only included to keep good reference traces in non-incremental updates.
             for (zcu.namespacePtr(ns).pub_usingnamespace.items) |nav| {
-                const cau = ip.getNav(nav).analysis_owner.unwrap().?;
-                const unit = AnalUnit.wrap(.{ .cau = cau });
+                const unit: AnalUnit = .wrap(.{ .nav_val = nav });
                 if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer);
             }
             for (zcu.namespacePtr(ns).priv_usingnamespace.items) |nav| {
-                const cau = ip.getNav(nav).analysis_owner.unwrap().?;
-                const unit = AnalUnit.wrap(.{ .cau = cau });
+                const unit: AnalUnit = .wrap(.{ .nav_val = nav });
                 if (!result.contains(unit)) try unit_queue.put(gpa, unit, referencer);
             }
             continue;
@@ -3527,12 +3525,6 @@ pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File {
     return zcu.fileByIndex(zcu.navFileScopeIndex(nav));
 }
 
-pub fn cauFileScope(zcu: *Zcu, cau: InternPool.Cau.Index) *File {
-    const ip = &zcu.intern_pool;
-    const file_index = ip.getCau(cau).zir_index.resolveFile(ip);
-    return zcu.fileByIndex(file_index);
-}
-
 pub fn fmtAnalUnit(zcu: *Zcu, unit: AnalUnit) std.fmt.Formatter(formatAnalUnit) {
     return .{ .data = .{ .unit = unit, .zcu = zcu } };
 }
@@ -3545,19 +3537,17 @@ fn formatAnalUnit(data: struct { unit: AnalUnit, zcu: *Zcu }, comptime fmt: []co
     const zcu = data.zcu;
     const ip = &zcu.intern_pool;
     switch (data.unit.unwrap()) {
-        .cau => |cau_index| {
-            const cau = ip.getCau(cau_index);
-            switch (cau.owner.unwrap()) {
-                .nav => |nav| return writer.print("cau(decl='{}')", .{ip.getNav(nav).fqn.fmt(ip)}),
-                .type => |ty| return writer.print("cau(ty='{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}),
-                .none => if (cau.zir_index.resolveFull(ip)) |resolved| {
-                    const file_path = zcu.fileByIndex(resolved.file).sub_file_path;
-                    return writer.print("cau(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) });
-                } else {
-                    return writer.writeAll("cau(inst=<lost>)");
-                },
+        .@"comptime" => |cu_id| {
+            const cu = ip.getComptimeUnit(cu_id);
+            if (cu.zir_index.resolveFull(ip)) |resolved| {
+                const file_path = zcu.fileByIndex(resolved.file).sub_file_path;
+                return writer.print("comptime(inst=('{s}', %{}))", .{ file_path, @intFromEnum(resolved.inst) });
+            } else {
+                return writer.writeAll("comptime(inst=<list>)");
             }
         },
+        .nav_val => |nav| return writer.print("nav_val('{}')", .{ip.getNav(nav).fqn.fmt(ip)}),
+        .type => |ty| return writer.print("ty('{}')", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)}),
         .func => |func| {
             const nav = zcu.funcInfo(func).owner_nav;
             return writer.print("func('{}')", .{ip.getNav(nav).fqn.fmt(ip)});