Commit 18362ebe13

mlugg <mlugg@mlugg.co.uk>
2024-12-17 01:41:01
Zir: refactor `declaration` instruction representation
The new representation is often more compact. It is also more straightforward to understand: for instance, `extern` is represented on the `declaration` instruction itself rather than using a special instruction. The same applies to `var`, making both of these far more compact. This commit also separates the type and value bodies of a `declaration` instruction. This is a prerequisite for #131. In general, `declaration` now directly encodes details of the syntax form used, and the embedded ZIR bodies are for actual expressions. The only exception to this is functions, where ZIR is effectively designed as if we had #1717. `extern fn` declarations are modeled as `extern const` with a function type, and normal `fn` definitions are modeled as `const` with a `func{,_fancy,_inferred}` instruction. This may change in the future, but improving on this was out of scope for this commit.
1 parent af5e731
lib/std/zig/AstGen.zig
@@ -106,7 +106,6 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
             Zir.Inst.SwitchBlock.Bits,
             Zir.Inst.SwitchBlockErrUnion.Bits,
             Zir.Inst.FuncFancy.Bits,
-            Zir.Inst.Declaration.Flags,
             => @bitCast(@field(extra, field.name)),
 
             else => @compileError("bad field type"),
@@ -1317,12 +1316,45 @@ fn fnProtoExpr(
         return astgen.failTok(some, "function type cannot have a name", .{});
     }
 
+    if (fn_proto.ast.align_expr != 0) {
+        return astgen.failNode(fn_proto.ast.align_expr, "function type cannot have an alignment", .{});
+    }
+
+    if (fn_proto.ast.addrspace_expr != 0) {
+        return astgen.failNode(fn_proto.ast.addrspace_expr, "function type cannot have an addrspace", .{});
+    }
+
+    if (fn_proto.ast.section_expr != 0) {
+        return astgen.failNode(fn_proto.ast.section_expr, "function type cannot have a linksection", .{});
+    }
+
+    const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
+    const is_inferred_error = token_tags[maybe_bang] == .bang;
+    if (is_inferred_error) {
+        return astgen.failTok(maybe_bang, "function type cannot have an inferred error set", .{});
+    }
+
     const is_extern = blk: {
         const maybe_extern_token = fn_proto.extern_export_inline_token orelse break :blk false;
         break :blk token_tags[maybe_extern_token] == .keyword_extern;
     };
     assert(!is_extern);
 
+    return fnProtoExprInner(gz, scope, ri, node, fn_proto, false);
+}
+
+fn fnProtoExprInner(
+    gz: *GenZir,
+    scope: *Scope,
+    ri: ResultInfo,
+    node: Ast.Node.Index,
+    fn_proto: Ast.full.FnProto,
+    implicit_ccc: bool,
+) InnerError!Zir.Inst.Ref {
+    const astgen = gz.astgen;
+    const tree = astgen.tree;
+    const token_tags = tree.tokens.items(.tag);
+
     var block_scope = gz.makeSubBlock(scope);
     defer block_scope.unstack();
 
@@ -1386,18 +1418,6 @@ fn fnProtoExpr(
         break :is_var_args false;
     };
 
-    if (fn_proto.ast.align_expr != 0) {
-        return astgen.failNode(fn_proto.ast.align_expr, "function type cannot have an alignment", .{});
-    }
-
-    if (fn_proto.ast.addrspace_expr != 0) {
-        return astgen.failNode(fn_proto.ast.addrspace_expr, "function type cannot have an addrspace", .{});
-    }
-
-    if (fn_proto.ast.section_expr != 0) {
-        return astgen.failNode(fn_proto.ast.section_expr, "function type cannot have a linksection", .{});
-    }
-
     const cc: Zir.Inst.Ref = if (fn_proto.ast.callconv_expr != 0)
         try expr(
             &block_scope,
@@ -1405,14 +1425,11 @@ fn fnProtoExpr(
             .{ .rl = .{ .coerced_ty = try block_scope.addBuiltinValue(fn_proto.ast.callconv_expr, .calling_convention) } },
             fn_proto.ast.callconv_expr,
         )
+    else if (implicit_ccc)
+        try block_scope.addBuiltinValue(node, .calling_convention_c)
     else
-        Zir.Inst.Ref.none;
+        .none;
 
-    const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
-    const is_inferred_error = token_tags[maybe_bang] == .bang;
-    if (is_inferred_error) {
-        return astgen.failTok(maybe_bang, "function type cannot have an inferred error set", .{});
-    }
     const ret_ty = try expr(&block_scope, scope, coerced_type_ri, fn_proto.ast.return_type);
 
     const result = try block_scope.addFunc(.{
@@ -1428,11 +1445,8 @@ fn fnProtoExpr(
 
         .param_block = block_inst,
         .body_gz = null,
-        .lib_name = .empty,
         .is_var_args = is_var_args,
         .is_inferred_error = false,
-        .is_test = false,
-        .is_extern = false,
         .is_noinline = false,
         .noalias_bits = noalias_bits,
 
@@ -4121,17 +4135,6 @@ fn fnDecl(
 
     const saved_cursor = astgen.saveSourceCursor();
 
-    var decl_gz: GenZir = .{
-        .is_comptime = true,
-        .decl_node_index = fn_proto.ast.proto_node,
-        .decl_line = astgen.source_line,
-        .parent = scope,
-        .astgen = astgen,
-        .instructions = gz.instructions,
-        .instructions_top = gz.instructions.items.len,
-    };
-    defer decl_gz.unstack();
-
     const decl_column = astgen.source_column;
 
     // Set this now, since parameter types, return type, etc may be generic.
@@ -4152,12 +4155,140 @@ fn fnDecl(
         const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false;
         break :blk token_tags[maybe_inline_token] == .keyword_inline;
     };
+    const lib_name = if (fn_proto.lib_name) |lib_name_token| blk: {
+        const lib_name_str = try astgen.strLitAsString(lib_name_token);
+        const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len];
+        if (mem.indexOfScalar(u8, lib_name_slice, 0) != null) {
+            return astgen.failTok(lib_name_token, "library name cannot contain null bytes", .{});
+        } else if (lib_name_str.len == 0) {
+            return astgen.failTok(lib_name_token, "library name cannot be empty", .{});
+        }
+        break :blk lib_name_str.index;
+    } else .empty;
+    if (fn_proto.ast.callconv_expr != 0 and has_inline_keyword) {
+        return astgen.failNode(
+            fn_proto.ast.callconv_expr,
+            "explicit callconv incompatible with inline keyword",
+            .{},
+        );
+    }
+    const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
+    const is_inferred_error = token_tags[maybe_bang] == .bang;
+    if (body_node == 0) {
+        if (!is_extern) {
+            return astgen.failTok(fn_proto.ast.fn_token, "non-extern function has no body", .{});
+        }
+        if (is_inferred_error) {
+            return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{});
+        }
+    } else {
+        assert(!is_extern); // validated by parser (TODO why???)
+    }
+
+    wip_members.nextDecl(decl_inst);
+
+    var type_gz: GenZir = .{
+        .is_comptime = true,
+        .decl_node_index = fn_proto.ast.proto_node,
+        .decl_line = astgen.source_line,
+        .parent = scope,
+        .astgen = astgen,
+        .instructions = gz.instructions,
+        .instructions_top = gz.instructions.items.len,
+    };
+    defer type_gz.unstack();
+
+    if (is_extern) {
+        // We include a function *type*, not a value.
+        const type_inst = try fnProtoExprInner(&type_gz, &type_gz.base, .{ .rl = .none }, decl_node, fn_proto, true);
+        _ = try type_gz.addBreakWithSrcNode(.break_inline, decl_inst, type_inst, decl_node);
+    }
+
+    var align_gz = type_gz.makeSubBlock(scope);
+    defer align_gz.unstack();
+
+    if (fn_proto.ast.align_expr != 0) {
+        astgen.restoreSourceCursor(saved_cursor);
+        const inst = try expr(&align_gz, &align_gz.base, coerced_align_ri, fn_proto.ast.align_expr);
+        _ = try align_gz.addBreakWithSrcNode(.break_inline, decl_inst, inst, decl_node);
+    }
+
+    var linksection_gz = align_gz.makeSubBlock(scope);
+    defer linksection_gz.unstack();
+
+    if (fn_proto.ast.section_expr != 0) {
+        astgen.restoreSourceCursor(saved_cursor);
+        const inst = try expr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, fn_proto.ast.section_expr);
+        _ = try linksection_gz.addBreakWithSrcNode(.break_inline, decl_inst, inst, decl_node);
+    }
+
+    var addrspace_gz = linksection_gz.makeSubBlock(scope);
+    defer addrspace_gz.unstack();
+
+    if (fn_proto.ast.addrspace_expr != 0) {
+        astgen.restoreSourceCursor(saved_cursor);
+        const addrspace_ty = try addrspace_gz.addBuiltinValue(fn_proto.ast.addrspace_expr, .address_space);
+        const inst = try expr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, fn_proto.ast.section_expr);
+        _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, inst, decl_node);
+    }
+
+    var value_gz = addrspace_gz.makeSubBlock(scope);
+    defer value_gz.unstack();
+
+    if (!is_extern) {
+        // We include a function *value*, not a type.
+        astgen.restoreSourceCursor(saved_cursor);
+        try astgen.fnDeclInner(&value_gz, &value_gz.base, saved_cursor, decl_inst, decl_node, body_node, fn_proto);
+    }
+
+    // *Now* we can incorporate the full source code into the hasher.
+    astgen.src_hasher.update(tree.getNodeSource(decl_node));
+
+    var hash: std.zig.SrcHash = undefined;
+    astgen.src_hasher.final(&hash);
+    try setDeclaration(decl_inst, .{
+        .src_hash = hash,
+        .src_line = type_gz.decl_line,
+        .src_column = decl_column,
+
+        .kind = .@"const",
+        .name = try astgen.identAsString(fn_name_token),
+        .is_pub = is_pub,
+        .is_threadlocal = false,
+        .linkage = if (is_extern) .@"extern" else if (is_export) .@"export" else .normal,
+        .lib_name = lib_name,
+
+        .type_gz = &type_gz,
+        .align_gz = &align_gz,
+        .linksection_gz = &linksection_gz,
+        .addrspace_gz = &addrspace_gz,
+        .value_gz = &value_gz,
+    });
+}
+
+fn fnDeclInner(
+    astgen: *AstGen,
+    decl_gz: *GenZir,
+    scope: *Scope,
+    saved_cursor: SourceCursor,
+    decl_inst: Zir.Inst.Index,
+    decl_node: Ast.Node.Index,
+    body_node: Ast.Node.Index,
+    fn_proto: Ast.full.FnProto,
+) InnerError!void {
+    const tree = astgen.tree;
+    const token_tags = tree.tokens.items(.tag);
+
     const is_noinline = blk: {
         const maybe_noinline_token = fn_proto.extern_export_inline_token orelse break :blk false;
         break :blk token_tags[maybe_noinline_token] == .keyword_noinline;
     };
-
-    wip_members.nextDecl(decl_inst);
+    const has_inline_keyword = blk: {
+        const maybe_inline_token = fn_proto.extern_export_inline_token orelse break :blk false;
+        break :blk token_tags[maybe_inline_token] == .keyword_inline;
+    };
+    const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
+    const is_inferred_error = token_tags[maybe_bang] == .bang;
 
     // Note that the capacity here may not be sufficient, as this does not include `anytype` parameters.
     var param_insts: std.ArrayListUnmanaged(Zir.Inst.Index) = try .initCapacity(astgen.arena, fn_proto.ast.params.len);
@@ -4192,11 +4323,9 @@ fn fnDecl(
                     break :blk .empty;
 
                 const param_name = try astgen.identAsString(name_token);
-                if (!is_extern) {
-                    try astgen.detectLocalShadowing(params_scope, param_name, name_token, name_bytes, .@"function parameter");
-                }
+                try astgen.detectLocalShadowing(params_scope, param_name, name_token, name_bytes, .@"function parameter");
                 break :blk param_name;
-            } else if (!is_extern) {
+            } else {
                 if (param.anytype_ellipsis3) |tok| {
                     return astgen.failTok(tok, "missing parameter name", .{});
                 } else {
@@ -4225,7 +4354,7 @@ fn fnDecl(
                     }
                     return astgen.failNode(param.type_expr, "missing parameter name", .{});
                 }
-            } else .empty;
+            };
 
             const param_inst = if (is_anytype) param: {
                 const name_token = param.name_token orelse param.anytype_ellipsis3.?;
@@ -4251,12 +4380,12 @@ fn fnDecl(
                 break :param param_inst.toRef();
             };
 
-            if (param_name == .empty or is_extern) continue;
+            if (param_name == .empty) continue;
 
             const sub_scope = try astgen.arena.create(Scope.LocalVal);
             sub_scope.* = .{
                 .parent = params_scope,
-                .gen_zir = &decl_gz,
+                .gen_zir = decl_gz,
                 .name = param_name,
                 .inst = param_inst,
                 .token_src = param.name_token.?,
@@ -4268,23 +4397,9 @@ fn fnDecl(
         break :is_var_args false;
     };
 
-    const lib_name = if (fn_proto.lib_name) |lib_name_token| blk: {
-        const lib_name_str = try astgen.strLitAsString(lib_name_token);
-        const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len];
-        if (mem.indexOfScalar(u8, lib_name_slice, 0) != null) {
-            return astgen.failTok(lib_name_token, "library name cannot contain null bytes", .{});
-        } else if (lib_name_str.len == 0) {
-            return astgen.failTok(lib_name_token, "library name cannot be empty", .{});
-        }
-        break :blk lib_name_str.index;
-    } else .empty;
-
-    const maybe_bang = tree.firstToken(fn_proto.ast.return_type) - 1;
-    const is_inferred_error = token_tags[maybe_bang] == .bang;
-
     // After creating the function ZIR instruction, it will need to update the break
-    // instructions inside the expression blocks for align, addrspace, cc, and ret_ty
-    // to use the function instruction as the "block" to break from.
+    // instructions inside the expression blocks for cc and ret_ty to use the function
+    // instruction as the body to break from.
 
     var ret_gz = decl_gz.makeSubBlock(params_scope);
     defer ret_gz.unstack();
@@ -4309,13 +4424,6 @@ fn fnDecl(
     defer cc_gz.unstack();
     const cc_ref: Zir.Inst.Ref = blk: {
         if (fn_proto.ast.callconv_expr != 0) {
-            if (has_inline_keyword) {
-                return astgen.failNode(
-                    fn_proto.ast.callconv_expr,
-                    "explicit callconv incompatible with inline keyword",
-                    .{},
-                );
-            }
             const inst = try expr(
                 &cc_gz,
                 scope,
@@ -4328,10 +4436,6 @@ fn fnDecl(
             }
             _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst);
             break :blk inst;
-        } else if (is_extern) {
-            const inst = try cc_gz.addBuiltinValue(decl_node, .calling_convention_c);
-            _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst);
-            break :blk inst;
         } else if (has_inline_keyword) {
             const inst = try cc_gz.addBuiltinValue(decl_node, .calling_convention_inline);
             _ = try cc_gz.addBreak(.break_inline, @enumFromInt(0), inst);
@@ -4341,167 +4445,86 @@ fn fnDecl(
         }
     };
 
-    const func_inst: Zir.Inst.Ref = if (body_node == 0) func: {
-        if (!is_extern) {
-            return astgen.failTok(fn_proto.ast.fn_token, "non-extern function has no body", .{});
-        }
-        if (is_inferred_error) {
-            return astgen.failTok(maybe_bang, "function prototype may not have inferred error set", .{});
-        }
-        break :func try decl_gz.addFunc(.{
-            .src_node = decl_node,
-            .cc_ref = cc_ref,
-            .cc_gz = &cc_gz,
-            .ret_ref = ret_ref,
-            .ret_gz = &ret_gz,
-            .ret_param_refs = ret_body_param_refs,
-            .param_block = decl_inst,
-            .param_insts = param_insts.items,
-            .body_gz = null,
-            .lib_name = lib_name,
-            .is_var_args = is_var_args,
-            .is_inferred_error = false,
-            .is_test = false,
-            .is_extern = true,
-            .is_noinline = is_noinline,
-            .noalias_bits = noalias_bits,
-            .proto_hash = undefined, // ignored for `body_gz == null`
-        });
-    } else func: {
-        var body_gz: GenZir = .{
-            .is_comptime = false,
-            .decl_node_index = fn_proto.ast.proto_node,
-            .decl_line = decl_gz.decl_line,
-            .parent = params_scope,
-            .astgen = astgen,
-            .instructions = gz.instructions,
-            .instructions_top = gz.instructions.items.len,
-        };
-        defer body_gz.unstack();
-
-        // We want `params_scope` to be stacked like this:
-        // body_gz (top)
-        // param2
-        // param1
-        // param0
-        // decl_gz (bottom)
-
-        // Construct the prototype hash.
-        // Leave `astgen.src_hasher` unmodified; this will be used for hashing
-        // the *whole* function declaration, including its body.
-        var proto_hasher = astgen.src_hasher;
-        const proto_node = tree.nodes.items(.data)[decl_node].lhs;
-        proto_hasher.update(tree.getNodeSource(proto_node));
-        var proto_hash: std.zig.SrcHash = undefined;
-        proto_hasher.final(&proto_hash);
-
-        const prev_fn_block = astgen.fn_block;
-        const prev_fn_ret_ty = astgen.fn_ret_ty;
-        defer {
-            astgen.fn_block = prev_fn_block;
-            astgen.fn_ret_ty = prev_fn_ret_ty;
-        }
-        astgen.fn_block = &body_gz;
-        astgen.fn_ret_ty = if (is_inferred_error or ret_ref.toIndex() != null) r: {
-            // We're essentially guaranteed to need the return type at some point,
-            // since the return type is likely not `void` or `noreturn` so there
-            // will probably be an explicit return requiring RLS. Fetch this
-            // return type now so the rest of the function can use it.
-            break :r try body_gz.addNode(.ret_type, decl_node);
-        } else ret_ref;
-
-        const prev_var_args = astgen.fn_var_args;
-        astgen.fn_var_args = is_var_args;
-        defer astgen.fn_var_args = prev_var_args;
-
-        astgen.advanceSourceCursorToNode(body_node);
-        const lbrace_line = astgen.source_line - decl_gz.decl_line;
-        const lbrace_column = astgen.source_column;
-
-        _ = try fullBodyExpr(&body_gz, &body_gz.base, .{ .rl = .none }, body_node, .allow_branch_hint);
-        try checkUsed(gz, scope, params_scope);
-
-        if (!body_gz.endsWithNoReturn()) {
-            // As our last action before the return, "pop" the error trace if needed
-            _ = try body_gz.addRestoreErrRetIndex(.ret, .always, decl_node);
-
-            // Add implicit return at end of function.
-            _ = try body_gz.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node));
-        }
-
-        break :func try decl_gz.addFunc(.{
-            .src_node = decl_node,
-            .cc_ref = cc_ref,
-            .cc_gz = &cc_gz,
-            .ret_ref = ret_ref,
-            .ret_gz = &ret_gz,
-            .ret_param_refs = ret_body_param_refs,
-            .lbrace_line = lbrace_line,
-            .lbrace_column = lbrace_column,
-            .param_block = decl_inst,
-            .param_insts = param_insts.items,
-            .body_gz = &body_gz,
-            .lib_name = lib_name,
-            .is_var_args = is_var_args,
-            .is_inferred_error = is_inferred_error,
-            .is_test = false,
-            .is_extern = false,
-            .is_noinline = is_noinline,
-            .noalias_bits = noalias_bits,
-            .proto_hash = proto_hash,
-        });
+    var body_gz: GenZir = .{
+        .is_comptime = false,
+        .decl_node_index = fn_proto.ast.proto_node,
+        .decl_line = decl_gz.decl_line,
+        .parent = params_scope,
+        .astgen = astgen,
+        .instructions = decl_gz.instructions,
+        .instructions_top = decl_gz.instructions.items.len,
     };
+    defer body_gz.unstack();
+
+    // The scope stack looks like this:
+    //  body_gz (top)
+    //  param2
+    //  param1
+    //  param0
+    //  decl_gz (bottom)
+
+    // Construct the prototype hash.
+    // Leave `astgen.src_hasher` unmodified; this will be used for hashing
+    // the *whole* function declaration, including its body.
+    var proto_hasher = astgen.src_hasher;
+    const proto_node = tree.nodes.items(.data)[decl_node].lhs;
+    proto_hasher.update(tree.getNodeSource(proto_node));
+    var proto_hash: std.zig.SrcHash = undefined;
+    proto_hasher.final(&proto_hash);
 
-    // Before we stack more stuff onto `decl_gz`, add its final instruction.
-    _ = try decl_gz.addBreak(.break_inline, decl_inst, func_inst);
+    const prev_fn_block = astgen.fn_block;
+    const prev_fn_ret_ty = astgen.fn_ret_ty;
+    defer {
+        astgen.fn_block = prev_fn_block;
+        astgen.fn_ret_ty = prev_fn_ret_ty;
+    }
+    astgen.fn_block = &body_gz;
+    astgen.fn_ret_ty = if (is_inferred_error or ret_ref.toIndex() != null) r: {
+        // We're essentially guaranteed to need the return type at some point,
+        // since the return type is likely not `void` or `noreturn` so there
+        // will probably be an explicit return requiring RLS. Fetch this
+        // return type now so the rest of the function can use it.
+        break :r try body_gz.addNode(.ret_type, decl_node);
+    } else ret_ref;
 
-    // Now that `cc_gz,` `ret_gz`, and `body_gz` are unstacked, we evaluate align, addrspace, and linksection.
+    const prev_var_args = astgen.fn_var_args;
+    astgen.fn_var_args = is_var_args;
+    defer astgen.fn_var_args = prev_var_args;
 
-    // We're jumping back in source, so restore the cursor.
-    astgen.restoreSourceCursor(saved_cursor);
+    astgen.advanceSourceCursorToNode(body_node);
+    const lbrace_line = astgen.source_line - decl_gz.decl_line;
+    const lbrace_column = astgen.source_column;
 
-    var align_gz = decl_gz.makeSubBlock(scope);
-    defer align_gz.unstack();
-    if (fn_proto.ast.align_expr != 0) {
-        const inst = try expr(&decl_gz, &decl_gz.base, coerced_align_ri, fn_proto.ast.align_expr);
-        _ = try align_gz.addBreak(.break_inline, decl_inst, inst);
-    }
+    _ = try fullBodyExpr(&body_gz, &body_gz.base, .{ .rl = .none }, body_node, .allow_branch_hint);
+    try checkUsed(decl_gz, scope, params_scope);
 
-    var section_gz = align_gz.makeSubBlock(scope);
-    defer section_gz.unstack();
-    if (fn_proto.ast.section_expr != 0) {
-        const inst = try expr(&decl_gz, scope, .{ .rl = .{ .coerced_ty = .slice_const_u8_type } }, fn_proto.ast.section_expr);
-        _ = try section_gz.addBreak(.break_inline, decl_inst, inst);
-    }
+    if (!body_gz.endsWithNoReturn()) {
+        // As our last action before the return, "pop" the error trace if needed
+        _ = try body_gz.addRestoreErrRetIndex(.ret, .always, decl_node);
 
-    var addrspace_gz = section_gz.makeSubBlock(scope);
-    defer addrspace_gz.unstack();
-    if (fn_proto.ast.addrspace_expr != 0) {
-        const addrspace_ty = try decl_gz.addBuiltinValue(fn_proto.ast.addrspace_expr, .address_space);
-        const inst = try expr(&decl_gz, scope, .{ .rl = .{ .coerced_ty = addrspace_ty } }, fn_proto.ast.addrspace_expr);
-        _ = try addrspace_gz.addBreak(.break_inline, decl_inst, inst);
+        // Add implicit return at end of function.
+        _ = try body_gz.addUnTok(.ret_implicit, .void_value, tree.lastToken(body_node));
     }
 
-    // *Now* we can incorporate the full source code into the hasher.
-    astgen.src_hasher.update(tree.getNodeSource(decl_node));
-
-    var hash: std.zig.SrcHash = undefined;
-    astgen.src_hasher.final(&hash);
-    try setDeclaration(
-        decl_inst,
-        hash,
-        .{ .named = fn_name_token },
-        decl_gz.decl_line,
-        decl_column,
-        is_pub,
-        is_export,
-        &decl_gz,
-        .{
-            .align_gz = &align_gz,
-            .linksection_gz = &section_gz,
-            .addrspace_gz = &addrspace_gz,
-        },
-    );
+    const func_inst = try decl_gz.addFunc(.{
+        .src_node = decl_node,
+        .cc_ref = cc_ref,
+        .cc_gz = &cc_gz,
+        .ret_ref = ret_ref,
+        .ret_gz = &ret_gz,
+        .ret_param_refs = ret_body_param_refs,
+        .lbrace_line = lbrace_line,
+        .lbrace_column = lbrace_column,
+        .param_block = decl_inst,
+        .param_insts = param_insts.items,
+        .body_gz = &body_gz,
+        .is_var_args = is_var_args,
+        .is_inferred_error = is_inferred_error,
+        .is_noinline = is_noinline,
+        .noalias_bits = noalias_bits,
+        .proto_hash = proto_hash,
+    });
+    _ = try decl_gz.addBreakWithSrcNode(.break_inline, decl_inst, func_inst, decl_node);
 }
 
 fn globalVarDecl(
@@ -4522,26 +4545,7 @@ fn globalVarDecl(
     astgen.src_hasher.update(std.mem.asBytes(&astgen.source_column));
 
     const is_mutable = token_tags[var_decl.ast.mut_token] == .keyword_var;
-    // We do this at the beginning so that the instruction index marks the range start
-    // of the top level declaration.
-    const decl_inst = try gz.makeDeclaration(node);
-
     const name_token = var_decl.ast.mut_token + 1;
-    astgen.advanceSourceCursorToNode(node);
-
-    var block_scope: GenZir = .{
-        .parent = scope,
-        .decl_node_index = node,
-        .decl_line = astgen.source_line,
-        .astgen = astgen,
-        .is_comptime = true,
-        .instructions = gz.instructions,
-        .instructions_top = gz.instructions.items.len,
-    };
-    defer block_scope.unstack();
-
-    const decl_column = astgen.source_column;
-
     const is_pub = var_decl.visib_token != null;
     const is_export = blk: {
         const maybe_export_token = var_decl.extern_export_token orelse break :blk false;
@@ -4551,15 +4555,12 @@ fn globalVarDecl(
         const maybe_extern_token = var_decl.extern_export_token orelse break :blk false;
         break :blk token_tags[maybe_extern_token] == .keyword_extern;
     };
-    wip_members.nextDecl(decl_inst);
-
     const is_threadlocal = if (var_decl.threadlocal_token) |tok| blk: {
         if (!is_mutable) {
             return astgen.failTok(tok, "threadlocal variable cannot be constant", .{});
         }
         break :blk true;
     } else false;
-
     const lib_name = if (var_decl.lib_name) |lib_name_token| blk: {
         const lib_name_str = try astgen.strLitAsString(lib_name_token);
         const lib_name_slice = astgen.string_bytes.items[@intFromEnum(lib_name_str.index)..][0..lib_name_str.len];
@@ -4571,9 +4572,14 @@ fn globalVarDecl(
         break :blk lib_name_str.index;
     } else .empty;
 
-    assert(var_decl.comptime_token == null); // handled by parser
+    astgen.advanceSourceCursorToNode(node);
+
+    const decl_column = astgen.source_column;
+
+    const decl_inst = try gz.makeDeclaration(node);
+    wip_members.nextDecl(decl_inst);
 
-    const var_inst: Zir.Inst.Ref = if (var_decl.ast.init_node != 0) vi: {
+    if (var_decl.ast.init_node != 0) {
         if (is_extern) {
             return astgen.failNode(
                 var_decl.ast.init_node,
@@ -4581,102 +4587,91 @@ fn globalVarDecl(
                 .{},
             );
         }
+    } else {
+        if (!is_extern) {
+            return astgen.failNode(node, "variables must be initialized", .{});
+        }
+    }
 
-        const type_inst: Zir.Inst.Ref = if (var_decl.ast.type_node != 0)
-            try expr(
-                &block_scope,
-                &block_scope.base,
-                coerced_type_ri,
-                var_decl.ast.type_node,
-            )
-        else
-            .none;
-
-        block_scope.anon_name_strategy = .parent;
+    if (is_extern and var_decl.ast.type_node == 0) {
+        return astgen.failNode(node, "unable to infer variable type", .{});
+    }
 
-        const init_inst = try expr(
-            &block_scope,
-            &block_scope.base,
-            if (type_inst != .none) .{ .rl = .{ .ty = type_inst } } else .{ .rl = .none },
-            var_decl.ast.init_node,
-        );
+    assert(var_decl.comptime_token == null); // handled by parser
 
-        if (is_mutable) {
-            const var_inst = try block_scope.addVar(.{
-                .var_type = type_inst,
-                .lib_name = .empty,
-                .align_inst = .none, // passed via the decls data
-                .init = init_inst,
-                .is_extern = false,
-                .is_const = !is_mutable,
-                .is_threadlocal = is_threadlocal,
-            });
-            break :vi var_inst;
-        } else {
-            break :vi init_inst;
-        }
-    } else if (!is_extern) {
-        return astgen.failNode(node, "variables must be initialized", .{});
-    } else if (var_decl.ast.type_node != 0) vi: {
-        // Extern variable which has an explicit type.
-        const type_inst = try typeExpr(&block_scope, &block_scope.base, var_decl.ast.type_node);
-
-        block_scope.anon_name_strategy = .parent;
-
-        const var_inst = try block_scope.addVar(.{
-            .var_type = type_inst,
-            .lib_name = lib_name,
-            .align_inst = .none, // passed via the decls data
-            .init = .none,
-            .is_extern = true,
-            .is_const = !is_mutable,
-            .is_threadlocal = is_threadlocal,
-        });
-        break :vi var_inst;
-    } else {
-        return astgen.failNode(node, "unable to infer variable type", .{});
+    var type_gz: GenZir = .{
+        .parent = scope,
+        .decl_node_index = node,
+        .decl_line = astgen.source_line,
+        .astgen = astgen,
+        .is_comptime = true,
+        .instructions = gz.instructions,
+        .instructions_top = gz.instructions.items.len,
     };
+    defer type_gz.unstack();
+
+    if (var_decl.ast.type_node != 0) {
+        const type_inst = try expr(&type_gz, &type_gz.base, coerced_type_ri, var_decl.ast.type_node);
+        _ = try type_gz.addBreakWithSrcNode(.break_inline, decl_inst, type_inst, node);
+    }
 
-    // We do this at the end so that the instruction index marks the end
-    // range of a top level declaration.
-    _ = try block_scope.addBreakWithSrcNode(.break_inline, decl_inst, var_inst, node);
+    var align_gz = type_gz.makeSubBlock(scope);
+    defer align_gz.unstack();
 
-    var align_gz = block_scope.makeSubBlock(scope);
     if (var_decl.ast.align_node != 0) {
-        const align_inst = try fullBodyExpr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node, .normal);
+        const align_inst = try expr(&align_gz, &align_gz.base, coerced_align_ri, var_decl.ast.align_node);
         _ = try align_gz.addBreakWithSrcNode(.break_inline, decl_inst, align_inst, node);
     }
 
-    var linksection_gz = align_gz.makeSubBlock(scope);
+    var linksection_gz = type_gz.makeSubBlock(scope);
+    defer linksection_gz.unstack();
+
     if (var_decl.ast.section_node != 0) {
-        const linksection_inst = try fullBodyExpr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, var_decl.ast.section_node, .normal);
+        const linksection_inst = try expr(&linksection_gz, &linksection_gz.base, coerced_linksection_ri, var_decl.ast.section_node);
         _ = try linksection_gz.addBreakWithSrcNode(.break_inline, decl_inst, linksection_inst, node);
     }
 
-    var addrspace_gz = linksection_gz.makeSubBlock(scope);
+    var addrspace_gz = type_gz.makeSubBlock(scope);
+    defer addrspace_gz.unstack();
+
     if (var_decl.ast.addrspace_node != 0) {
         const addrspace_ty = try addrspace_gz.addBuiltinValue(var_decl.ast.addrspace_node, .address_space);
-        const addrspace_inst = try fullBodyExpr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, var_decl.ast.addrspace_node, .normal);
+        const addrspace_inst = try expr(&addrspace_gz, &addrspace_gz.base, .{ .rl = .{ .coerced_ty = addrspace_ty } }, var_decl.ast.addrspace_node);
         _ = try addrspace_gz.addBreakWithSrcNode(.break_inline, decl_inst, addrspace_inst, node);
     }
 
+    var init_gz = type_gz.makeSubBlock(scope);
+    defer init_gz.unstack();
+
+    if (var_decl.ast.init_node != 0) {
+        init_gz.anon_name_strategy = .parent;
+        const init_ri: ResultInfo = if (var_decl.ast.type_node != 0) .{
+            .rl = .{ .coerced_ty = decl_inst.toRef() },
+        } else .{ .rl = .none };
+        const init_inst = try expr(&init_gz, &init_gz.base, init_ri, var_decl.ast.init_node);
+        _ = try init_gz.addBreakWithSrcNode(.break_inline, decl_inst, init_inst, node);
+    }
+
     var hash: std.zig.SrcHash = undefined;
     astgen.src_hasher.final(&hash);
-    try setDeclaration(
-        decl_inst,
-        hash,
-        .{ .named = name_token },
-        block_scope.decl_line,
-        decl_column,
-        is_pub,
-        is_export,
-        &block_scope,
-        .{
-            .align_gz = &align_gz,
-            .linksection_gz = &linksection_gz,
-            .addrspace_gz = &addrspace_gz,
-        },
-    );
+    try setDeclaration(decl_inst, .{
+        .src_hash = hash,
+        .src_line = type_gz.decl_line,
+        .src_column = decl_column,
+
+        .kind = if (is_mutable) .@"var" else .@"const",
+        .name = try astgen.identAsString(name_token),
+        .is_pub = is_pub,
+        .is_threadlocal = is_threadlocal,
+        .linkage = if (is_extern) .@"extern" else if (is_export) .@"export" else .normal,
+        .lib_name = lib_name,
+
+        .type_gz = &type_gz,
+        .align_gz = &align_gz,
+        .linksection_gz = &linksection_gz,
+        .addrspace_gz = &addrspace_gz,
+        .value_gz = &init_gz,
+    });
 }
 
 fn comptimeDecl(
@@ -4702,37 +4697,45 @@ fn comptimeDecl(
     wip_members.nextDecl(decl_inst);
     astgen.advanceSourceCursorToNode(node);
 
-    var decl_block: GenZir = .{
+    // This is just needed for the `setDeclaration` call.
+    var dummy_gz = gz.makeSubBlock(scope);
+    defer dummy_gz.unstack();
+
+    var comptime_gz: GenZir = .{
         .is_comptime = true,
         .decl_node_index = node,
         .decl_line = astgen.source_line,
         .parent = scope,
         .astgen = astgen,
-        .instructions = gz.instructions,
-        .instructions_top = gz.instructions.items.len,
+        .instructions = dummy_gz.instructions,
+        .instructions_top = dummy_gz.instructions.items.len,
     };
-    defer decl_block.unstack();
+    defer comptime_gz.unstack();
 
     const decl_column = astgen.source_column;
 
-    const block_result = try fullBodyExpr(&decl_block, &decl_block.base, .{ .rl = .none }, body_node, .normal);
-    if (decl_block.isEmpty() or !decl_block.refIsNoReturn(block_result)) {
-        _ = try decl_block.addBreak(.break_inline, decl_inst, .void_value);
+    const block_result = try fullBodyExpr(&comptime_gz, &comptime_gz.base, .{ .rl = .none }, body_node, .normal);
+    if (comptime_gz.isEmpty() or !comptime_gz.refIsNoReturn(block_result)) {
+        _ = try comptime_gz.addBreak(.break_inline, decl_inst, .void_value);
     }
 
     var hash: std.zig.SrcHash = undefined;
     astgen.src_hasher.final(&hash);
-    try setDeclaration(
-        decl_inst,
-        hash,
-        .@"comptime",
-        decl_block.decl_line,
-        decl_column,
-        false,
-        false,
-        &decl_block,
-        null,
-    );
+    try setDeclaration(decl_inst, .{
+        .src_hash = hash,
+        .src_line = comptime_gz.decl_line,
+        .src_column = decl_column,
+        .kind = .@"comptime",
+        .name = .empty,
+        .is_pub = false,
+        .is_threadlocal = false,
+        .linkage = .normal,
+        .type_gz = &dummy_gz,
+        .align_gz = &dummy_gz,
+        .linksection_gz = &dummy_gz,
+        .addrspace_gz = &dummy_gz,
+        .value_gz = &comptime_gz,
+    });
 }
 
 fn usingnamespaceDecl(
@@ -4764,7 +4767,11 @@ fn usingnamespaceDecl(
     wip_members.nextDecl(decl_inst);
     astgen.advanceSourceCursorToNode(node);
 
-    var decl_block: GenZir = .{
+    // This is just needed for the `setDeclaration` call.
+    var dummy_gz = gz.makeSubBlock(scope);
+    defer dummy_gz.unstack();
+
+    var usingnamespace_gz: GenZir = .{
         .is_comptime = true,
         .decl_node_index = node,
         .decl_line = astgen.source_line,
@@ -4773,26 +4780,30 @@ fn usingnamespaceDecl(
         .instructions = gz.instructions,
         .instructions_top = gz.instructions.items.len,
     };
-    defer decl_block.unstack();
+    defer usingnamespace_gz.unstack();
 
     const decl_column = astgen.source_column;
 
-    const namespace_inst = try typeExpr(&decl_block, &decl_block.base, type_expr);
-    _ = try decl_block.addBreak(.break_inline, decl_inst, namespace_inst);
+    const namespace_inst = try typeExpr(&usingnamespace_gz, &usingnamespace_gz.base, type_expr);
+    _ = try usingnamespace_gz.addBreak(.break_inline, decl_inst, namespace_inst);
 
     var hash: std.zig.SrcHash = undefined;
     astgen.src_hasher.final(&hash);
-    try setDeclaration(
-        decl_inst,
-        hash,
-        .@"usingnamespace",
-        decl_block.decl_line,
-        decl_column,
-        is_pub,
-        false,
-        &decl_block,
-        null,
-    );
+    try setDeclaration(decl_inst, .{
+        .src_hash = hash,
+        .src_line = usingnamespace_gz.decl_line,
+        .src_column = decl_column,
+        .kind = .@"usingnamespace",
+        .name = .empty,
+        .is_pub = is_pub,
+        .is_threadlocal = false,
+        .linkage = .normal,
+        .type_gz = &dummy_gz,
+        .align_gz = &dummy_gz,
+        .linksection_gz = &dummy_gz,
+        .addrspace_gz = &dummy_gz,
+        .value_gz = &usingnamespace_gz,
+    });
 }
 
 fn testDecl(
@@ -4819,14 +4830,18 @@ fn testDecl(
     wip_members.nextDecl(decl_inst);
     astgen.advanceSourceCursorToNode(node);
 
+    // This is just needed for the `setDeclaration` call.
+    var dummy_gz: GenZir = gz.makeSubBlock(scope);
+    defer dummy_gz.unstack();
+
     var decl_block: GenZir = .{
         .is_comptime = true,
         .decl_node_index = node,
         .decl_line = astgen.source_line,
         .parent = scope,
         .astgen = astgen,
-        .instructions = gz.instructions,
-        .instructions_top = gz.instructions.items.len,
+        .instructions = dummy_gz.instructions,
+        .instructions_top = dummy_gz.instructions.items.len,
     };
     defer decl_block.unstack();
 
@@ -4835,11 +4850,21 @@ fn testDecl(
     const main_tokens = tree.nodes.items(.main_token);
     const token_tags = tree.tokens.items(.tag);
     const test_token = main_tokens[node];
+
     const test_name_token = test_token + 1;
-    const test_name: DeclarationName = switch (token_tags[test_name_token]) {
-        else => .unnamed_test,
-        .string_literal => .{ .named_test = test_name_token },
-        .identifier => blk: {
+    const test_name: Zir.NullTerminatedString = switch (token_tags[test_name_token]) {
+        else => .empty,
+        .string_literal => name: {
+            const name = try astgen.strLitAsString(test_name_token);
+            const slice = astgen.string_bytes.items[@intFromEnum(name.index)..][0..name.len];
+            if (mem.indexOfScalar(u8, slice, 0) != null) {
+                return astgen.failTok(test_name_token, "test name cannot contain null bytes", .{});
+            } else if (slice.len == 0) {
+                return astgen.failTok(test_name_token, "empty test name must be omitted", .{});
+            }
+            break :name name.index;
+        },
+        .identifier => name: {
             const ident_name_raw = tree.tokenSlice(test_name_token);
 
             if (mem.eql(u8, ident_name_raw, "_")) return astgen.failTok(test_name_token, "'_' used as an identifier without @\"_\" syntax", .{});
@@ -4909,7 +4934,7 @@ fn testDecl(
                 return astgen.failTok(test_name_token, "use of undeclared identifier '{s}'", .{ident_name});
             }
 
-            break :blk .{ .decltest = test_name_token };
+            break :name try astgen.identAsString(test_name_token);
         },
     };
 
@@ -4965,11 +4990,8 @@ fn testDecl(
         .lbrace_column = lbrace_column,
         .param_block = decl_inst,
         .body_gz = &fn_block,
-        .lib_name = .empty,
         .is_var_args = false,
         .is_inferred_error = false,
-        .is_test = true,
-        .is_extern = false,
         .is_noinline = false,
         .noalias_bits = 0,
 
@@ -4981,17 +5003,27 @@ fn testDecl(
 
     var hash: std.zig.SrcHash = undefined;
     astgen.src_hasher.final(&hash);
-    try setDeclaration(
-        decl_inst,
-        hash,
-        test_name,
-        decl_block.decl_line,
-        decl_column,
-        false,
-        false,
-        &decl_block,
-        null,
-    );
+    try setDeclaration(decl_inst, .{
+        .src_hash = hash,
+        .src_line = decl_block.decl_line,
+        .src_column = decl_column,
+
+        .kind = switch (token_tags[test_name_token]) {
+            .string_literal => .@"test",
+            .identifier => .decltest,
+            else => .unnamed_test,
+        },
+        .name = test_name,
+        .is_pub = false,
+        .is_threadlocal = false,
+        .linkage = .normal,
+
+        .type_gz = &dummy_gz,
+        .align_gz = &dummy_gz,
+        .linksection_gz = &dummy_gz,
+        .addrspace_gz = &dummy_gz,
+        .value_gz = &decl_block,
+    });
 }
 
 fn structDeclInner(
@@ -5882,7 +5914,8 @@ fn containerMember(
                     try addFailedDeclaration(
                         wip_members,
                         gz,
-                        .{ .named = full.name_token.? },
+                        .@"const",
+                        try astgen.identAsString(full.name_token.?),
                         full.ast.proto_node,
                         full.visib_token != null,
                     );
@@ -5904,7 +5937,8 @@ fn containerMember(
                     try addFailedDeclaration(
                         wip_members,
                         gz,
-                        .{ .named = full.ast.mut_token + 1 },
+                        .@"const", // doesn't really matter
+                        try astgen.identAsString(full.ast.mut_token + 1),
                         member_node,
                         full.visib_token != null,
                     );
@@ -5922,6 +5956,7 @@ fn containerMember(
                         wip_members,
                         gz,
                         .@"comptime",
+                        .empty,
                         member_node,
                         false,
                     );
@@ -5938,6 +5973,7 @@ fn containerMember(
                         wip_members,
                         gz,
                         .@"usingnamespace",
+                        .empty,
                         member_node,
                         is_pub: {
                             const main_tokens = tree.nodes.items(.main_token);
@@ -5962,6 +5998,7 @@ fn containerMember(
                         wip_members,
                         gz,
                         .unnamed_test,
+                        .empty,
                         member_node,
                         false,
                     );
@@ -11670,23 +11707,6 @@ fn strLitNodeAsString(astgen: *AstGen, node: Ast.Node.Index) !IndexSlice {
     };
 }
 
-fn testNameString(astgen: *AstGen, str_lit_token: Ast.TokenIndex) !Zir.NullTerminatedString {
-    const gpa = astgen.gpa;
-    const string_bytes = &astgen.string_bytes;
-    const str_index: u32 = @intCast(string_bytes.items.len);
-    const token_bytes = astgen.tree.tokenSlice(str_lit_token);
-    try string_bytes.append(gpa, 0); // Indicates this is a test.
-    try astgen.parseStrLit(str_lit_token, string_bytes, token_bytes, 0);
-    const slice = string_bytes.items[str_index + 1 ..];
-    if (mem.indexOfScalar(u8, slice, 0) != null) {
-        return astgen.failTok(str_lit_token, "test name cannot contain null bytes", .{});
-    } else if (slice.len == 0) {
-        return astgen.failTok(str_lit_token, "empty test name must be omitted", .{});
-    }
-    try string_bytes.append(gpa, 0);
-    return @enumFromInt(str_index);
-}
-
 const Scope = struct {
     tag: Tag,
 
@@ -12077,12 +12097,9 @@ const GenZir = struct {
             cc_ref: Zir.Inst.Ref,
             ret_ref: Zir.Inst.Ref,
 
-            lib_name: Zir.NullTerminatedString,
             noalias_bits: u32,
             is_var_args: bool,
             is_inferred_error: bool,
-            is_test: bool,
-            is_extern: bool,
             is_noinline: bool,
 
             /// Ignored if `body_gz == null`.
@@ -12150,9 +12167,8 @@ const GenZir = struct {
 
         const body_len = astgen.countBodyLenAfterFixupsExtraRefs(body, args.param_insts);
 
-        const tag: Zir.Inst.Tag, const payload_index: u32 = if (args.cc_ref != .none or args.lib_name != .empty or
-            args.is_var_args or args.is_test or args.is_extern or
-            args.noalias_bits != 0 or args.is_noinline)
+        const tag: Zir.Inst.Tag, const payload_index: u32 = if (args.cc_ref != .none or
+            args.is_var_args or args.noalias_bits != 0 or args.is_noinline)
         inst_info: {
             try astgen.extra.ensureUnusedCapacity(
                 gpa,
@@ -12160,7 +12176,6 @@ const GenZir = struct {
                     fancyFnExprExtraLen(astgen, &.{}, cc_body, args.cc_ref) +
                     fancyFnExprExtraLen(astgen, args.ret_param_refs, ret_body, ret_ref) +
                     body_len + src_locs_and_hash.len +
-                    @intFromBool(args.lib_name != .empty) +
                     @intFromBool(args.noalias_bits != 0),
             );
             const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.FuncFancy{
@@ -12169,10 +12184,7 @@ const GenZir = struct {
                 .bits = .{
                     .is_var_args = args.is_var_args,
                     .is_inferred_error = args.is_inferred_error,
-                    .is_test = args.is_test,
-                    .is_extern = args.is_extern,
                     .is_noinline = args.is_noinline,
-                    .has_lib_name = args.lib_name != .empty,
                     .has_any_noalias = args.noalias_bits != 0,
 
                     .has_cc_ref = args.cc_ref != .none,
@@ -12182,9 +12194,6 @@ const GenZir = struct {
                     .has_ret_ty_body = ret_body.len != 0,
                 },
             });
-            if (args.lib_name != .empty) {
-                astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name));
-            }
 
             const zir_datas = astgen.instructions.items(.data);
             if (cc_body.len != 0) {
@@ -12279,61 +12288,6 @@ const GenZir = struct {
             @intFromBool(main_body.len > 0 or ref != .none);
     }
 
-    fn addVar(gz: *GenZir, args: struct {
-        align_inst: Zir.Inst.Ref,
-        lib_name: Zir.NullTerminatedString,
-        var_type: Zir.Inst.Ref,
-        init: Zir.Inst.Ref,
-        is_extern: bool,
-        is_const: bool,
-        is_threadlocal: bool,
-    }) !Zir.Inst.Ref {
-        const astgen = gz.astgen;
-        const gpa = astgen.gpa;
-
-        try gz.instructions.ensureUnusedCapacity(gpa, 1);
-        try astgen.instructions.ensureUnusedCapacity(gpa, 1);
-
-        try astgen.extra.ensureUnusedCapacity(
-            gpa,
-            @typeInfo(Zir.Inst.ExtendedVar).@"struct".fields.len +
-                @intFromBool(args.lib_name != .empty) +
-                @intFromBool(args.align_inst != .none) +
-                @intFromBool(args.init != .none),
-        );
-        const payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.ExtendedVar{
-            .var_type = args.var_type,
-        });
-        if (args.lib_name != .empty) {
-            astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name));
-        }
-        if (args.align_inst != .none) {
-            astgen.extra.appendAssumeCapacity(@intFromEnum(args.align_inst));
-        }
-        if (args.init != .none) {
-            astgen.extra.appendAssumeCapacity(@intFromEnum(args.init));
-        }
-
-        const new_index: Zir.Inst.Index = @enumFromInt(astgen.instructions.len);
-        astgen.instructions.appendAssumeCapacity(.{
-            .tag = .extended,
-            .data = .{ .extended = .{
-                .opcode = .variable,
-                .small = @bitCast(Zir.Inst.ExtendedVar.Small{
-                    .has_lib_name = args.lib_name != .empty,
-                    .has_align = args.align_inst != .none,
-                    .has_init = args.init != .none,
-                    .is_extern = args.is_extern,
-                    .is_const = args.is_const,
-                    .is_threadlocal = args.is_threadlocal,
-                }),
-                .operand = payload_index,
-            } },
-        });
-        gz.instructions.appendAssumeCapacity(new_index);
-        return new_index.toRef();
-    }
-
     fn addInt(gz: *GenZir, integer: u64) !Zir.Inst.Ref {
         return gz.add(.{
             .tag = .int,
@@ -13909,14 +13863,18 @@ const DeclarationName = union(enum) {
 fn addFailedDeclaration(
     wip_members: *WipMembers,
     gz: *GenZir,
-    name: DeclarationName,
+    kind: Zir.Inst.Declaration.Unwrapped.Kind,
+    name: Zir.NullTerminatedString,
     src_node: Ast.Node.Index,
     is_pub: bool,
 ) !void {
     const decl_inst = try gz.makeDeclaration(src_node);
     wip_members.nextDecl(decl_inst);
-    var decl_gz = gz.makeSubBlock(&gz.base); // scope doesn't matter here
-    _ = try decl_gz.add(.{
+
+    var dummy_gz = gz.makeSubBlock(&gz.base);
+
+    var value_gz = gz.makeSubBlock(&gz.base); // scope doesn't matter here
+    _ = try value_gz.add(.{
         .tag = .extended,
         .data = .{ .extended = .{
             .opcode = .astgen_error,
@@ -13924,110 +13882,198 @@ fn addFailedDeclaration(
             .operand = undefined,
         } },
     });
-    try setDeclaration(
-        decl_inst,
-        @splat(0), // use a fixed hash to represent an AstGen failure; we don't care about source changes if AstGen still failed!
-        name,
-        gz.astgen.source_line,
-        gz.astgen.source_column,
-        is_pub,
-        false, // we don't care about exports since semantic analysis will fail
-        &decl_gz,
-        null,
-    );
+
+    try setDeclaration(decl_inst, .{
+        .src_hash = @splat(0), // use a fixed hash to represent an AstGen failure; we don't care about source changes if AstGen still failed!
+        .src_line = gz.astgen.source_line,
+        .src_column = gz.astgen.source_column,
+        .kind = kind,
+        .name = name,
+        .is_pub = is_pub,
+        .is_threadlocal = false,
+        .linkage = .normal,
+        .type_gz = &dummy_gz,
+        .align_gz = &dummy_gz,
+        .linksection_gz = &dummy_gz,
+        .addrspace_gz = &dummy_gz,
+        .value_gz = &value_gz,
+    });
 }
 
 /// Sets all extra data for a `declaration` instruction.
-/// Unstacks `value_gz`, `align_gz`, `linksection_gz`, and `addrspace_gz`.
+/// Unstacks `type_gz`, `align_gz`, `linksection_gz`, `addrspace_gz`, and `value_gz`.
 fn setDeclaration(
     decl_inst: Zir.Inst.Index,
-    src_hash: std.zig.SrcHash,
-    name: DeclarationName,
-    src_line: u32,
-    src_column: u32,
-    is_pub: bool,
-    is_export: bool,
-    value_gz: *GenZir,
-    /// May be `null` if all these blocks would be empty.
-    /// If `null`, then `value_gz` must have nothing stacked on it.
-    extra_gzs: ?struct {
-        /// Must be stacked on `value_gz`.
+    args: struct {
+        src_hash: std.zig.SrcHash,
+        src_line: u32,
+        src_column: u32,
+
+        kind: Zir.Inst.Declaration.Unwrapped.Kind,
+        name: Zir.NullTerminatedString,
+        is_pub: bool,
+        is_threadlocal: bool,
+        linkage: Zir.Inst.Declaration.Unwrapped.Linkage,
+        lib_name: Zir.NullTerminatedString = .empty,
+
+        type_gz: *GenZir,
+        /// Must be stacked on `type_gz`.
         align_gz: *GenZir,
         /// Must be stacked on `align_gz`.
         linksection_gz: *GenZir,
-        /// Must be stacked on `linksection_gz`, and have nothing stacked on it.
+        /// Must be stacked on `linksection_gz`.
         addrspace_gz: *GenZir,
+        /// Must be stacked on `addrspace_gz` and have nothing stacked on top of it.
+        value_gz: *GenZir,
     },
 ) !void {
-    const astgen = value_gz.astgen;
+    const astgen = args.value_gz.astgen;
     const gpa = astgen.gpa;
 
-    const empty_body: []Zir.Inst.Index = &.{};
-    const value_body, const align_body, const linksection_body, const addrspace_body = if (extra_gzs) |e| .{
-        value_gz.instructionsSliceUpto(e.align_gz),
-        e.align_gz.instructionsSliceUpto(e.linksection_gz),
-        e.linksection_gz.instructionsSliceUpto(e.addrspace_gz),
-        e.addrspace_gz.instructionsSlice(),
-    } else .{ value_gz.instructionsSlice(), empty_body, empty_body, empty_body };
+    const type_body = args.type_gz.instructionsSliceUpto(args.align_gz);
+    const align_body = args.align_gz.instructionsSliceUpto(args.linksection_gz);
+    const linksection_body = args.linksection_gz.instructionsSliceUpto(args.addrspace_gz);
+    const addrspace_body = args.addrspace_gz.instructionsSliceUpto(args.value_gz);
+    const value_body = args.value_gz.instructionsSlice();
+
+    const has_name = args.name != .empty;
+    const has_lib_name = args.lib_name != .empty;
+    const has_type_body = type_body.len != 0;
+    const has_special_body = align_body.len != 0 or linksection_body.len != 0 or addrspace_body.len != 0;
+    const has_value_body = value_body.len != 0;
+
+    const id: Zir.Inst.Declaration.Flags.Id = switch (args.kind) {
+        .unnamed_test => .unnamed_test,
+        .@"test" => .@"test",
+        .decltest => .decltest,
+        .@"comptime" => .@"comptime",
+        .@"usingnamespace" => if (args.is_pub) .pub_usingnamespace else .@"usingnamespace",
+        .@"const" => switch (args.linkage) {
+            .normal => if (args.is_pub) id: {
+                if (has_special_body) break :id .pub_const;
+                if (has_type_body) break :id .pub_const_typed;
+                break :id .pub_const_simple;
+            } else id: {
+                if (has_special_body) break :id .@"const";
+                if (has_type_body) break :id .const_typed;
+                break :id .const_simple;
+            },
+            .@"extern" => if (args.is_pub) id: {
+                if (has_lib_name) break :id .pub_extern_const;
+                if (has_special_body) break :id .pub_extern_const;
+                break :id .pub_extern_const_simple;
+            } else id: {
+                if (has_lib_name) break :id .extern_const;
+                if (has_special_body) break :id .extern_const;
+                break :id .extern_const_simple;
+            },
+            .@"export" => if (args.is_pub) .pub_export_const else .export_const,
+        },
+        .@"var" => switch (args.linkage) {
+            .normal => if (args.is_pub) id: {
+                if (args.is_threadlocal) break :id .pub_var_threadlocal;
+                if (has_special_body) break :id .pub_var;
+                if (has_type_body) break :id .pub_var;
+                break :id .pub_var_simple;
+            } else id: {
+                if (args.is_threadlocal) break :id .var_threadlocal;
+                if (has_special_body) break :id .@"var";
+                if (has_type_body) break :id .@"var";
+                break :id .var_simple;
+            },
+            .@"extern" => if (args.is_pub) id: {
+                if (args.is_threadlocal) break :id .pub_extern_var_threadlocal;
+                break :id .pub_extern_var;
+            } else id: {
+                if (args.is_threadlocal) break :id .extern_var_threadlocal;
+                break :id .extern_var;
+            },
+            .@"export" => if (args.is_pub) id: {
+                if (args.is_threadlocal) break :id .pub_export_var_threadlocal;
+                break :id .pub_export_var;
+            } else id: {
+                if (args.is_threadlocal) break :id .export_var_threadlocal;
+                break :id .export_var;
+            },
+        },
+    };
 
-    const value_len = astgen.countBodyLenAfterFixups(value_body);
+    assert(id.hasTypeBody() or !has_type_body);
+    assert(id.hasSpecialBodies() or !has_special_body);
+    assert(id.hasValueBody() == has_value_body);
+    assert(id.linkage() == args.linkage);
+    assert(id.hasName() == has_name);
+    assert(id.hasLibName() or !has_lib_name);
+    assert(id.isPub() == args.is_pub);
+    assert(id.isThreadlocal() == args.is_threadlocal);
+
+    const type_len = astgen.countBodyLenAfterFixups(type_body);
     const align_len = astgen.countBodyLenAfterFixups(align_body);
     const linksection_len = astgen.countBodyLenAfterFixups(linksection_body);
     const addrspace_len = astgen.countBodyLenAfterFixups(addrspace_body);
+    const value_len = astgen.countBodyLenAfterFixups(value_body);
+
+    const src_hash_arr: [4]u32 = @bitCast(args.src_hash);
+    const flags: Zir.Inst.Declaration.Flags = .{
+        .src_line = @intCast(args.src_line),
+        .src_column = @intCast(args.src_column),
+        .id = id,
+    };
+    const flags_arr: [2]u32 = @bitCast(flags);
 
-    const src_hash_arr: [4]u32 = @bitCast(src_hash);
+    const need_extra: usize =
+        @typeInfo(Zir.Inst.Declaration).@"struct".fields.len +
+        @as(usize, @intFromBool(id.hasName())) +
+        @as(usize, @intFromBool(id.hasLibName())) +
+        @as(usize, @intFromBool(id.hasTypeBody())) +
+        3 * @as(usize, @intFromBool(id.hasSpecialBodies())) +
+        @as(usize, @intFromBool(id.hasValueBody())) +
+        type_len + align_len + linksection_len + addrspace_len + value_len;
+
+    try astgen.extra.ensureUnusedCapacity(gpa, need_extra);
 
     const extra: Zir.Inst.Declaration = .{
         .src_hash_0 = src_hash_arr[0],
         .src_hash_1 = src_hash_arr[1],
         .src_hash_2 = src_hash_arr[2],
         .src_hash_3 = src_hash_arr[3],
-        .name = switch (name) {
-            .named => |tok| @enumFromInt(@intFromEnum(try astgen.identAsString(tok))),
-            .named_test => |tok| @enumFromInt(@intFromEnum(try astgen.testNameString(tok))),
-            .decltest => |tok| @enumFromInt(str_idx: {
-                const idx = astgen.string_bytes.items.len;
-                try astgen.string_bytes.append(gpa, 0); // indicates this is a test
-                try astgen.appendIdentStr(tok, &astgen.string_bytes);
-                try astgen.string_bytes.append(gpa, 0); // end of the string
-                break :str_idx idx;
-            }),
-            .unnamed_test => .unnamed_test,
-            .@"comptime" => .@"comptime",
-            .@"usingnamespace" => .@"usingnamespace",
-        },
-        .src_line = src_line,
-        .src_column = src_column,
-        .flags = .{
-            .value_body_len = @intCast(value_len),
-            .is_pub = is_pub,
-            .is_export = is_export,
-            .test_is_decltest = name == .decltest,
-            .has_align_linksection_addrspace = align_len != 0 or linksection_len != 0 or addrspace_len != 0,
-        },
+        .flags_0 = flags_arr[0],
+        .flags_1 = flags_arr[1],
     };
-    astgen.instructions.items(.data)[@intFromEnum(decl_inst)].declaration.payload_index = try astgen.addExtra(extra);
-    if (extra.flags.has_align_linksection_addrspace) {
-        try astgen.extra.appendSlice(gpa, &.{
+    astgen.instructions.items(.data)[@intFromEnum(decl_inst)].declaration.payload_index =
+        astgen.addExtraAssumeCapacity(extra);
+
+    if (id.hasName()) {
+        astgen.extra.appendAssumeCapacity(@intFromEnum(args.name));
+    }
+    if (id.hasLibName()) {
+        astgen.extra.appendAssumeCapacity(@intFromEnum(args.lib_name));
+    }
+    if (id.hasTypeBody()) {
+        astgen.extra.appendAssumeCapacity(type_len);
+    }
+    if (id.hasSpecialBodies()) {
+        astgen.extra.appendSliceAssumeCapacity(&.{
             align_len,
             linksection_len,
             addrspace_len,
         });
     }
-    try astgen.extra.ensureUnusedCapacity(gpa, value_len + align_len + linksection_len + addrspace_len);
-    astgen.appendBodyWithFixups(value_body);
-    if (extra.flags.has_align_linksection_addrspace) {
-        astgen.appendBodyWithFixups(align_body);
-        astgen.appendBodyWithFixups(linksection_body);
-        astgen.appendBodyWithFixups(addrspace_body);
+    if (id.hasValueBody()) {
+        astgen.extra.appendAssumeCapacity(value_len);
     }
 
-    if (extra_gzs) |e| {
-        e.addrspace_gz.unstack();
-        e.linksection_gz.unstack();
-        e.align_gz.unstack();
-    }
-    value_gz.unstack();
+    astgen.appendBodyWithFixups(type_body);
+    astgen.appendBodyWithFixups(align_body);
+    astgen.appendBodyWithFixups(linksection_body);
+    astgen.appendBodyWithFixups(addrspace_body);
+    astgen.appendBodyWithFixups(value_body);
+
+    args.value_gz.unstack();
+    args.addrspace_gz.unstack();
+    args.linksection_gz.unstack();
+    args.align_gz.unstack();
+    args.type_gz.unstack();
 }
 
 /// Given a list of instructions, returns a list of all instructions which are a `ref` of one of the originals,
lib/std/zig/Zir.zig
@@ -1868,10 +1868,6 @@ pub const Inst = struct {
     /// Rarer instructions are here; ones that do not fit in the 8-bit `Tag` enum.
     /// `noreturn` instructions may not go here; they must be part of the main `Tag` enum.
     pub const Extended = enum(u16) {
-        /// Declares a global variable.
-        /// `operand` is payload index to `ExtendedVar`.
-        /// `small` is `ExtendedVar.Small`.
-        variable,
         /// A struct type definition. Contains references to ZIR instructions for
         /// the field types, defaults, and alignments.
         /// `operand` is payload index to `StructDecl`.
@@ -2493,26 +2489,25 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// 0. lib_name: NullTerminatedString, // null terminated string index, if has_lib_name is set
     /// if (has_cc_ref and !has_cc_body) {
-    ///   1. cc: Ref,
+    ///   0. cc: Ref,
     /// }
     /// if (has_cc_body) {
-    ///   2. cc_body_len: u32
-    ///   3. cc_body: u32 // for each cc_body_len
+    ///   1. cc_body_len: u32
+    ///   2. cc_body: u32 // for each cc_body_len
     /// }
     /// if (has_ret_ty_ref and !has_ret_ty_body) {
-    ///   4. ret_ty: Ref,
+    ///   3. ret_ty: Ref,
     /// }
     /// if (has_ret_ty_body) {
-    ///   5. ret_ty_body_len: u32
-    ///   6. ret_ty_body: u32 // for each ret_ty_body_len
+    ///   4. ret_ty_body_len: u32
+    ///   5. ret_ty_body: u32 // for each ret_ty_body_len
     /// }
-    /// 7. noalias_bits: u32 // if has_any_noalias
+    /// 6. noalias_bits: u32 // if has_any_noalias
     ///    - each bit starting with LSB corresponds to parameter indexes
-    /// 8. body: Index // for each body_len
-    /// 9. src_locs: Func.SrcLocs // if body_len != 0
-    /// 10. proto_hash: std.zig.SrcHash // if body_len != 0; hash of function prototype
+    /// 7. body: Index // for each body_len
+    /// 8. src_locs: Func.SrcLocs // if body_len != 0
+    /// 9. proto_hash: std.zig.SrcHash // if body_len != 0; hash of function prototype
     pub const FuncFancy = struct {
         /// Points to the block that contains the param instructions for this function.
         /// If this is a `declaration`, it refers to the declaration's value body.
@@ -2522,38 +2517,16 @@ pub const Inst = struct {
 
         /// If both has_cc_ref and has_cc_body are false, it means auto calling convention.
         /// If both has_ret_ty_ref and has_ret_ty_body are false, it means void return type.
-        pub const Bits = packed struct {
+        pub const Bits = packed struct(u32) {
             is_var_args: bool,
             is_inferred_error: bool,
-            is_test: bool,
-            is_extern: bool,
             is_noinline: bool,
             has_cc_ref: bool,
             has_cc_body: bool,
             has_ret_ty_ref: bool,
             has_ret_ty_body: bool,
-            has_lib_name: bool,
             has_any_noalias: bool,
-            _: u21 = undefined,
-        };
-    };
-
-    /// Trailing:
-    /// 0. lib_name: NullTerminatedString, // null terminated string index, if has_lib_name is set
-    /// 1. align: Ref, // if has_align is set
-    /// 2. init: Ref // if has_init is set
-    /// The source node is obtained from the containing `block_inline`.
-    pub const ExtendedVar = struct {
-        var_type: Ref,
-
-        pub const Small = packed struct {
-            has_lib_name: bool,
-            has_align: bool,
-            has_init: bool,
-            is_extern: bool,
-            is_const: bool,
-            is_threadlocal: bool,
-            _: u10 = undefined,
+            _: u24 = undefined,
         };
     };
 
@@ -2582,39 +2555,301 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// 0. align_body_len: u32       // if `has_align_linksection_addrspace`; 0 means no `align`
-    /// 1. linksection_body_len: u32 // if `has_align_linksection_addrspace`; 0 means no `linksection`
-    /// 2. addrspace_body_len: u32   // if `has_align_linksection_addrspace`; 0 means no `addrspace`
-    /// 3. value_body_inst: Zir.Inst.Index
-    ///    - for each `value_body_len`
+    /// 0. name: NullTerminatedString      // if `flags.id.hasName()`
+    /// 1. lib_name: NullTerminatedString  // if `flags.id.hasLibName()`
+    /// 2. type_body_len: u32              // if `flags.id.hasTypeBody()`
+    /// 3. align_body_len: u32             // if `flags.id.hasSpecialBodies()`
+    /// 4. linksection_body_len: u32       // if `flags.id.hasSpecialBodies()`
+    /// 5. addrspace_body_len: u32         // if `flags.id.hasSpecialBodies()`
+    /// 6. value_body_len: u32             // if `flags.id.hasValueBody()`
+    /// 7. type_body_inst: Zir.Inst.Index
+    ///    - for each `type_body_len`
     ///    - body to be exited via `break_inline` to this `declaration` instruction
-    /// 4. align_body_inst: Zir.Inst.Index
+    /// 8. align_body_inst: Zir.Inst.Index
     ///    - for each `align_body_len`
     ///    - body to be exited via `break_inline` to this `declaration` instruction
-    /// 5. linksection_body_inst: Zir.Inst.Index
+    /// 9. linksection_body_inst: Zir.Inst.Index
     ///    - for each `linksection_body_len`
     ///    - body to be exited via `break_inline` to this `declaration` instruction
-    /// 6. addrspace_body_inst: Zir.Inst.Index
+    /// 10. addrspace_body_inst: Zir.Inst.Index
     ///    - for each `addrspace_body_len`
     ///    - body to be exited via `break_inline` to this `declaration` instruction
+    /// 11. value_body_inst: Zir.Inst.Index
+    ///    - for each `value_body_len`
+    ///    - body to be exited via `break_inline` to this `declaration` instruction
+    ///    - within this body, the `declaration` instruction refers to the resolved type from the type body
     pub const Declaration = struct {
         // These fields should be concatenated and reinterpreted as a `std.zig.SrcHash`.
         src_hash_0: u32,
         src_hash_1: u32,
         src_hash_2: u32,
         src_hash_3: u32,
-        /// The name of this `Decl`. Also indicates whether it is a test, comptime block, etc.
-        name: Name,
-        src_line: u32,
-        src_column: u32,
-        flags: Flags,
+        // These fields should be concatenated and reinterpreted as a `Flags`.
+        flags_0: u32,
+        flags_1: u32,
+
+        pub const Unwrapped = struct {
+            pub const Kind = enum {
+                unnamed_test,
+                @"test",
+                decltest,
+                @"comptime",
+                @"usingnamespace",
+                @"const",
+                @"var",
+            };
 
-        pub const Flags = packed struct(u32) {
-            value_body_len: u28,
+            pub const Linkage = enum {
+                normal,
+                @"extern",
+                @"export",
+            };
+
+            src_node: Ast.Node.Index,
+
+            src_line: u32,
+            src_column: u32,
+
+            kind: Kind,
+            /// Always `.empty` for `kind` of `unnamed_test`, `.@"comptime"`, `.@"usingnamespace"`.
+            name: NullTerminatedString,
+            /// Always `false` for `kind` of `unnamed_test`, `.@"test"`, `.decltest`, `.@"comptime"`.
             is_pub: bool,
-            is_export: bool,
-            test_is_decltest: bool,
-            has_align_linksection_addrspace: bool,
+            /// Always `false` for `kind != .@"var"`.
+            is_threadlocal: bool,
+            /// Always `.normal` for `kind != .@"const" and kind != .@"var"`.
+            linkage: Linkage,
+            /// Always `.empty` for `linkage != .@"extern"`.
+            lib_name: NullTerminatedString,
+
+            /// Always populated for `linkage == .@"extern".
+            type_body: ?[]const Inst.Index,
+            align_body: ?[]const Inst.Index,
+            linksection_body: ?[]const Inst.Index,
+            addrspace_body: ?[]const Inst.Index,
+            /// Always populated for `linkage != .@"extern".
+            value_body: ?[]const Inst.Index,
+        };
+
+        pub const Flags = packed struct(u64) {
+            src_line: u30,
+            src_column: u29,
+            id: Id,
+
+            pub const Id = enum(u5) {
+                unnamed_test,
+                @"test",
+                decltest,
+                @"comptime",
+
+                @"usingnamespace",
+                pub_usingnamespace,
+
+                const_simple,
+                const_typed,
+                @"const",
+                pub_const_simple,
+                pub_const_typed,
+                pub_const,
+
+                extern_const_simple,
+                extern_const,
+                pub_extern_const_simple,
+                pub_extern_const,
+
+                export_const,
+                pub_export_const,
+
+                var_simple,
+                @"var",
+                var_threadlocal,
+                pub_var_simple,
+                pub_var,
+                pub_var_threadlocal,
+
+                extern_var,
+                extern_var_threadlocal,
+                pub_extern_var,
+                pub_extern_var_threadlocal,
+
+                export_var,
+                export_var_threadlocal,
+                pub_export_var,
+                pub_export_var_threadlocal,
+
+                pub fn hasName(id: Id) bool {
+                    return switch (id) {
+                        .unnamed_test,
+                        .@"comptime",
+                        .@"usingnamespace",
+                        .pub_usingnamespace,
+                        => false,
+                        else => true,
+                    };
+                }
+
+                pub fn hasLibName(id: Id) bool {
+                    return switch (id) {
+                        .extern_const,
+                        .pub_extern_const,
+                        .extern_var,
+                        .extern_var_threadlocal,
+                        .pub_extern_var,
+                        .pub_extern_var_threadlocal,
+                        => true,
+                        else => false,
+                    };
+                }
+
+                pub fn hasTypeBody(id: Id) bool {
+                    return switch (id) {
+                        .unnamed_test,
+                        .@"test",
+                        .decltest,
+                        .@"comptime",
+                        .@"usingnamespace",
+                        .pub_usingnamespace,
+                        => false, // these constructs are untyped
+                        .const_simple,
+                        .pub_const_simple,
+                        .var_simple,
+                        .pub_var_simple,
+                        => false, // these reprs omit type bodies
+                        else => true,
+                    };
+                }
+
+                pub fn hasValueBody(id: Id) bool {
+                    return switch (id) {
+                        .extern_const_simple,
+                        .extern_const,
+                        .pub_extern_const_simple,
+                        .pub_extern_const,
+                        .extern_var,
+                        .extern_var_threadlocal,
+                        .pub_extern_var,
+                        .pub_extern_var_threadlocal,
+                        => false, // externs do not have values
+                        else => true,
+                    };
+                }
+
+                pub fn hasSpecialBodies(id: Id) bool {
+                    return switch (id) {
+                        .unnamed_test,
+                        .@"test",
+                        .decltest,
+                        .@"comptime",
+                        .@"usingnamespace",
+                        .pub_usingnamespace,
+                        => false, // these constructs are untyped
+                        .const_simple,
+                        .const_typed,
+                        .pub_const_simple,
+                        .pub_const_typed,
+                        .extern_const_simple,
+                        .pub_extern_const_simple,
+                        .var_simple,
+                        .pub_var_simple,
+                        => false, // these reprs omit special bodies
+                        else => true,
+                    };
+                }
+
+                pub fn linkage(id: Id) Declaration.Unwrapped.Linkage {
+                    return switch (id) {
+                        .extern_const_simple,
+                        .extern_const,
+                        .pub_extern_const_simple,
+                        .pub_extern_const,
+                        .extern_var,
+                        .extern_var_threadlocal,
+                        .pub_extern_var,
+                        .pub_extern_var_threadlocal,
+                        => .@"extern",
+                        .export_const,
+                        .pub_export_const,
+                        .export_var,
+                        .export_var_threadlocal,
+                        .pub_export_var,
+                        .pub_export_var_threadlocal,
+                        => .@"export",
+                        else => .normal,
+                    };
+                }
+
+                pub fn kind(id: Id) Declaration.Unwrapped.Kind {
+                    return switch (id) {
+                        .unnamed_test => .unnamed_test,
+                        .@"test" => .@"test",
+                        .decltest => .decltest,
+                        .@"comptime" => .@"comptime",
+                        .@"usingnamespace", .pub_usingnamespace => .@"usingnamespace",
+                        .const_simple,
+                        .const_typed,
+                        .@"const",
+                        .pub_const_simple,
+                        .pub_const_typed,
+                        .pub_const,
+                        .extern_const_simple,
+                        .extern_const,
+                        .pub_extern_const_simple,
+                        .pub_extern_const,
+                        .export_const,
+                        .pub_export_const,
+                        => .@"const",
+                        .var_simple,
+                        .@"var",
+                        .var_threadlocal,
+                        .pub_var_simple,
+                        .pub_var,
+                        .pub_var_threadlocal,
+                        .extern_var,
+                        .extern_var_threadlocal,
+                        .pub_extern_var,
+                        .pub_extern_var_threadlocal,
+                        .export_var,
+                        .export_var_threadlocal,
+                        .pub_export_var,
+                        .pub_export_var_threadlocal,
+                        => .@"var",
+                    };
+                }
+
+                pub fn isPub(id: Id) bool {
+                    return switch (id) {
+                        .pub_usingnamespace,
+                        .pub_const_simple,
+                        .pub_const_typed,
+                        .pub_const,
+                        .pub_extern_const_simple,
+                        .pub_extern_const,
+                        .pub_export_const,
+                        .pub_var_simple,
+                        .pub_var,
+                        .pub_var_threadlocal,
+                        .pub_extern_var,
+                        .pub_extern_var_threadlocal,
+                        .pub_export_var,
+                        .pub_export_var_threadlocal,
+                        => true,
+                        else => false,
+                    };
+                }
+
+                pub fn isThreadlocal(id: Id) bool {
+                    return switch (id) {
+                        .var_threadlocal,
+                        .pub_var_threadlocal,
+                        .extern_var_threadlocal,
+                        .pub_extern_var_threadlocal,
+                        .export_var_threadlocal,
+                        .pub_export_var_threadlocal,
+                        => true,
+                        else => false,
+                    };
+                }
+            };
         };
 
         pub const Name = enum(u32) {
@@ -2647,17 +2882,24 @@ pub const Inst = struct {
         };
 
         pub const Bodies = struct {
-            value_body: []const Index,
+            type_body: ?[]const Index,
             align_body: ?[]const Index,
             linksection_body: ?[]const Index,
             addrspace_body: ?[]const Index,
+            value_body: ?[]const Index,
         };
 
         pub fn getBodies(declaration: Declaration, extra_end: u32, zir: Zir) Bodies {
             var extra_index: u32 = extra_end;
-            const value_body_len = declaration.flags.value_body_len;
+            const value_body_len = declaration.value_body_len;
+            const type_body_len: u32 = len: {
+                if (!declaration.flags().kind.hasTypeBody()) break :len 0;
+                const len = zir.extra[extra_index];
+                extra_index += 1;
+                break :len len;
+            };
             const align_body_len, const linksection_body_len, const addrspace_body_len = lens: {
-                if (!declaration.flags.has_align_linksection_addrspace) {
+                if (!declaration.flags.kind.hasSpecialBodies()) {
                     break :lens .{ 0, 0, 0 };
                 }
                 const lens = zir.extra[extra_index..][0..3].*;
@@ -2665,21 +2907,30 @@ pub const Inst = struct {
                 break :lens lens;
             };
             return .{
-                .value_body = b: {
-                    defer extra_index += value_body_len;
-                    break :b zir.bodySlice(extra_index, value_body_len);
+                .type_body = if (type_body_len == 0) null else b: {
+                    const b = zir.bodySlice(extra_index, type_body_len);
+                    extra_index += type_body_len;
+                    break :b b;
                 },
                 .align_body = if (align_body_len == 0) null else b: {
-                    defer extra_index += align_body_len;
-                    break :b zir.bodySlice(extra_index, align_body_len);
+                    const b = zir.bodySlice(extra_index, align_body_len);
+                    extra_index += align_body_len;
+                    break :b b;
                 },
                 .linksection_body = if (linksection_body_len == 0) null else b: {
-                    defer extra_index += linksection_body_len;
-                    break :b zir.bodySlice(extra_index, linksection_body_len);
+                    const b = zir.bodySlice(extra_index, linksection_body_len);
+                    extra_index += linksection_body_len;
+                    break :b b;
                 },
                 .addrspace_body = if (addrspace_body_len == 0) null else b: {
-                    defer extra_index += addrspace_body_len;
-                    break :b zir.bodySlice(extra_index, addrspace_body_len);
+                    const b = zir.bodySlice(extra_index, addrspace_body_len);
+                    extra_index += addrspace_body_len;
+                    break :b b;
+                },
+                .value_body = if (value_body_len == 0) null else b: {
+                    const b = zir.bodySlice(extra_index, value_body_len);
+                    extra_index += value_body_len;
+                    break :b b;
                 },
             };
         }
@@ -3711,18 +3962,18 @@ pub const DeclContents = struct {
 pub fn findTrackable(zir: Zir, gpa: Allocator, contents: *DeclContents, decl_inst: Zir.Inst.Index) !void {
     contents.clear();
 
-    const declaration, const extra_end = zir.getDeclaration(decl_inst);
-    const bodies = declaration.getBodies(extra_end, zir);
+    const decl = zir.getDeclaration(decl_inst);
 
     // `defer` instructions duplicate the same body arbitrarily many times, but we only want to traverse
     // their contents once per defer. So, we store the extra index of the body here to deduplicate.
     var found_defers: std.AutoHashMapUnmanaged(u32, void) = .empty;
     defer found_defers.deinit(gpa);
 
-    try zir.findTrackableBody(gpa, contents, &found_defers, bodies.value_body);
-    if (bodies.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
-    if (bodies.linksection_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
-    if (bodies.addrspace_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
+    if (decl.type_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
+    if (decl.align_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
+    if (decl.linksection_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
+    if (decl.addrspace_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
+    if (decl.value_body) |b| try zir.findTrackableBody(gpa, contents, &found_defers, b);
 }
 
 /// Like `findTrackable`, but only considers the `main_struct_inst` instruction. This may return more than
@@ -3991,7 +4242,6 @@ fn findTrackableInner(
                 .value_placeholder => unreachable,
 
                 // Once again, we start with the boring tags.
-                .variable,
                 .this,
                 .ret_addr,
                 .builtin_src,
@@ -4237,7 +4487,6 @@ fn findTrackableInner(
             const inst_data = datas[@intFromEnum(inst)].pl_node;
             const extra = zir.extraData(Inst.FuncFancy, inst_data.payload_index);
             var extra_index: usize = extra.end;
-            extra_index += @intFromBool(extra.data.bits.has_lib_name);
 
             if (extra.data.bits.has_cc_body) {
                 const body_len = zir.extra[extra_index];
@@ -4470,8 +4719,7 @@ pub fn getParamBody(zir: Zir, fn_inst: Inst.Index) []const Zir.Inst.Index {
             return zir.bodySlice(param_block.end, param_block.data.body_len);
         },
         .declaration => {
-            const decl, const extra_end = zir.getDeclaration(param_block_index);
-            return decl.getBodies(extra_end, zir).value_body;
+            return zir.getDeclaration(param_block_index).value_body.?;
         },
         else => unreachable,
     }
@@ -4526,7 +4774,6 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
             var ret_ty_ref: Inst.Ref = .void_type;
             var ret_ty_body: []const Inst.Index = &.{};
 
-            extra_index += @intFromBool(extra.data.bits.has_lib_name);
             if (extra.data.bits.has_cc_body) {
                 extra_index += zir.extra[extra_index] + 1;
             } else if (extra.data.bits.has_cc_ref) {
@@ -4555,17 +4802,7 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
         },
         else => unreachable,
     };
-    const param_body = switch (tags[@intFromEnum(info.param_block)]) {
-        .block, .block_comptime, .block_inline => param_body: {
-            const param_block = zir.extraData(Inst.Block, datas[@intFromEnum(info.param_block)].pl_node.payload_index);
-            break :param_body zir.bodySlice(param_block.end, param_block.data.body_len);
-        },
-        .declaration => param_body: {
-            const decl, const extra_end = zir.getDeclaration(info.param_block);
-            break :param_body decl.getBodies(extra_end, zir).value_body;
-        },
-        else => unreachable,
-    };
+    const param_body = zir.getParamBody(fn_inst);
     var total_params_len: u32 = 0;
     for (param_body) |inst| {
         switch (tags[@intFromEnum(inst)]) {
@@ -4585,13 +4822,74 @@ pub fn getFnInfo(zir: Zir, fn_inst: Inst.Index) FnInfo {
     };
 }
 
-pub fn getDeclaration(zir: Zir, inst: Zir.Inst.Index) struct { Inst.Declaration, u32 } {
+pub fn getDeclaration(zir: Zir, inst: Zir.Inst.Index) Inst.Declaration.Unwrapped {
     assert(zir.instructions.items(.tag)[@intFromEnum(inst)] == .declaration);
     const pl_node = zir.instructions.items(.data)[@intFromEnum(inst)].declaration;
     const extra = zir.extraData(Inst.Declaration, pl_node.payload_index);
+
+    const flags_vals: [2]u32 = .{ extra.data.flags_0, extra.data.flags_1 };
+    const flags: Inst.Declaration.Flags = @bitCast(flags_vals);
+
+    var extra_index = extra.end;
+
+    const name: NullTerminatedString = if (flags.id.hasName()) name: {
+        const name = zir.extra[extra_index];
+        extra_index += 1;
+        break :name @enumFromInt(name);
+    } else .empty;
+
+    const lib_name: NullTerminatedString = if (flags.id.hasLibName()) lib_name: {
+        const lib_name = zir.extra[extra_index];
+        extra_index += 1;
+        break :lib_name @enumFromInt(lib_name);
+    } else .empty;
+
+    const type_body_len: u32 = if (flags.id.hasTypeBody()) len: {
+        const len = zir.extra[extra_index];
+        extra_index += 1;
+        break :len len;
+    } else 0;
+    const align_body_len: u32, const linksection_body_len: u32, const addrspace_body_len: u32 = lens: {
+        if (!flags.id.hasSpecialBodies()) break :lens .{ 0, 0, 0 };
+        const lens = zir.extra[extra_index..][0..3].*;
+        extra_index += 3;
+        break :lens lens;
+    };
+    const value_body_len: u32 = if (flags.id.hasValueBody()) len: {
+        const len = zir.extra[extra_index];
+        extra_index += 1;
+        break :len len;
+    } else 0;
+
+    const type_body = zir.bodySlice(extra_index, type_body_len);
+    extra_index += type_body_len;
+    const align_body = zir.bodySlice(extra_index, align_body_len);
+    extra_index += align_body_len;
+    const linksection_body = zir.bodySlice(extra_index, linksection_body_len);
+    extra_index += linksection_body_len;
+    const addrspace_body = zir.bodySlice(extra_index, addrspace_body_len);
+    extra_index += addrspace_body_len;
+    const value_body = zir.bodySlice(extra_index, value_body_len);
+    extra_index += value_body_len;
+
     return .{
-        extra.data,
-        @intCast(extra.end),
+        .src_node = pl_node.src_node,
+
+        .src_line = flags.src_line,
+        .src_column = flags.src_column,
+
+        .kind = flags.id.kind(),
+        .name = name,
+        .is_pub = flags.id.isPub(),
+        .is_threadlocal = flags.id.isThreadlocal(),
+        .linkage = flags.id.linkage(),
+        .lib_name = lib_name,
+
+        .type_body = if (type_body_len == 0) null else type_body,
+        .align_body = if (align_body_len == 0) null else align_body,
+        .linksection_body = if (linksection_body_len == 0) null else linksection_body,
+        .addrspace_body = if (addrspace_body_len == 0) null else addrspace_body,
+        .value_body = if (value_body_len == 0) null else value_body,
     };
 }
 
@@ -4636,7 +4934,6 @@ pub fn getAssociatedSrcHash(zir: Zir, inst: Zir.Inst.Index) ?std.zig.SrcHash {
             }
             const bits = extra.data.bits;
             var extra_index = extra.end;
-            extra_index += @intFromBool(bits.has_lib_name);
             if (bits.has_cc_body) {
                 const body_len = zir.extra[extra_index];
                 extra_index += 1 + body_len;
src/codegen/llvm.zig
@@ -2939,7 +2939,6 @@ pub const Object = struct {
         const sret = firstParamSRet(fn_info, zcu, target);
 
         const is_extern, const lib_name = switch (ip.indexToKey(val.toIntern())) {
-            .variable => |variable| .{ false, variable.lib_name },
             .@"extern" => |@"extern"| .{ true, @"extern".lib_name },
             else => .{ false, .none },
         };
@@ -4803,7 +4802,7 @@ pub const NavGen = struct {
         const resolved = nav.status.resolved;
 
         const is_extern, const lib_name, const is_threadlocal, const is_weak_linkage, const is_dll_import, const is_const, const init_val, const owner_nav = switch (ip.indexToKey(resolved.val)) {
-            .variable => |variable| .{ false, variable.lib_name, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav },
+            .variable => |variable| .{ false, .none, variable.is_threadlocal, variable.is_weak_linkage, false, false, variable.init, variable.owner_nav },
             .@"extern" => |@"extern"| .{ true, @"extern".lib_name, @"extern".is_threadlocal, @"extern".is_weak_linkage, @"extern".is_dll_import, @"extern".is_const, .none, @"extern".owner_nav },
             else => .{ false, .none, false, false, false, true, resolved.val, nav_index },
         };
src/link/Wasm/ZigObject.zig
@@ -241,7 +241,7 @@ pub fn updateNav(
 
     const nav_val = zcu.navValue(nav_index);
     const is_extern, const lib_name, const nav_init = switch (ip.indexToKey(nav_val.toIntern())) {
-        .variable => |variable| .{ false, variable.lib_name, Value.fromInterned(variable.init) },
+        .variable => |variable| .{ false, .none, Value.fromInterned(variable.init) },
         .func => return,
         .@"extern" => |@"extern"| if (ip.isFunctionType(nav.typeOf(ip)))
             return
src/link/Dwarf.zig
@@ -2259,24 +2259,13 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In
     switch (ip.indexToKey(nav_val.toIntern())) {
         else => {
             assert(file.zir_loaded);
-            const decl = file.zir.getDeclaration(inst_info.inst)[0];
+            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);
                 break :parent .{
                     parent_namespace_ptr.owner_type,
-                    switch (decl.name) {
-                        .@"comptime",
-                        .@"usingnamespace",
-                        .unnamed_test,
-                        => DW.ACCESS.private,
-                        _ => if (decl.name.isNamedTest(file.zir))
-                            DW.ACCESS.private
-                        else if (decl.flags.is_pub)
-                            DW.ACCESS.public
-                        else
-                            DW.ACCESS.private,
-                    },
+                    if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
                 };
             } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private };
 
@@ -2301,24 +2290,13 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In
         },
         .variable => |variable| {
             assert(file.zir_loaded);
-            const decl = file.zir.getDeclaration(inst_info.inst)[0];
+            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);
                 break :parent .{
                     parent_namespace_ptr.owner_type,
-                    switch (decl.name) {
-                        .@"comptime",
-                        .@"usingnamespace",
-                        .unnamed_test,
-                        => DW.ACCESS.private,
-                        _ => if (decl.name.isNamedTest(file.zir))
-                            DW.ACCESS.private
-                        else if (decl.flags.is_pub)
-                            DW.ACCESS.public
-                        else
-                            DW.ACCESS.private,
-                    },
+                    if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
                 };
             } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private };
 
@@ -2341,24 +2319,13 @@ pub fn initWipNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool.Nav.In
         },
         .func => |func| {
             assert(file.zir_loaded);
-            const decl = file.zir.getDeclaration(inst_info.inst)[0];
+            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);
                 break :parent .{
                     parent_namespace_ptr.owner_type,
-                    switch (decl.name) {
-                        .@"comptime",
-                        .@"usingnamespace",
-                        .unnamed_test,
-                        => DW.ACCESS.private,
-                        _ => if (decl.name.isNamedTest(file.zir))
-                            DW.ACCESS.private
-                        else if (decl.flags.is_pub)
-                            DW.ACCESS.public
-                        else
-                            DW.ACCESS.private,
-                    },
+                    if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
                 };
             } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private };
 
@@ -2585,12 +2552,11 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool
     const inst_info = nav.srcInst(ip).resolveFull(ip).?;
     const file = zcu.fileByIndex(inst_info.file);
     assert(file.zir_loaded);
-    const decl = file.zir.getDeclaration(inst_info.inst)[0];
+    const decl = file.zir.getDeclaration(inst_info.inst);
 
-    const is_test = switch (decl.name) {
-        .unnamed_test => true,
-        .@"comptime", .@"usingnamespace" => false,
-        _ => decl.name.isNamedTest(file.zir),
+    const is_test = switch (decl.kind) {
+        .unnamed_test, .@"test", .decltest => true,
+        .@"comptime", .@"usingnamespace", .@"const", .@"var" => false,
     };
     if (is_test) {
         // This isn't actually a comptime Nav! It's a test, so it'll definitely never be referenced at comptime.
@@ -2601,7 +2567,7 @@ pub fn updateComptimeNav(dwarf: *Dwarf, pt: Zcu.PerThread, nav_index: InternPool
         const parent_namespace_ptr = ip.namespacePtr(ip.getCau(cau).namespace);
         break :parent .{
             parent_namespace_ptr.owner_type,
-            if (decl.flags.is_pub) DW.ACCESS.public else DW.ACCESS.private,
+            if (decl.is_pub) DW.ACCESS.public else DW.ACCESS.private,
         };
     } else .{ zcu.fileRootType(inst_info.file), DW.ACCESS.private };
 
@@ -4198,9 +4164,7 @@ pub fn updateNavLineNumber(dwarf: *Dwarf, zcu: *Zcu, nav_index: InternPool.Nav.I
     assert(inst_info.inst != .main_struct_inst);
     const file = zcu.fileByIndex(inst_info.file);
 
-    const inst = file.zir.instructions.get(@intFromEnum(inst_info.inst));
-    assert(inst.tag == .declaration);
-    const line = file.zir.extraData(Zir.Inst.Declaration, inst.data.declaration.payload_index).data.src_line;
+    const line = file.zir.getDeclaration(inst_info.inst).src_line;
     var line_buf: [4]u8 = undefined;
     std.mem.writeInt(u32, &line_buf, line, dwarf.endian);
 
src/Zcu/PerThread.zig
@@ -469,12 +469,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
             {
                 var it = old_zir.declIterator(old_inst);
                 while (it.next()) |decl_inst| {
-                    const decl_name = old_zir.getDeclaration(decl_inst)[0].name;
-                    switch (decl_name) {
-                        .@"comptime", .@"usingnamespace", .unnamed_test => continue,
-                        _ => if (decl_name.isNamedTest(old_zir)) continue,
-                    }
-                    const name_zir = decl_name.toString(old_zir).?;
+                    const name_zir = old_zir.getDeclaration(decl_inst).name;
+                    if (name_zir == .empty) continue;
                     const name_ip = try zcu.intern_pool.getOrPutString(
                         zcu.gpa,
                         pt.tid,
@@ -488,12 +484,8 @@ pub fn updateZirRefs(pt: Zcu.PerThread) Allocator.Error!void {
             {
                 var it = new_zir.declIterator(new_inst);
                 while (it.next()) |decl_inst| {
-                    const decl_name = new_zir.getDeclaration(decl_inst)[0].name;
-                    switch (decl_name) {
-                        .@"comptime", .@"usingnamespace", .unnamed_test => continue,
-                        _ => if (decl_name.isNamedTest(new_zir)) continue,
-                    }
-                    const name_zir = decl_name.toString(new_zir).?;
+                    const name_zir = new_zir.getDeclaration(decl_inst).name;
+                    if (name_zir == .empty) continue;
                     const name_ip = try zcu.intern_pool.getOrPutString(
                         zcu.gpa,
                         pt.tid,
@@ -1252,10 +1244,7 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
     };
     defer block.instructions.deinit(gpa);
 
-    const zir_decl: Zir.Inst.Declaration, const decl_bodies: Zir.Inst.Declaration.Bodies = decl: {
-        const decl, const extra_end = zir.getDeclaration(inst_info.inst);
-        break :decl .{ decl, decl.getBodies(extra_end, zir) };
-    };
+    const zir_decl = zir.getDeclaration(inst_info.inst);
 
     // We have to fetch this state before resolving the body because of the `nav_already_populated`
     // case below. We might change the language in future so that align/linksection/etc for functions
@@ -1265,7 +1254,134 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
         .nav => |nav| ip.getNav(nav),
     };
 
-    const result_ref = try sema.resolveInlineBody(&block, decl_bodies.value_body, inst_info.inst);
+    const align_src = block.src(.{ .node_offset_var_decl_align = 0 });
+    const section_src = block.src(.{ .node_offset_var_decl_section = 0 });
+    const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 });
+    const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 });
+    const init_src = block.src(.{ .node_offset_var_decl_init = 0 });
+
+    // First, we must resolve the declaration's type. To do this, we analyze the type body if available,
+    // or otherwise, we analyze the value body, populating `early_val` in the process.
+
+    const decl_ty: Type, const early_val: ?Value = if (zir_decl.type_body) |type_body| ty: {
+        // We evaluate only the type now; no need for the value yet.
+        const uncoerced_type_ref = try sema.resolveInlineBody(&block, type_body, inst_info.inst);
+        const type_ref = try sema.coerce(&block, .type, uncoerced_type_ref, ty_src);
+        break :ty .{ .fromInterned(type_ref.toInterned().?), null };
+    } else ty: {
+        // We don't have a type body, so we need to evaluate the value immediately.
+        const value_body = zir_decl.value_body.?;
+        const result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst);
+        const val = try sema.resolveFinalDeclValue(&block, init_src, result_ref);
+        break :ty .{ val.typeOf(zcu), val };
+    };
+
+    switch (zir_decl.kind) {
+        .unnamed_test, .@"test", .decltest => assert(decl_ty.zigTypeTag(zcu) == .@"fn"),
+        .@"comptime" => assert(decl_ty.toIntern() == .void_type),
+        .@"usingnamespace" => {},
+        .@"const" => {},
+        .@"var" => try sema.validateVarType(
+            &block,
+            if (zir_decl.type_body != null) ty_src else init_src,
+            decl_ty,
+            zir_decl.linkage == .@"extern",
+        ),
+    }
+
+    // Now that we know the type, we can evaluate the alignment, linksection, and addrspace, to determine
+    // the full pointer type of this declaration.
+
+    const alignment: InternPool.Alignment = a: {
+        const align_body = zir_decl.align_body orelse break :a .none;
+        const align_ref = try sema.resolveInlineBody(&block, align_body, inst_info.inst);
+        break :a try sema.analyzeAsAlign(&block, align_src, align_ref);
+    };
+
+    const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
+        const linksection_body = zir_decl.linksection_body orelse break :ls .none;
+        const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_info.inst);
+        const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{
+            .needed_comptime_reason = "linksection must be comptime-known",
+        });
+        if (std.mem.indexOfScalar(u8, bytes, 0) != null) {
+            return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{});
+        } else if (bytes.len == 0) {
+            return sema.fail(&block, section_src, "linksection cannot be empty", .{});
+        }
+        break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
+    };
+
+    const @"addrspace": std.builtin.AddressSpace = as: {
+        const addrspace_ctx: Sema.AddressSpaceContext = switch (zir_decl.kind) {
+            .@"var" => .variable,
+            else => switch (decl_ty.zigTypeTag(zcu)) {
+                .@"fn" => .function,
+                else => .constant,
+            },
+        };
+        const target = zcu.getTarget();
+        const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) {
+            .function => target_util.defaultAddressSpace(target, .function),
+            .variable => target_util.defaultAddressSpace(target, .global_mutable),
+            .constant => target_util.defaultAddressSpace(target, .global_constant),
+            else => unreachable,
+        };
+        const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst);
+        break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx);
+    };
+
+    // Lastly, we must evaluate the value if we have not already done so. Note, however, that extern declarations
+    // don't have an associated value body.
+
+    const final_val: ?Value = early_val orelse if (zir_decl.value_body) |value_body| val: {
+        // Put the resolved type into `inst_map` to be used as the result type of the init.
+        try sema.inst_map.ensureSpaceForInstructions(gpa, &.{inst_info.inst});
+        sema.inst_map.putAssumeCapacity(inst_info.inst, Air.internedToRef(decl_ty.toIntern()));
+        const uncoerced_result_ref = try sema.resolveInlineBody(&block, value_body, inst_info.inst);
+        assert(sema.inst_map.remove(inst_info.inst));
+
+        const result_ref = try sema.coerce(&block, decl_ty, uncoerced_result_ref, init_src);
+        break :val try sema.resolveFinalDeclValue(&block, init_src, result_ref);
+    } else null;
+
+    // TODO: missing validation?
+
+    const decl_val: Value = switch (zir_decl.linkage) {
+        .normal, .@"export" => switch (zir_decl.kind) {
+            .@"var" => .fromInterned(try pt.intern(.{ .variable = .{
+                .ty = decl_ty.toIntern(),
+                .init = final_val.?.toIntern(),
+                .owner_nav = cau.owner.unwrap().nav,
+                .is_threadlocal = zir_decl.is_threadlocal,
+                .is_weak_linkage = false,
+            } })),
+            else => final_val.?,
+        },
+        .@"extern" => val: {
+            assert(final_val == null); // extern decls do not have a value body
+            const lib_name: ?[]const u8 = if (zir_decl.lib_name != .empty) l: {
+                break :l zir.nullTerminatedString(zir_decl.lib_name);
+            } else null;
+            if (lib_name) |l| {
+                const lib_name_src = block.src(.{ .node_offset_lib_name = 0 });
+                try sema.handleExternLibName(&block, lib_name_src, l);
+            }
+            break :val .fromInterned(try pt.getExtern(.{
+                .name = old_nav_info.name,
+                .ty = decl_ty.toIntern(),
+                .lib_name = try ip.getOrPutStringOpt(gpa, pt.tid, lib_name, .no_embedded_nulls),
+                .is_const = zir_decl.kind == .@"const",
+                .is_threadlocal = zir_decl.is_threadlocal,
+                .is_weak_linkage = false,
+                .is_dll_import = false,
+                .alignment = alignment,
+                .@"addrspace" = @"addrspace",
+                .zir_index = cau.zir_index, // `declaration` instruction
+                .owner_nav = undefined, // ignored by `getExtern`
+            }));
+        },
+    };
 
     const nav_index = switch (cau.owner.unwrap()) {
         .none => {
@@ -1282,15 +1398,6 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
         .type => unreachable, // Handled at top of function.
     };
 
-    const align_src = block.src(.{ .node_offset_var_decl_align = 0 });
-    const section_src = block.src(.{ .node_offset_var_decl_section = 0 });
-    const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 });
-    const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 });
-    const init_src = block.src(.{ .node_offset_var_decl_init = 0 });
-
-    const decl_val = try sema.resolveFinalDeclValue(&block, init_src, result_ref);
-    const decl_ty = decl_val.typeOf(zcu);
-
     switch (decl_val.toIntern()) {
         .generic_poison => unreachable, // assertion failure
         .unreachable_value => unreachable, // assertion failure
@@ -1331,50 +1438,10 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
     };
 
     // Keep in sync with logic in `Sema.zirVarExtended`.
-    const alignment: InternPool.Alignment = a: {
-        const align_body = decl_bodies.align_body orelse break :a .none;
-        const align_ref = try sema.resolveInlineBody(&block, align_body, inst_info.inst);
-        break :a try sema.analyzeAsAlign(&block, align_src, align_ref);
-    };
-
-    const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
-        const linksection_body = decl_bodies.linksection_body orelse break :ls .none;
-        const linksection_ref = try sema.resolveInlineBody(&block, linksection_body, inst_info.inst);
-        const bytes = try sema.toConstString(&block, section_src, linksection_ref, .{
-            .needed_comptime_reason = "linksection must be comptime-known",
-        });
-        if (std.mem.indexOfScalar(u8, bytes, 0) != null) {
-            return sema.fail(&block, section_src, "linksection cannot contain null bytes", .{});
-        } else if (bytes.len == 0) {
-            return sema.fail(&block, section_src, "linksection cannot be empty", .{});
-        }
-        break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
-    };
-
-    const @"addrspace": std.builtin.AddressSpace = as: {
-        const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(decl_val.toIntern())) {
-            .func => .function,
-            .variable => .variable,
-            .@"extern" => |e| if (ip.indexToKey(e.ty) == .func_type)
-                .function
-            else
-                .variable,
-            else => .constant,
-        };
-        const target = zcu.getTarget();
-        const addrspace_body = decl_bodies.addrspace_body orelse break :as switch (addrspace_ctx) {
-            .function => target_util.defaultAddressSpace(target, .function),
-            .variable => target_util.defaultAddressSpace(target, .global_mutable),
-            .constant => target_util.defaultAddressSpace(target, .global_constant),
-            else => unreachable,
-        };
-        const addrspace_ref = try sema.resolveInlineBody(&block, addrspace_body, inst_info.inst);
-        break :as try sema.analyzeAsAddressSpace(&block, addrspace_src, addrspace_ref, addrspace_ctx);
-    };
 
     if (is_owned_fn) {
         // linksection etc are legal, except some targets do not support function alignment.
-        if (decl_bodies.align_body != null and !target_util.supportsFunctionAlignment(zcu.getTarget())) {
+        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)) {
@@ -1383,13 +1450,13 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
             .func => "function alias", // slightly clearer message, since you *can* specify these on function *declarations*
             else => "comptime-only type",
         };
-        if (decl_bodies.align_body != null) {
+        if (zir_decl.align_body != null) {
             return sema.fail(&block, align_src, "cannot specify alignment of {s}", .{reason});
         }
-        if (decl_bodies.linksection_body != null) {
+        if (zir_decl.linksection_body != null) {
             return sema.fail(&block, section_src, "cannot specify linksection of {s}", .{reason});
         }
-        if (decl_bodies.addrspace_body != null) {
+        if (zir_decl.addrspace_body != null) {
             return sema.fail(&block, addrspace_src, "cannot specify addrspace of {s}", .{reason});
         }
     }
@@ -1404,9 +1471,9 @@ fn semaCau(pt: Zcu.PerThread, cau_index: InternPool.Cau.Index) !SemaCauResult {
     // Mark the `Cau` as completed before evaluating the export!
     assert(zcu.analysis_in_progress.swapRemove(anal_unit));
 
-    if (zir_decl.flags.is_export) {
-        const export_src = block.src(.{ .token_offset = @intFromBool(zir_decl.flags.is_pub) });
-        const name_slice = zir.nullTerminatedString(zir_decl.name.toString(zir).?);
+    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);
     }
@@ -1919,13 +1986,11 @@ const ScanDeclIter = struct {
         const zir = file.zir;
         const ip = &zcu.intern_pool;
 
-        const inst_data = zir.instructions.items(.data)[@intFromEnum(decl_inst)].declaration;
-        const extra = zir.extraData(Zir.Inst.Declaration, inst_data.payload_index);
-        const declaration = extra.data;
+        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 (declaration.name) {
+        const maybe_name: InternPool.OptionalNullTerminatedString, const kind: Kind, const is_named_test: bool = switch (decl.kind) {
             .@"comptime" => info: {
                 if (iter.pass != .unnamed) return;
                 break :info .{
@@ -1954,21 +2019,22 @@ const ScanDeclIter = struct {
                     false,
                 };
             },
-            _ => if (declaration.name.isNamedTest(zir)) info: {
+            .@"test", .decltest => |kind| info: {
                 // 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 = if (declaration.flags.test_is_decltest) "decltest" else "test";
+                const prefix = @tagName(kind);
                 break :info .{
-                    (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(declaration.name.toString(zir).?) })).toOptional(),
+                    (try iter.avoidNameConflict("{s}.{s}", .{ prefix, zir.nullTerminatedString(decl.name) })).toOptional(),
                     .@"test",
                     true,
                 };
-            } else info: {
+            },
+            .@"const", .@"var" => info: {
                 if (iter.pass != .named) return;
                 const name = try ip.getOrPutString(
                     gpa,
                     pt.tid,
-                    zir.nullTerminatedString(declaration.name.toString(zir).?),
+                    zir.nullTerminatedString(decl.name),
                     .no_embedded_nulls,
                 );
                 try iter.seen_decls.putNoClobber(gpa, name, {});
@@ -2030,7 +2096,7 @@ const ScanDeclIter = struct {
                         if (comp.incremental) {
                             @panic("'usingnamespace' is not supported by incremental compilation");
                         }
-                        if (declaration.flags.is_pub) {
+                        if (decl.is_pub) {
                             try namespace.pub_usingnamespace.append(gpa, nav);
                         } else {
                             try namespace.priv_usingnamespace.append(gpa, nav);
@@ -2056,7 +2122,7 @@ const ScanDeclIter = struct {
                         break :a true;
                     },
                     .named => a: {
-                        if (declaration.flags.is_pub) {
+                        if (decl.is_pub) {
                             try namespace.pub_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
                         } else {
                             try namespace.priv_decls.putContext(gpa, nav, {}, .{ .zcu = zcu });
@@ -2068,7 +2134,7 @@ const ScanDeclIter = struct {
             },
         };
 
-        if (existing_cau == null and (want_analysis or declaration.flags.is_export)) {
+        if (existing_cau == 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 },
src/codegen.zig
@@ -853,7 +853,7 @@ fn genNavRef(
 
     const nav_index, const is_extern, const lib_name, const is_threadlocal = switch (ip.indexToKey(zcu.navValue(ref_nav_index).toIntern())) {
         .func => |func| .{ func.owner_nav, false, .none, false },
-        .variable => |variable| .{ variable.owner_nav, false, variable.lib_name, variable.is_threadlocal },
+        .variable => |variable| .{ variable.owner_nav, false, .none, variable.is_threadlocal },
         .@"extern" => |@"extern"| .{ @"extern".owner_nav, true, @"extern".lib_name, @"extern".is_threadlocal },
         else => .{ ref_nav_index, false, .none, false },
     };
src/InternPool.zig
@@ -2018,7 +2018,6 @@ pub const Key = union(enum) {
         ty: Index,
         init: Index,
         owner_nav: Nav.Index,
-        lib_name: OptionalNullTerminatedString,
         is_threadlocal: bool,
         is_weak_linkage: bool,
     };
@@ -2741,7 +2740,6 @@ pub const Key = union(enum) {
                 return a_info.owner_nav == b_info.owner_nav and
                     a_info.ty == b_info.ty and
                     a_info.init == b_info.init and
-                    a_info.lib_name == b_info.lib_name and
                     a_info.is_threadlocal == b_info.is_threadlocal and
                     a_info.is_weak_linkage == b_info.is_weak_linkage;
             },
@@ -5573,9 +5571,6 @@ pub const Tag = enum(u8) {
         /// May be `none`.
         init: Index,
         owner_nav: Nav.Index,
-        /// Library name if specified.
-        /// For example `extern "c" var stderrp = ...` would have 'c' as library name.
-        lib_name: OptionalNullTerminatedString,
         flags: Flags,
 
         pub const Flags = packed struct(u32) {
@@ -6928,7 +6923,6 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
                 .ty = extra.ty,
                 .init = extra.init,
                 .owner_nav = extra.owner_nav,
-                .lib_name = extra.lib_name,
                 .is_threadlocal = extra.flags.is_threadlocal,
                 .is_weak_linkage = extra.flags.is_weak_linkage,
             } };
@@ -7575,7 +7569,6 @@ pub fn get(ip: *InternPool, gpa: Allocator, tid: Zcu.PerThread.Id, key: Key) All
                     .ty = variable.ty,
                     .init = variable.init,
                     .owner_nav = variable.owner_nav,
-                    .lib_name = variable.lib_name,
                     .flags = .{
                         .is_const = false,
                         .is_threadlocal = variable.is_threadlocal,
src/print_zir.zig
@@ -542,7 +542,6 @@ const Writer = struct {
 
             .@"asm" => try self.writeAsm(stream, extended, false),
             .asm_expr => try self.writeAsm(stream, extended, true),
-            .variable => try self.writeVarExtended(stream, extended),
             .alloc => try self.writeAllocExtended(stream, extended),
 
             .compile_log => try self.writeNodeMultiOp(stream, extended),
@@ -2347,7 +2346,6 @@ const Writer = struct {
             inferred_error_set,
             false,
             false,
-            false,
 
             .none,
             &.{},
@@ -2371,13 +2369,6 @@ const Writer = struct {
         var ret_ty_ref: Zir.Inst.Ref = .none;
         var ret_ty_body: []const Zir.Inst.Index = &.{};
 
-        if (extra.data.bits.has_lib_name) {
-            const lib_name = self.code.nullTerminatedString(@enumFromInt(self.code.extra[extra_index]));
-            extra_index += 1;
-            try stream.print("lib_name=\"{}\", ", .{std.zig.fmtEscapes(lib_name)});
-        }
-        try self.writeFlag(stream, "test, ", extra.data.bits.is_test);
-
         if (extra.data.bits.has_cc_body) {
             const body_len = self.code.extra[extra_index];
             extra_index += 1;
@@ -2414,7 +2405,6 @@ const Writer = struct {
             stream,
             extra.data.bits.is_inferred_error,
             extra.data.bits.is_var_args,
-            extra.data.bits.is_extern,
             extra.data.bits.is_noinline,
             cc_ref,
             cc_body,
@@ -2427,36 +2417,6 @@ const Writer = struct {
         );
     }
 
-    fn writeVarExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
-        const extra = self.code.extraData(Zir.Inst.ExtendedVar, extended.operand);
-        const small = @as(Zir.Inst.ExtendedVar.Small, @bitCast(extended.small));
-
-        try self.writeInstRef(stream, extra.data.var_type);
-
-        var extra_index: usize = extra.end;
-        if (small.has_lib_name) {
-            const lib_name_index: Zir.NullTerminatedString = @enumFromInt(self.code.extra[extra_index]);
-            const lib_name = self.code.nullTerminatedString(lib_name_index);
-            extra_index += 1;
-            try stream.print(", lib_name=\"{}\"", .{std.zig.fmtEscapes(lib_name)});
-        }
-        const align_inst: Zir.Inst.Ref = if (!small.has_align) .none else blk: {
-            const align_inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
-            extra_index += 1;
-            break :blk align_inst;
-        };
-        const init_inst: Zir.Inst.Ref = if (!small.has_init) .none else blk: {
-            const init_inst = @as(Zir.Inst.Ref, @enumFromInt(self.code.extra[extra_index]));
-            extra_index += 1;
-            break :blk init_inst;
-        };
-        try self.writeFlag(stream, ", is_extern", small.is_extern);
-        try self.writeFlag(stream, ", is_threadlocal", small.is_threadlocal);
-        try self.writeOptionalInstRef(stream, ", align=", align_inst);
-        try self.writeOptionalInstRef(stream, ", init=", init_inst);
-        try stream.writeAll("))");
-    }
-
     fn writeAllocExtended(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
         const extra = self.code.extraData(Zir.Inst.AllocExtended, extended.operand);
         const small = @as(Zir.Inst.AllocExtended.Small, @bitCast(extended.small));
@@ -2604,7 +2564,6 @@ const Writer = struct {
         stream: anytype,
         inferred_error_set: bool,
         var_args: bool,
-        is_extern: bool,
         is_noinline: bool,
         cc_ref: Zir.Inst.Ref,
         cc_body: []const Zir.Inst.Index,
@@ -2618,7 +2577,6 @@ const Writer = struct {
         try self.writeOptionalInstRefOrBody(stream, "cc=", cc_ref, cc_body);
         try self.writeOptionalInstRefOrBody(stream, "ret_ty=", ret_ty_ref, ret_ty_body);
         try self.writeFlag(stream, "vargs, ", var_args);
-        try self.writeFlag(stream, "extern, ", is_extern);
         try self.writeFlag(stream, "inferror, ", inferred_error_set);
         try self.writeFlag(stream, "noinline, ", is_noinline);
 
@@ -2664,56 +2622,58 @@ const Writer = struct {
     }
 
     fn writeDeclaration(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
-        const inst_data = self.code.instructions.items(.data)[@intFromEnum(inst)].declaration;
-        const extra = self.code.extraData(Zir.Inst.Declaration, inst_data.payload_index);
+        const decl = self.code.getDeclaration(inst);
 
         const prev_parent_decl_node = self.parent_decl_node;
         defer self.parent_decl_node = prev_parent_decl_node;
-        self.parent_decl_node = inst_data.src_node;
+        self.parent_decl_node = decl.src_node;
 
-        if (extra.data.flags.is_pub) try stream.writeAll("pub ");
-        if (extra.data.flags.is_export) try stream.writeAll("export ");
-        switch (extra.data.name) {
+        if (decl.is_pub) try stream.writeAll("pub ");
+        switch (decl.linkage) {
+            .normal => {},
+            .@"export" => try stream.writeAll("export "),
+            .@"extern" => try stream.writeAll("extern "),
+        }
+        switch (decl.kind) {
             .@"comptime" => try stream.writeAll("comptime"),
             .@"usingnamespace" => try stream.writeAll("usingnamespace"),
             .unnamed_test => try stream.writeAll("test"),
-            _ => {
-                const name = extra.data.name.toString(self.code).?;
-                const prefix = if (extra.data.name.isNamedTest(self.code)) p: {
-                    break :p if (extra.data.flags.test_is_decltest) "decltest " else "test ";
-                } else "";
-                try stream.print("{s}'{s}'", .{ prefix, self.code.nullTerminatedString(name) });
+            .@"test", .decltest, .@"const", .@"var" => {
+                try stream.print("{s} '{s}'", .{ @tagName(decl.kind), self.code.nullTerminatedString(decl.name) });
             },
         }
-        const src_hash_arr: [4]u32 = .{
-            extra.data.src_hash_0,
-            extra.data.src_hash_1,
-            extra.data.src_hash_2,
-            extra.data.src_hash_3,
-        };
-        const src_hash_bytes: [16]u8 = @bitCast(src_hash_arr);
-        try stream.print(" line({d}) hash({})", .{ extra.data.src_line, std.fmt.fmtSliceHexLower(&src_hash_bytes) });
+        const src_hash = self.code.getAssociatedSrcHash(inst).?;
+        try stream.print(" line({d}) column({d}) hash({})", .{
+            decl.src_line,
+            decl.src_column,
+            std.fmt.fmtSliceHexLower(&src_hash),
+        });
 
         {
-            const bodies = extra.data.getBodies(@intCast(extra.end), self.code);
-
-            try stream.writeAll(" value=");
-            try self.writeBracedDecl(stream, bodies.value_body);
+            if (decl.type_body) |b| {
+                try stream.writeAll(" type=");
+                try self.writeBracedDecl(stream, b);
+            }
 
-            if (bodies.align_body) |b| {
+            if (decl.align_body) |b| {
                 try stream.writeAll(" align=");
                 try self.writeBracedDecl(stream, b);
             }
 
-            if (bodies.linksection_body) |b| {
+            if (decl.linksection_body) |b| {
                 try stream.writeAll(" linksection=");
                 try self.writeBracedDecl(stream, b);
             }
 
-            if (bodies.addrspace_body) |b| {
+            if (decl.addrspace_body) |b| {
                 try stream.writeAll(" addrspace=");
                 try self.writeBracedDecl(stream, b);
             }
+
+            if (decl.value_body) |b| {
+                try stream.writeAll(" value=");
+                try self.writeBracedDecl(stream, b);
+            }
         }
 
         try stream.writeAll(") ");
src/Sema.zig
@@ -1284,7 +1284,6 @@ fn analyzeBodyInner(
                 const extended = datas[@intFromEnum(inst)].extended;
                 break :ext switch (extended.opcode) {
                     // zig fmt: off
-                    .variable           => try sema.zirVarExtended(       block, extended),
                     .struct_decl        => try sema.zirStructDecl(        block, extended, inst),
                     .enum_decl          => try sema.zirEnumDecl(          block, extended, inst),
                     .union_decl         => try sema.zirUnionDecl(         block, extended, inst),
@@ -2114,13 +2113,33 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
 }
 
 /// Return the Value corresponding to a given AIR ref, or `null` if it refers to a runtime value.
-/// InternPool key `variable` is considered a runtime value.
 /// Generic poison causes `error.GenericPoison` to be returned.
 fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
-    const val = (try sema.resolveValueAllowVariables(inst)) orelse return null;
-    if (val.isGenericPoison()) return error.GenericPoison;
-    if (sema.pt.zcu.intern_pool.isVariable(val.toIntern())) return null;
-    return val;
+    const zcu = sema.pt.zcu;
+    assert(inst != .none);
+
+    if (try sema.typeHasOnePossibleValue(sema.typeOf(inst))) |opv| {
+        return opv;
+    }
+
+    if (inst.toInterned()) |ip_index| {
+        const val: Value = .fromInterned(ip_index);
+
+        assert(val.getVariable(zcu) == null);
+        if (val.isPtrRuntimeValue(zcu)) return null;
+        if (val.isGenericPoison()) return error.GenericPoison;
+
+        return val;
+    } else {
+        // Runtime-known value.
+        const air_tags = sema.air_instructions.items(.tag);
+        switch (air_tags[@intFromEnum(inst.toIndex().?)]) {
+            .inferred_alloc => unreachable, // assertion failure
+            .inferred_alloc_comptime => unreachable, // assertion failure
+            else => {},
+        }
+        return null;
+    }
 }
 
 /// Like `resolveValue`, but emits an error if the value is not comptime-known.
@@ -2183,35 +2202,6 @@ fn resolveValueIntable(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
     return try sema.resolveLazyValue(val);
 }
 
-/// Returns all InternPool keys representing values, including `variable`, `undef`, and `generic_poison`.
-fn resolveValueAllowVariables(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
-    const pt = sema.pt;
-    assert(inst != .none);
-    // First section of indexes correspond to a set number of constant values.
-    if (@intFromEnum(inst) < InternPool.static_len) {
-        return Value.fromInterned(@as(InternPool.Index, @enumFromInt(@intFromEnum(inst))));
-    }
-
-    const air_tags = sema.air_instructions.items(.tag);
-    if (try sema.typeHasOnePossibleValue(sema.typeOf(inst))) |opv| {
-        if (inst.toInterned()) |ip_index| {
-            const val = Value.fromInterned(ip_index);
-            if (val.getVariable(pt.zcu) != null) return val;
-        }
-        return opv;
-    }
-    const ip_index = inst.toInterned() orelse {
-        switch (air_tags[@intFromEnum(inst.toIndex().?)]) {
-            .inferred_alloc => unreachable,
-            .inferred_alloc_comptime => unreachable,
-            else => return null,
-        }
-    };
-    const val = Value.fromInterned(ip_index);
-    if (val.isPtrRuntimeValue(pt.zcu)) return null;
-    return val;
-}
-
 /// Value Tag may be `undef` or `variable`.
 pub fn resolveFinalDeclValue(
     sema: *Sema,
@@ -2221,8 +2211,13 @@ pub fn resolveFinalDeclValue(
 ) CompileError!Value {
     const zcu = sema.pt.zcu;
 
-    const val = try sema.resolveValueAllowVariables(air_ref) orelse {
-        const value_comptime_reason: ?[]const u8 = if (air_ref.toInterned()) |_|
+    const val = try sema.resolveValue(air_ref) orelse {
+        const is_runtime_ptr = rt_ptr: {
+            const ip_index = air_ref.toInterned() orelse break :rt_ptr false;
+            const val: Value = .fromInterned(ip_index);
+            break :rt_ptr val.isPtrRuntimeValue(zcu);
+        };
+        const value_comptime_reason: ?[]const u8 = if (is_runtime_ptr)
             "thread local and dll imported variables have runtime-known addresses"
         else
             null;
@@ -2232,10 +2227,8 @@ pub fn resolveFinalDeclValue(
             .value_comptime_reason = value_comptime_reason,
         });
     };
-    if (val.isGenericPoison()) return error.GenericPoison;
 
-    const init_val: Value = if (val.getVariable(zcu)) |v| .fromInterned(v.init) else val;
-    if (init_val.canMutateComptimeVarState(zcu)) {
+    if (val.canMutateComptimeVarState(zcu)) {
         return sema.fail(block, src, "global variable contains reference to comptime var", .{});
     }
 
@@ -9525,8 +9518,8 @@ fn zirFunc(
         } else sema.owner.unwrap().cau;
         const fn_is_exported = exported: {
             const decl_inst = ip.getCau(func_decl_cau).zir_index.resolve(ip) orelse return error.AnalysisFail;
-            const zir_decl = sema.code.getDeclaration(decl_inst)[0];
-            break :exported zir_decl.flags.is_export;
+            const zir_decl = sema.code.getDeclaration(decl_inst);
+            break :exported zir_decl.linkage == .@"export";
         };
         if (fn_is_exported) {
             break :cc target.cCallingConvention() orelse {
@@ -9557,10 +9550,8 @@ fn zirFunc(
         ret_ty,
         false,
         inferred_error_set,
-        false,
         has_body,
         src_locs,
-        null,
         0,
         false,
     );
@@ -9619,7 +9610,7 @@ fn resolveGenericBody(
 /// respective `Decl` (either `ExternFn` or `Var`).
 /// The liveness of the duped library name is tied to liveness of `Zcu`.
 /// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`).
-fn handleExternLibName(
+pub fn handleExternLibName(
     sema: *Sema,
     block: *Block,
     src_loc: LazySrcLoc,
@@ -9843,10 +9834,8 @@ fn funcCommon(
     bare_return_type: Type,
     var_args: bool,
     inferred_error_set: bool,
-    is_extern: bool,
     has_body: bool,
     src_locs: Zir.Inst.Func.SrcLocs,
-    opt_lib_name: ?[]const u8,
     noalias_bits: u32,
     is_noinline: bool,
 ) CompileError!Air.Inst.Ref {
@@ -9998,7 +9987,6 @@ fn funcCommon(
     }
 
     if (inferred_error_set) {
-        assert(!is_extern);
         assert(has_body);
         if (!ret_poison)
             try sema.validateErrorUnionPayloadType(block, bare_return_type, ret_ty_src);
@@ -10050,32 +10038,6 @@ fn funcCommon(
         .is_noinline = is_noinline,
     });
 
-    if (is_extern) {
-        assert(comptime_bits == 0);
-        assert(!is_generic);
-        if (opt_lib_name) |lib_name| try sema.handleExternLibName(block, block.src(.{
-            .node_offset_lib_name = src_node_offset,
-        }), lib_name);
-        const extern_func_index = try sema.resolveExternDecl(block, .fromInterned(func_ty), opt_lib_name, true, false);
-        return finishFunc(
-            sema,
-            block,
-            extern_func_index,
-            func_ty,
-            ret_poison,
-            bare_return_type,
-            ret_ty_src,
-            cc,
-            is_source_decl,
-            ret_ty_requires_comptime,
-            func_inst,
-            cc_src,
-            is_noinline,
-            is_generic,
-            final_is_generic,
-        );
-    }
-
     if (has_body) {
         const func_index = try ip.getFuncDecl(gpa, pt.tid, .{
             .owner_nav = sema.getOwnerCauNav(),
@@ -26711,135 +26673,6 @@ fn zirAwaitNosuspend(
     return sema.failWithUseOfAsync(block, src);
 }
 
-fn zirVarExtended(
-    sema: *Sema,
-    block: *Block,
-    extended: Zir.Inst.Extended.InstData,
-) CompileError!Air.Inst.Ref {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-    const extra = sema.code.extraData(Zir.Inst.ExtendedVar, extended.operand);
-    const ty_src = block.src(.{ .node_offset_var_decl_ty = 0 });
-    const init_src = block.src(.{ .node_offset_var_decl_init = 0 });
-    const small: Zir.Inst.ExtendedVar.Small = @bitCast(extended.small);
-
-    var extra_index: usize = extra.end;
-
-    const lib_name = if (small.has_lib_name) lib_name: {
-        const lib_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
-        const lib_name = sema.code.nullTerminatedString(lib_name_index);
-        extra_index += 1;
-        try sema.handleExternLibName(block, ty_src, lib_name);
-        break :lib_name lib_name;
-    } else null;
-
-    // ZIR supports encoding this information but it is not used; the information
-    // is encoded via the Decl entry.
-    assert(!small.has_align);
-
-    const uncasted_init: Air.Inst.Ref = if (small.has_init) blk: {
-        const init_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
-        extra_index += 1;
-        break :blk try sema.resolveInst(init_ref);
-    } else .none;
-
-    const have_ty = extra.data.var_type != .none;
-    const var_ty = if (have_ty)
-        try sema.resolveType(block, ty_src, extra.data.var_type)
-    else
-        sema.typeOf(uncasted_init);
-
-    const init_val = if (uncasted_init != .none) blk: {
-        const init = if (have_ty)
-            try sema.coerce(block, var_ty, uncasted_init, init_src)
-        else
-            uncasted_init;
-
-        break :blk ((try sema.resolveValue(init)) orelse {
-            return sema.failWithNeededComptime(block, init_src, .{
-                .needed_comptime_reason = "container level variable initializers must be comptime-known",
-            });
-        }).toIntern();
-    } else .none;
-
-    try sema.validateVarType(block, ty_src, var_ty, small.is_extern);
-
-    if (small.is_extern) {
-        const extern_val = try sema.resolveExternDecl(block, var_ty, lib_name, small.is_const, small.is_threadlocal);
-        return Air.internedToRef(extern_val);
-    }
-    assert(!small.is_const); // non-const non-extern variable is not legal
-    return Air.internedToRef(try pt.intern(.{ .variable = .{
-        .ty = var_ty.toIntern(),
-        .init = init_val,
-        .owner_nav = sema.getOwnerCauNav(),
-        .lib_name = try ip.getOrPutStringOpt(sema.gpa, pt.tid, lib_name, .no_embedded_nulls),
-        .is_threadlocal = small.is_threadlocal,
-        .is_weak_linkage = false,
-    } }));
-}
-
-fn resolveExternDecl(
-    sema: *Sema,
-    block: *Block,
-    ty: Type,
-    opt_lib_name: ?[]const u8,
-    is_const: bool,
-    is_threadlocal: bool,
-) CompileError!InternPool.Index {
-    const pt = sema.pt;
-    const zcu = pt.zcu;
-    const ip = &zcu.intern_pool;
-
-    // We need to resolve the alignment and addrspace early.
-    // Keep in sync with logic in `Zcu.PerThread.semaCau`.
-    const align_src = block.src(.{ .node_offset_var_decl_align = 0 });
-    const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = 0 });
-
-    const decl_inst, const decl_bodies = decl: {
-        const decl_inst = sema.getOwnerCauDeclInst().resolve(ip) orelse return error.AnalysisFail;
-        const zir_decl, const extra_end = sema.code.getDeclaration(decl_inst);
-        break :decl .{ decl_inst, zir_decl.getBodies(extra_end, sema.code) };
-    };
-
-    const alignment: InternPool.Alignment = a: {
-        const align_body = decl_bodies.align_body orelse break :a .none;
-        const align_ref = try sema.resolveInlineBody(block, align_body, decl_inst);
-        break :a try sema.analyzeAsAlign(block, align_src, align_ref);
-    };
-
-    const @"addrspace": std.builtin.AddressSpace = as: {
-        const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(ty.toIntern())) {
-            .func_type => .function,
-            else => .variable,
-        };
-        const target = zcu.getTarget();
-        const addrspace_body = decl_bodies.addrspace_body orelse break :as switch (addrspace_ctx) {
-            .function => target_util.defaultAddressSpace(target, .function),
-            .variable => target_util.defaultAddressSpace(target, .global_mutable),
-            .constant => target_util.defaultAddressSpace(target, .global_constant),
-            else => unreachable,
-        };
-        const addrspace_ref = try sema.resolveInlineBody(block, addrspace_body, decl_inst);
-        break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx);
-    };
-
-    return pt.getExtern(.{
-        .name = sema.getOwnerCauNavName(),
-        .ty = ty.toIntern(),
-        .lib_name = try ip.getOrPutStringOpt(sema.gpa, pt.tid, opt_lib_name, .no_embedded_nulls),
-        .is_const = is_const,
-        .is_threadlocal = is_threadlocal,
-        .is_weak_linkage = false,
-        .is_dll_import = false,
-        .alignment = alignment,
-        .@"addrspace" = @"addrspace",
-        .zir_index = sema.getOwnerCauDeclInst(), // `declaration` instruction
-        .owner_nav = undefined, // ignored by `getExtern`
-    });
-}
-
 fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const tracy = trace(@src());
     defer tracy.end();
@@ -26857,13 +26690,6 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 
     var extra_index: usize = extra.end;
 
-    const lib_name: ?[]const u8 = if (extra.data.bits.has_lib_name) blk: {
-        const lib_name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
-        const lib_name = sema.code.nullTerminatedString(lib_name_index);
-        extra_index += 1;
-        break :blk lib_name;
-    } else null;
-
     const cc: std.builtin.CallingConvention = if (extra.data.bits.has_cc_body) blk: {
         const body_len = sema.code.extra[extra_index];
         extra_index += 1;
@@ -26895,8 +26721,8 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
                 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)[0];
-            if (zir_decl.flags.is_export) {
+            const zir_decl = sema.code.getDeclaration(decl_inst.resolve(&zcu.intern_pool) orelse return error.AnalysisFail);
+            if (zir_decl.linkage == .@"export") {
                 break :cc target.cCallingConvention() orelse {
                     // This target has no default C calling convention. We sometimes trigger a similar
                     // error by trying to evaluate `std.builtin.CallingConvention.c`, so for consistency,
@@ -26958,7 +26784,6 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 
     const is_var_args = extra.data.bits.is_var_args;
     const is_inferred_error = extra.data.bits.is_inferred_error;
-    const is_extern = extra.data.bits.is_extern;
     const is_noinline = extra.data.bits.is_noinline;
 
     return sema.funcCommon(
@@ -26969,10 +26794,8 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         ret_ty,
         is_var_args,
         is_inferred_error,
-        is_extern,
         has_body,
         src_locs,
-        lib_name,
         noalias_bits,
         is_noinline,
     );
@@ -27467,7 +27290,7 @@ fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src:
 }
 
 /// Emit a compile error if type cannot be used for a runtime variable.
-fn validateVarType(
+pub fn validateVarType(
     sema: *Sema,
     block: *Block,
     src: LazySrcLoc,
@@ -29881,7 +29704,7 @@ fn elemPtrSlice(
     return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty);
 }
 
-fn coerce(
+pub fn coerce(
     sema: *Sema,
     block: *Block,
     dest_ty_unresolved: Type,
@@ -38843,13 +38666,6 @@ fn getOwnerCauNav(sema: *Sema) InternPool.Nav.Index {
     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 declaration name from its corresponding `Nav`.
-fn getOwnerCauNavName(sema: *Sema) InternPool.NullTerminatedString {
-    const nav = sema.getOwnerCauNav();
-    return sema.pt.zcu.intern_pool.getNav(nav).name;
-}
-
 /// 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 {
src/Zcu.zig
@@ -2679,24 +2679,14 @@ pub fn mapOldZirToNew(
         {
             var old_decl_it = old_zir.declIterator(match_item.old_inst);
             while (old_decl_it.next()) |old_decl_inst| {
-                const old_decl, _ = old_zir.getDeclaration(old_decl_inst);
-                switch (old_decl.name) {
+                const old_decl = old_zir.getDeclaration(old_decl_inst);
+                switch (old_decl.kind) {
                     .@"comptime" => try comptime_decls.append(gpa, old_decl_inst),
                     .@"usingnamespace" => try usingnamespace_decls.append(gpa, old_decl_inst),
                     .unnamed_test => try unnamed_tests.append(gpa, old_decl_inst),
-                    _ => {
-                        const name_nts = old_decl.name.toString(old_zir).?;
-                        const name = old_zir.nullTerminatedString(name_nts);
-                        if (old_decl.name.isNamedTest(old_zir)) {
-                            if (old_decl.flags.test_is_decltest) {
-                                try named_decltests.put(gpa, name, old_decl_inst);
-                            } else {
-                                try named_tests.put(gpa, name, old_decl_inst);
-                            }
-                        } else {
-                            try named_decls.put(gpa, name, old_decl_inst);
-                        }
-                    },
+                    .@"test" => try named_tests.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst),
+                    .decltest => try named_decltests.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst),
+                    .@"const", .@"var" => try named_decls.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst),
                 }
             }
         }
@@ -2707,7 +2697,7 @@ pub fn mapOldZirToNew(
 
         var new_decl_it = new_zir.declIterator(match_item.new_inst);
         while (new_decl_it.next()) |new_decl_inst| {
-            const new_decl, _ = new_zir.getDeclaration(new_decl_inst);
+            const new_decl = new_zir.getDeclaration(new_decl_inst);
             // Attempt to match this to a declaration in the old ZIR:
             // * For named declarations (`const`/`var`/`fn`), we match based on name.
             // * For named tests (`test "foo"`) and decltests (`test foo`), we also match based on name.
@@ -2715,7 +2705,7 @@ pub fn mapOldZirToNew(
             // * For comptime blocks, we match based on order.
             // * For usingnamespace decls, we match based on order.
             // If we cannot match this declaration, we can't match anything nested inside of it either, so we just `continue`.
-            const old_decl_inst = switch (new_decl.name) {
+            const old_decl_inst = switch (new_decl.kind) {
                 .@"comptime" => inst: {
                     if (comptime_decl_idx == comptime_decls.items.len) continue;
                     defer comptime_decl_idx += 1;
@@ -2731,18 +2721,17 @@ pub fn mapOldZirToNew(
                     defer unnamed_test_idx += 1;
                     break :inst unnamed_tests.items[unnamed_test_idx];
                 },
-                _ => inst: {
-                    const name_nts = new_decl.name.toString(new_zir).?;
-                    const name = new_zir.nullTerminatedString(name_nts);
-                    if (new_decl.name.isNamedTest(new_zir)) {
-                        if (new_decl.flags.test_is_decltest) {
-                            break :inst named_decltests.get(name) orelse continue;
-                        } else {
-                            break :inst named_tests.get(name) orelse continue;
-                        }
-                    } else {
-                        break :inst named_decls.get(name) orelse continue;
-                    }
+                .@"test" => inst: {
+                    const name = new_zir.nullTerminatedString(new_decl.name);
+                    break :inst named_tests.get(name) orelse continue;
+                },
+                .decltest => inst: {
+                    const name = new_zir.nullTerminatedString(new_decl.name);
+                    break :inst named_decltests.get(name) orelse continue;
+                },
+                .@"const", .@"var" => inst: {
+                    const name = new_zir.nullTerminatedString(new_decl.name);
+                    break :inst named_decls.get(name) orelse continue;
                 },
             };
 
@@ -3353,20 +3342,20 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
                 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 declaration = zir.getDeclaration(inst_info.inst)[0];
-                const want_analysis = switch (declaration.name) {
+                const decl = zir.getDeclaration(inst_info.inst);
+                const want_analysis = switch (decl.kind) {
                     .@"usingnamespace" => unreachable,
+                    .@"const", .@"var" => unreachable,
                     .@"comptime" => true,
-                    else => a: {
+                    .unnamed_test => comp.config.is_test and file.mod == zcu.main_mod,
+                    .@"test", .decltest => a: {
                         if (!comp.config.is_test) break :a false;
                         if (file.mod != zcu.main_mod) break :a false;
-                        if (declaration.name.isNamedTest(zir)) {
-                            const nav = ip.getCau(cau).owner.unwrap().nav;
-                            const fqn_slice = ip.getNav(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;
-                        }
+                        const nav = ip.getCau(cau).owner.unwrap().nav;
+                        const fqn_slice = ip.getNav(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;
                         break :a true;
                     },
                 };
@@ -3388,8 +3377,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
                 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 declaration = zir.getDeclaration(inst_info.inst)[0];
-                if (declaration.flags.is_export) {
+                const decl = zir.getDeclaration(inst_info.inst);
+                if (decl.linkage == .@"export") {
                     const unit = AnalUnit.wrap(.{ .cau = cau });
                     if (!result.contains(unit)) {
                         log.debug("type '{}': ref cau %{}", .{
@@ -3407,8 +3396,8 @@ fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?Resolv
                 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 declaration = zir.getDeclaration(inst_info.inst)[0];
-                if (declaration.flags.is_export) {
+                const decl = zir.getDeclaration(inst_info.inst);
+                if (decl.linkage == .@"export") {
                     const unit = AnalUnit.wrap(.{ .cau = cau });
                     if (!result.contains(unit)) {
                         log.debug("type '{}': ref cau %{}", .{
@@ -3522,9 +3511,7 @@ pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 {
     const ip = &zcu.intern_pool;
     const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip).?;
     const zir = zcu.fileByIndex(inst_info.file).zir;
-    const inst = zir.instructions.get(@intFromEnum(inst_info.inst));
-    assert(inst.tag == .declaration);
-    return zir.extraData(Zir.Inst.Declaration, inst.data.declaration.payload_index).data.src_line;
+    return zir.getDeclaration(inst_info.inst).src_line;
 }
 
 pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value {
test/cases/compile_errors/address_of_threadlocal_not_comptime_known.zig
@@ -10,4 +10,5 @@ pub export fn entry() void {
 // target=native
 //
 // :2:36: error: unable to resolve comptime value
-// :2:36: note: container level variable initializers must be comptime-known
+// :2:36: note: global variable initializer must be comptime-known
+// :2:36: note: thread local and dll imported variables have runtime-known addresses
test/cases/compile_errors/type_variables_must_be_constant.zig
@@ -7,5 +7,5 @@ export fn entry() foo {
 // backend=stage2
 // target=native
 //
-// :1:5: error: variable of type 'type' must be const or comptime
-// :1:5: note: types are not available at runtime
+// :1:11: error: variable of type 'type' must be const or comptime
+// :1:11: note: types are not available at runtime
test/cases/compile_errors/use_invalid_number_literal_as_array_index.zig
@@ -8,5 +8,5 @@ export fn entry() void {
 // backend=stage2
 // target=native
 //
-// :1:5: error: variable of type 'comptime_int' must be const or comptime
-// :1:5: note: to modify this variable at runtime, it must be given an explicit fixed-size number type
+// :1:9: error: variable of type 'comptime_int' must be const or comptime
+// :1:9: note: to modify this variable at runtime, it must be given an explicit fixed-size number type