Commit b9a099e83c

Andrew Kelley <andrew@ziglang.org>
2021-05-11 06:34:43
stage2: type declarations ZIR encode AnonNameStrategy
which can be either parent, func, or anon. Here's the enum reproduced in the commit message for convenience: ```zig pub const NameStrategy = enum(u2) { /// Use the same name as the parent declaration name. /// e.g. `const Foo = struct {...};`. parent, /// Use the name of the currently executing comptime function call, /// with the current parameters. e.g. `ArrayList(i32)`. func, /// Create an anonymous name for this declaration. /// Like this: "ParentDeclName_struct_69" anon, }; ``` With this information in the ZIR, a future commit can improve the names of structs, unions, enums, and opaques. In order to accomplish this, the following ZIR instruction forms were removed and replaced with Extended op codes: * struct_decl * struct_decl_packed * struct_decl_extern * union_decl * union_decl_packed * union_decl_extern * enum_decl * enum_decl_nonexhaustive By being extended opcodes, one more u32 is needed, however we more than make up for it by repurposing the 16 "small" bits to provide shorter encodings for when decls_len == 0, fields_len == 0, a source node is not provided, etc. There tends to be no downside, and in fact sometimes upsides, to using an extended op code when there is a need for flag bits, which is the case for all three of these. Likewise, the container layout can be encoded in these bits rather than into the opcode. The following 4 ZIR instructions were added, netting a total of 4 freed up ZIR enum tags for future use: * opaque_decl_anon * opaque_decl_func * error_set_decl_anon * error_set_decl_func This is so that opaques and error sets can have the same name hint as structs, enums, and unions. `std.builtin.ContainerLayout` gets an explicit integer tag type so that it can be used inside packed structs. This commit also makes `Module.Namespace` use a separate set for anonymous decls, thus allowing anonymous decls to share the same `Decl.name` as their owner `Decl` objects.
1 parent 9e72f31
lib/std/builtin.zig
@@ -263,7 +263,7 @@ pub const TypeInfo = union(enum) {
 
     /// This data structure is used by the Zig language code generation and
     /// therefore must be kept in sync with the compiler implementation.
-    pub const ContainerLayout = enum {
+    pub const ContainerLayout = enum(u2) {
         Auto,
         Extern,
         Packed,
src/AstGen.zig
@@ -85,6 +85,7 @@ pub fn generate(gpa: *Allocator, tree: ast.Tree) InnerError!Zir {
     var gen_scope: GenZir = .{
         .force_comptime = true,
         .parent = null,
+        .anon_name_strategy = .parent,
         .decl_node_index = 0,
         .decl_line = 0,
         .astgen = &astgen,
@@ -105,7 +106,7 @@ pub fn generate(gpa: *Allocator, tree: ast.Tree) InnerError!Zir {
         &gen_scope.base,
         0,
         container_decl,
-        .struct_decl,
+        .Auto,
     )) |struct_decl_ref| {
         astgen.extra.items[@enumToInt(Zir.ExtraIndex.main_struct)] = @enumToInt(struct_decl_ref);
     } else |err| switch (err) {
@@ -1959,16 +1960,12 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
             .union_init_ptr,
             .field_type,
             .field_type_ref,
-            .struct_decl,
-            .struct_decl_packed,
-            .struct_decl_extern,
-            .union_decl,
-            .union_decl_packed,
-            .union_decl_extern,
-            .enum_decl,
-            .enum_decl_nonexhaustive,
             .opaque_decl,
+            .opaque_decl_anon,
+            .opaque_decl_func,
             .error_set_decl,
+            .error_set_decl_anon,
+            .error_set_decl_func,
             .int_to_enum,
             .enum_to_int,
             .type_info,
@@ -2166,7 +2163,7 @@ fn varDecl(
                 const local_val = s.cast(Scope.LocalVal).?;
                 if (local_val.name == ident_name) {
                     return astgen.failTokNotes(name_token, "redeclaration of '{s}'", .{
-                        @ptrCast([*:0]const u8, astgen.string_bytes.items.ptr) + ident_name,
+                        astgen.nullTerminatedString(ident_name),
                     }, &[_]u32{
                         try astgen.errNoteTok(
                             local_val.token_src,
@@ -2181,7 +2178,7 @@ fn varDecl(
                 const local_ptr = s.cast(Scope.LocalPtr).?;
                 if (local_ptr.name == ident_name) {
                     return astgen.failTokNotes(name_token, "redeclaration of '{s}'", .{
-                        @ptrCast([*:0]const u8, astgen.string_bytes.items.ptr) + ident_name,
+                        astgen.nullTerminatedString(ident_name),
                     }, &[_]u32{
                         try astgen.errNoteTok(
                             local_ptr.token_src,
@@ -2941,12 +2938,16 @@ fn globalVarDecl(
     // of the top level declaration.
     const block_inst = try gz.addBlock(.block_inline, node);
 
+    const name_token = var_decl.ast.mut_token + 1;
+    const name_str_index = try astgen.identAsString(name_token);
+
     var block_scope: GenZir = .{
         .parent = scope,
         .decl_node_index = node,
         .decl_line = gz.calcLine(node),
         .astgen = astgen,
         .force_comptime = true,
+        .anon_name_strategy = .parent,
     };
     defer block_scope.instructions.deinit(gpa);
 
@@ -3043,9 +3044,6 @@ fn globalVarDecl(
     _ = try block_scope.addBreak(.break_inline, block_inst, var_inst);
     try block_scope.setBlockBody(block_inst);
 
-    const name_token = var_decl.ast.mut_token + 1;
-    const name_str_index = try astgen.identAsString(name_token);
-
     try wip_decls.payload.ensureUnusedCapacity(gpa, 9);
     {
         const contents_hash = std.zig.hashSrc(tree.getNodeSource(node));
@@ -3258,14 +3256,18 @@ fn structDeclInner(
     scope: *Scope,
     node: ast.Node.Index,
     container_decl: ast.full.ContainerDecl,
-    tag: Zir.Inst.Tag,
+    layout: std.builtin.TypeInfo.ContainerLayout,
 ) InnerError!Zir.Inst.Ref {
     if (container_decl.ast.members.len == 0) {
-        return gz.addPlNode(tag, node, Zir.Inst.StructDecl{
+        const decl_inst = try gz.reserveInstructionIndex();
+        try gz.setStruct(decl_inst, .{
+            .src_node = node,
+            .layout = layout,
             .fields_len = 0,
             .body_len = 0,
             .decls_len = 0,
         });
+        return gz.indexToRef(decl_inst);
     }
 
     const astgen = gz.astgen;
@@ -3437,23 +3439,24 @@ fn structDeclInner(
         }
     }
 
-    const decl_inst = try gz.addBlock(tag, node);
-    try gz.instructions.append(gpa, decl_inst);
+    const decl_inst = try gz.reserveInstructionIndex();
     if (block_scope.instructions.items.len != 0) {
         _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
     }
 
-    try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.StructDecl).Struct.fields.len +
-        bit_bag.items.len + @boolToInt(field_index != 0) + fields_data.items.len +
-        block_scope.instructions.items.len +
-        wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
-        wip_decls.payload.items.len);
-    const zir_datas = astgen.instructions.items(.data);
-    zir_datas[decl_inst].pl_node.payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.StructDecl{
+    try gz.setStruct(decl_inst, .{
+        .src_node = node,
+        .layout = layout,
         .body_len = @intCast(u32, block_scope.instructions.items.len),
         .fields_len = @intCast(u32, field_index),
         .decls_len = @intCast(u32, wip_decls.decl_index),
     });
+
+    try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
+        @boolToInt(field_index != 0) + fields_data.items.len +
+        block_scope.instructions.items.len +
+        wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
+        wip_decls.payload.items.len);
     astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
     if (wip_decls.decl_index != 0) {
         astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -3476,7 +3479,7 @@ fn unionDeclInner(
     scope: *Scope,
     node: ast.Node.Index,
     members: []const ast.Node.Index,
-    tag: Zir.Inst.Tag,
+    layout: std.builtin.TypeInfo.ContainerLayout,
     arg_inst: Zir.Inst.Ref,
     have_auto_enum: bool,
 ) InnerError!Zir.Inst.Ref {
@@ -3611,11 +3614,12 @@ fn unionDeclInner(
         const have_type = member.ast.type_expr != 0;
         const have_align = member.ast.align_expr != 0;
         const have_value = member.ast.value_expr != 0;
+        const unused = false;
         cur_bit_bag = (cur_bit_bag >> bits_per_field) |
             (@as(u32, @boolToInt(have_type)) << 28) |
             (@as(u32, @boolToInt(have_align)) << 29) |
             (@as(u32, @boolToInt(have_value)) << 30) |
-            (@as(u32, @boolToInt(have_auto_enum)) << 31);
+            (@as(u32, @boolToInt(unused)) << 31);
 
         if (have_type) {
             const field_type = try typeExpr(&block_scope, &block_scope.base, member.ast.type_expr);
@@ -3662,24 +3666,26 @@ fn unionDeclInner(
         }
     }
 
-    const decl_inst = try gz.addBlock(tag, node);
-    try gz.instructions.append(gpa, decl_inst);
+    const decl_inst = try gz.reserveInstructionIndex();
     if (block_scope.instructions.items.len != 0) {
         _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
     }
 
-    try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.UnionDecl).Struct.fields.len +
-        bit_bag.items.len + 1 + fields_data.items.len +
-        block_scope.instructions.items.len +
-        wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
-        wip_decls.payload.items.len);
-    const zir_datas = astgen.instructions.items(.data);
-    zir_datas[decl_inst].pl_node.payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.UnionDecl{
+    try gz.setUnion(decl_inst, .{
+        .src_node = node,
+        .layout = layout,
         .tag_type = arg_inst,
         .body_len = @intCast(u32, block_scope.instructions.items.len),
         .fields_len = @intCast(u32, field_index),
         .decls_len = @intCast(u32, wip_decls.decl_index),
+        .auto_enum_tag = have_auto_enum,
     });
+
+    try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
+        1 + fields_data.items.len +
+        block_scope.instructions.items.len +
+        wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
+        wip_decls.payload.items.len);
     astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
     if (wip_decls.decl_index != 0) {
         astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -3719,29 +3725,27 @@ fn containerDecl(
 
     switch (token_tags[container_decl.ast.main_token]) {
         .keyword_struct => {
-            const tag = if (container_decl.layout_token) |t| switch (token_tags[t]) {
-                .keyword_packed => Zir.Inst.Tag.struct_decl_packed,
-                .keyword_extern => Zir.Inst.Tag.struct_decl_extern,
+            const layout = if (container_decl.layout_token) |t| switch (token_tags[t]) {
+                .keyword_packed => std.builtin.TypeInfo.ContainerLayout.Packed,
+                .keyword_extern => std.builtin.TypeInfo.ContainerLayout.Extern,
                 else => unreachable,
-            } else Zir.Inst.Tag.struct_decl;
+            } else std.builtin.TypeInfo.ContainerLayout.Auto;
 
             assert(arg_inst == .none);
 
-            const result = try structDeclInner(gz, scope, node, container_decl, tag);
+            const result = try structDeclInner(gz, scope, node, container_decl, layout);
             return rvalue(gz, scope, rl, result, node);
         },
         .keyword_union => {
-            const tag = if (container_decl.layout_token) |t| switch (token_tags[t]) {
-                .keyword_packed => Zir.Inst.Tag.union_decl_packed,
-                .keyword_extern => Zir.Inst.Tag.union_decl_extern,
+            const layout = if (container_decl.layout_token) |t| switch (token_tags[t]) {
+                .keyword_packed => std.builtin.TypeInfo.ContainerLayout.Packed,
+                .keyword_extern => std.builtin.TypeInfo.ContainerLayout.Extern,
                 else => unreachable,
-            } else Zir.Inst.Tag.union_decl;
+            } else std.builtin.TypeInfo.ContainerLayout.Auto;
 
-            // See `Zir.Inst.UnionDecl` doc comments for why this is stored along
-            // with fields instead of separately.
             const have_auto_enum = container_decl.ast.enum_token != null;
 
-            const result = try unionDeclInner(gz, scope, node, container_decl.ast.members, tag, arg_inst, have_auto_enum);
+            const result = try unionDeclInner(gz, scope, node, container_decl.ast.members, layout, arg_inst, have_auto_enum);
             return rvalue(gz, scope, rl, result, node);
         },
         .keyword_enum => {
@@ -3832,10 +3836,7 @@ fn containerDecl(
             }
             // In this case we must generate ZIR code for the tag values, similar to
             // how structs are handled above.
-            const tag: Zir.Inst.Tag = if (counts.nonexhaustive_node == 0)
-                .enum_decl
-            else
-                .enum_decl_nonexhaustive;
+            const nonexhaustive = counts.nonexhaustive_node != 0;
 
             // The enum_decl instruction introduces a scope in which the decls of the enum
             // are in scope, so that tag values can refer to decls within the enum itself.
@@ -3995,24 +3996,25 @@ fn containerDecl(
                 }
             }
 
-            const decl_inst = try gz.addBlock(tag, node);
-            try gz.instructions.append(gpa, decl_inst);
+            const decl_inst = try gz.reserveInstructionIndex();
             if (block_scope.instructions.items.len != 0) {
                 _ = try block_scope.addBreak(.break_inline, decl_inst, .void_value);
             }
 
-            try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.EnumDecl).Struct.fields.len +
-                bit_bag.items.len + 1 + fields_data.items.len +
-                block_scope.instructions.items.len +
-                wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
-                wip_decls.payload.items.len);
-            const zir_datas = astgen.instructions.items(.data);
-            zir_datas[decl_inst].pl_node.payload_index = astgen.addExtraAssumeCapacity(Zir.Inst.EnumDecl{
+            try gz.setEnum(decl_inst, .{
+                .src_node = node,
+                .nonexhaustive = nonexhaustive,
                 .tag_type = arg_inst,
                 .body_len = @intCast(u32, block_scope.instructions.items.len),
                 .fields_len = @intCast(u32, field_index),
                 .decls_len = @intCast(u32, wip_decls.decl_index),
             });
+
+            try astgen.extra.ensureUnusedCapacity(gpa, bit_bag.items.len +
+                1 + fields_data.items.len +
+                block_scope.instructions.items.len +
+                wip_decls.bit_bag.items.len + @boolToInt(wip_decls.decl_index != 0) +
+                wip_decls.payload.items.len);
             astgen.extra.appendSliceAssumeCapacity(wip_decls.bit_bag.items); // Likely empty.
             if (wip_decls.decl_index != 0) {
                 astgen.extra.appendAssumeCapacity(wip_decls.cur_bit_bag);
@@ -4118,7 +4120,12 @@ fn containerDecl(
                     wip_decls.cur_bit_bag >>= @intCast(u5, empty_slot_count * WipDecls.bits_per_field);
                 }
             }
-            const decl_inst = try gz.addBlock(.opaque_decl, node);
+            const tag: Zir.Inst.Tag = switch (gz.anon_name_strategy) {
+                .parent => .opaque_decl,
+                .anon => .opaque_decl_anon,
+                .func => .opaque_decl_func,
+            };
+            const decl_inst = try gz.addBlock(tag, node);
             try gz.instructions.append(gpa, decl_inst);
 
             try astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.OpaqueDecl).Struct.fields.len +
@@ -4173,6 +4180,11 @@ fn errorSetDecl(
         }
     }
 
+    const tag: Zir.Inst.Tag = switch (gz.anon_name_strategy) {
+        .parent => .error_set_decl,
+        .anon => .error_set_decl_anon,
+        .func => .error_set_decl_func,
+    };
     const result = try gz.addPlNode(.error_set_decl, node, Zir.Inst.ErrorSetDecl{
         .fields_len = @intCast(u32, field_names.items.len),
     });
@@ -5907,7 +5919,6 @@ fn charLiteral(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: ast.Node.Index)
     const value = std.zig.parseCharLiteral(slice, &bad_index) catch |err| switch (err) {
         error.InvalidCharacter => {
             const bad_byte = slice[bad_index];
-            const token_starts = tree.tokens.items(.start);
             return astgen.failOff(
                 main_token,
                 @intCast(u32, bad_index),
@@ -7779,6 +7790,8 @@ const GenZir = struct {
     const base_tag: Scope.Tag = .gen_zir;
     base: Scope = Scope{ .tag = base_tag },
     force_comptime: bool,
+    /// How decls created in this scope should be named.
+    anon_name_strategy: Zir.Inst.NameStrategy = .anon,
     /// The end of special indexes. `Zir.Inst.Ref` subtracts against this number to convert
     /// to `Zir.Inst.Index`. The default here is correct if there are 0 parameters.
     ref_start_index: u32 = Zir.Inst.Ref.typed_value_map.len,
@@ -8623,18 +8636,176 @@ const GenZir = struct {
         return new_index;
     }
 
+    fn setStruct(gz: *GenZir, inst: Zir.Inst.Index, args: struct {
+        src_node: ast.Node.Index,
+        body_len: u32,
+        fields_len: u32,
+        decls_len: u32,
+        layout: std.builtin.TypeInfo.ContainerLayout,
+    }) !void {
+        const astgen = gz.astgen;
+        const gpa = astgen.gpa;
+
+        try astgen.extra.ensureUnusedCapacity(gpa, 4);
+        const payload_index = @intCast(u32, astgen.extra.items.len);
+
+        if (args.src_node != 0) {
+            const node_offset = gz.nodeIndexToRelative(args.src_node);
+            astgen.extra.appendAssumeCapacity(@bitCast(u32, node_offset));
+        }
+        if (args.body_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.body_len);
+        }
+        if (args.fields_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.fields_len);
+        }
+        if (args.decls_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.decls_len);
+        }
+        astgen.instructions.set(inst, .{
+            .tag = .extended,
+            .data = .{ .extended = .{
+                .opcode = .struct_decl,
+                .small = @bitCast(u16, Zir.Inst.StructDecl.Small{
+                    .has_src_node = args.src_node != 0,
+                    .has_body_len = args.body_len != 0,
+                    .has_fields_len = args.fields_len != 0,
+                    .has_decls_len = args.decls_len != 0,
+                    .name_strategy = gz.anon_name_strategy,
+                    .layout = args.layout,
+                }),
+                .operand = payload_index,
+            } },
+        });
+    }
+
+    fn setUnion(gz: *GenZir, inst: Zir.Inst.Index, args: struct {
+        src_node: ast.Node.Index,
+        tag_type: Zir.Inst.Ref,
+        body_len: u32,
+        fields_len: u32,
+        decls_len: u32,
+        layout: std.builtin.TypeInfo.ContainerLayout,
+        auto_enum_tag: bool,
+    }) !void {
+        const astgen = gz.astgen;
+        const gpa = astgen.gpa;
+
+        try astgen.extra.ensureUnusedCapacity(gpa, 5);
+        const payload_index = @intCast(u32, astgen.extra.items.len);
+
+        if (args.src_node != 0) {
+            const node_offset = gz.nodeIndexToRelative(args.src_node);
+            astgen.extra.appendAssumeCapacity(@bitCast(u32, node_offset));
+        }
+        if (args.tag_type != .none) {
+            astgen.extra.appendAssumeCapacity(@enumToInt(args.tag_type));
+        }
+        if (args.body_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.body_len);
+        }
+        if (args.fields_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.fields_len);
+        }
+        if (args.decls_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.decls_len);
+        }
+        astgen.instructions.set(inst, .{
+            .tag = .extended,
+            .data = .{ .extended = .{
+                .opcode = .union_decl,
+                .small = @bitCast(u16, Zir.Inst.UnionDecl.Small{
+                    .has_src_node = args.src_node != 0,
+                    .has_tag_type = args.tag_type != .none,
+                    .has_body_len = args.body_len != 0,
+                    .has_fields_len = args.fields_len != 0,
+                    .has_decls_len = args.decls_len != 0,
+                    .name_strategy = gz.anon_name_strategy,
+                    .layout = args.layout,
+                    .auto_enum_tag = args.auto_enum_tag,
+                }),
+                .operand = payload_index,
+            } },
+        });
+    }
+
+    fn setEnum(gz: *GenZir, inst: Zir.Inst.Index, args: struct {
+        src_node: ast.Node.Index,
+        tag_type: Zir.Inst.Ref,
+        body_len: u32,
+        fields_len: u32,
+        decls_len: u32,
+        nonexhaustive: bool,
+    }) !void {
+        const astgen = gz.astgen;
+        const gpa = astgen.gpa;
+
+        try astgen.extra.ensureUnusedCapacity(gpa, 5);
+        const payload_index = @intCast(u32, astgen.extra.items.len);
+
+        if (args.src_node != 0) {
+            const node_offset = gz.nodeIndexToRelative(args.src_node);
+            astgen.extra.appendAssumeCapacity(@bitCast(u32, node_offset));
+        }
+        if (args.tag_type != .none) {
+            astgen.extra.appendAssumeCapacity(@enumToInt(args.tag_type));
+        }
+        if (args.body_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.body_len);
+        }
+        if (args.fields_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.fields_len);
+        }
+        if (args.decls_len != 0) {
+            astgen.extra.appendAssumeCapacity(args.decls_len);
+        }
+        astgen.instructions.set(inst, .{
+            .tag = .extended,
+            .data = .{ .extended = .{
+                .opcode = .enum_decl,
+                .small = @bitCast(u16, Zir.Inst.EnumDecl.Small{
+                    .has_src_node = args.src_node != 0,
+                    .has_tag_type = args.tag_type != .none,
+                    .has_body_len = args.body_len != 0,
+                    .has_fields_len = args.fields_len != 0,
+                    .has_decls_len = args.decls_len != 0,
+                    .name_strategy = gz.anon_name_strategy,
+                    .nonexhaustive = args.nonexhaustive,
+                }),
+                .operand = payload_index,
+            } },
+        });
+    }
+
     fn add(gz: *GenZir, inst: Zir.Inst) !Zir.Inst.Ref {
         return gz.indexToRef(try gz.addAsIndex(inst));
     }
 
     fn addAsIndex(gz: *GenZir, inst: Zir.Inst) !Zir.Inst.Index {
         const gpa = gz.astgen.gpa;
-        try gz.instructions.ensureCapacity(gpa, gz.instructions.items.len + 1);
-        try gz.astgen.instructions.ensureCapacity(gpa, gz.astgen.instructions.len + 1);
+        try gz.instructions.ensureUnusedCapacity(gpa, 1);
+        try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
 
         const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
         gz.astgen.instructions.appendAssumeCapacity(inst);
         gz.instructions.appendAssumeCapacity(new_index);
         return new_index;
     }
+
+    fn reserveInstructionIndex(gz: *GenZir) !Zir.Inst.Index {
+        const gpa = gz.astgen.gpa;
+        try gz.instructions.ensureUnusedCapacity(gpa, 1);
+        try gz.astgen.instructions.ensureUnusedCapacity(gpa, 1);
+
+        const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
+        gz.astgen.instructions.len += 1;
+        gz.instructions.appendAssumeCapacity(new_index);
+        return new_index;
+    }
 };
+
+/// This can only be for short-lived references; the memory becomes invalidated
+/// when another string is added.
+fn nullTerminatedString(astgen: AstGen, index: usize) [*:0]const u8 {
+    return @ptrCast([*:0]const u8, astgen.string_bytes.items.ptr) + index;
+}
src/Module.zig
@@ -75,6 +75,8 @@ failed_files: std.AutoArrayHashMapUnmanaged(*Scope.File, ?*ErrorMsg) = .{},
 /// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator.
 failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{},
 
+next_anon_name_index: usize = 0,
+
 /// Candidates for deletion. After a semantic analysis update completes, this list
 /// contains Decls that need to be deleted if they end up having no references to them.
 deletion_set: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
@@ -884,8 +886,11 @@ pub const Scope = struct {
         /// Declaration order is preserved via entry order.
         /// Key memory is owned by `decl.name`.
         /// TODO save memory with https://github.com/ziglang/zig/issues/8619.
+        /// Anonymous decls are not stored here; they are kept in `anon_decls` instead.
         decls: std.StringArrayHashMapUnmanaged(*Decl) = .{},
 
+        anon_decls: std.AutoArrayHashMapUnmanaged(*Decl, void) = .{},
+
         pub fn deinit(ns: *Namespace, mod: *Module) void {
             ns.clearDecls(mod);
             ns.* = undefined;
@@ -899,15 +904,27 @@ pub const Scope = struct {
             var decls = ns.decls;
             ns.decls = .{};
 
+            var anon_decls = ns.anon_decls;
+            ns.anon_decls = .{};
+
             for (decls.items()) |entry| {
                 entry.value.destroy(mod);
             }
             decls.deinit(gpa);
+
+            for (anon_decls.items()) |entry| {
+                entry.key.destroy(mod);
+            }
+            anon_decls.deinit(gpa);
         }
 
         pub fn removeDecl(ns: *Namespace, child: *Decl) void {
-            // Preserve declaration order.
-            _ = ns.decls.orderedRemove(mem.spanZ(child.name));
+            if (child.zir_decl_index == 0) {
+                _ = ns.anon_decls.swapRemove(child);
+            } else {
+                // Preserve declaration order.
+                _ = ns.decls.orderedRemove(mem.spanZ(child.name));
+            }
         }
 
         // This renders e.g. "std.fs.Dir.OpenOptions"
@@ -2607,10 +2624,14 @@ fn updateZirRefs(gpa: *Allocator, file: *Scope.File, old_zir: Zir) !void {
         }
 
         if (decl.getInnerNamespace()) |namespace| {
-            for (namespace.decls.items()) |*entry| {
+            for (namespace.decls.items()) |entry| {
                 const sub_decl = entry.value;
                 try decl_stack.append(gpa, sub_decl);
             }
+            for (namespace.anon_decls.items()) |entry| {
+                const sub_decl = entry.key;
+                try decl_stack.append(gpa, sub_decl);
+            }
         }
     }
 }
@@ -3741,31 +3762,17 @@ pub fn constIntBig(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, b
 pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue) !*Decl {
     const scope_decl = scope.ownerDecl().?;
     const namespace = scope_decl.namespace;
-    try namespace.decls.ensureUnusedCapacity(mod.gpa, 1);
-
-    // Find a unique name for the anon decl.
-    var name_buf = std.ArrayList(u8).init(mod.gpa);
-    defer name_buf.deinit();
-
-    try name_buf.appendSlice(mem.spanZ(scope_decl.name));
-    var name_index: usize = namespace.decls.count();
-
-    const new_decl = while (true) {
-        const gop = namespace.decls.getOrPutAssumeCapacity(name_buf.items);
-        if (!gop.found_existing) {
-            const name = try name_buf.toOwnedSliceSentinel(0);
-            const new_decl = try mod.allocateNewDecl(namespace, scope_decl.src_node);
-            new_decl.name = name;
-            gop.entry.key = name;
-            gop.entry.value = new_decl;
-            break gop.entry.value;
-        }
+    try namespace.anon_decls.ensureUnusedCapacity(mod.gpa, 1);
+
+    const name_index = mod.getNextAnonNameIndex();
+    const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{
+        scope_decl.name, name_index,
+    });
+    errdefer mod.gpa.free(name);
 
-        name_buf.clearRetainingCapacity();
-        try name_buf.writer().print("{s}__anon_{d}", .{ scope_decl.name, name_index });
-        name_index += 1;
-    } else unreachable; // TODO should not need else unreachable on while(true)
+    const new_decl = try mod.allocateNewDecl(namespace, scope_decl.src_node);
 
+    new_decl.name = name;
     new_decl.src_line = scope_decl.src_line;
     new_decl.ty = typed_value.ty;
     new_decl.val = typed_value.val;
@@ -3773,6 +3780,8 @@ pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue)
     new_decl.analysis = .complete;
     new_decl.generation = mod.generation;
 
+    namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {});
+
     // TODO: This generates the Decl into the machine code file if it is of a
     // type that is non-zero size. We should be able to further improve the
     // compiler to omit Decls which are only referenced at compile-time and not runtime.
@@ -3784,6 +3793,10 @@ pub fn createAnonymousDecl(mod: *Module, scope: *Scope, typed_value: TypedValue)
     return new_decl;
 }
 
+fn getNextAnonNameIndex(mod: *Module) usize {
+    return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic);
+}
+
 /// This looks up a bare identifier in the given scope. This will walk up the tree of namespaces
 /// in scope and check each one for the identifier.
 /// TODO emit a compile error if more than one decl would be matched.
@@ -4394,18 +4407,38 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) InnerError!void {
 
     const gpa = mod.gpa;
     const zir = struct_obj.owner_decl.namespace.file_scope.zir;
-    const inst_data = zir.instructions.items(.data)[struct_obj.zir_index].pl_node;
-    const src = inst_data.src();
-    const extra = zir.extraData(Zir.Inst.StructDecl, inst_data.payload_index);
-    const fields_len = extra.data.fields_len;
-    const decls_len = extra.data.decls_len;
+    const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
+    assert(extended.opcode == .struct_decl);
+    const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset };
+    extra_index += @boolToInt(small.has_src_node);
+
+    const body_len = if (small.has_body_len) blk: {
+        const body_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :blk body_len;
+    } else 0;
+
+    const fields_len = if (small.has_fields_len) blk: {
+        const fields_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :blk fields_len;
+    } else 0;
+
+    const decls_len = if (small.has_decls_len) decls_len: {
+        const decls_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :decls_len decls_len;
+    } else 0;
 
     // Skip over decls.
-    var decls_it = zir.declIterator(struct_obj.zir_index);
+    var decls_it = zir.declIteratorInner(extra_index, decls_len);
     while (decls_it.next()) |_| {}
-    var extra_index = decls_it.extra_index;
+    extra_index = decls_it.extra_index;
 
-    const body = zir.extra[extra_index..][0..extra.data.body_len];
+    const body = zir.extra[extra_index..][0..body_len];
     if (fields_len == 0) {
         assert(body.len == 0);
         return;
@@ -4525,18 +4558,44 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) InnerError!void {
 
     const gpa = mod.gpa;
     const zir = union_obj.owner_decl.namespace.file_scope.zir;
-    const inst_data = zir.instructions.items(.data)[union_obj.zir_index].pl_node;
-    const src = inst_data.src();
-    const extra = zir.extraData(Zir.Inst.UnionDecl, inst_data.payload_index);
-    const fields_len = extra.data.fields_len;
-    const decls_len = extra.data.decls_len;
+    const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
+    assert(extended.opcode == .union_decl);
+    const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset };
+    extra_index += @boolToInt(small.has_src_node);
+
+    const tag_type_ref = if (small.has_tag_type) blk: {
+        const tag_type_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
+        extra_index += 1;
+        break :blk tag_type_ref;
+    } else .none;
+
+    const body_len = if (small.has_body_len) blk: {
+        const body_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :blk body_len;
+    } else 0;
+
+    const fields_len = if (small.has_fields_len) blk: {
+        const fields_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :blk fields_len;
+    } else 0;
+
+    const decls_len = if (small.has_decls_len) decls_len: {
+        const decls_len = zir.extra[extra_index];
+        extra_index += 1;
+        break :decls_len decls_len;
+    } else 0;
 
     // Skip over decls.
-    var decls_it = zir.declIterator(union_obj.zir_index);
+    var decls_it = zir.declIteratorInner(extra_index, decls_len);
     while (decls_it.next()) |_| {}
-    var extra_index = decls_it.extra_index;
+    extra_index = decls_it.extra_index;
 
-    const body = zir.extra[extra_index..][0..extra.data.body_len];
+    const body = zir.extra[extra_index..][0..body_len];
     if (fields_len == 0) {
         assert(body.len == 0);
         return;
@@ -4580,8 +4639,6 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) InnerError!void {
         _ = try sema.analyzeBody(&block, body);
     }
 
-    var auto_enum_tag: ?bool = null;
-
     const bits_per_field = 4;
     const fields_per_u32 = 32 / bits_per_field;
     const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
@@ -4603,10 +4660,6 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) InnerError!void {
         const unused = @truncate(u1, cur_bit_bag) != 0;
         cur_bit_bag >>= 1;
 
-        if (auto_enum_tag == null) {
-            auto_enum_tag = unused;
-        }
-
         const field_name_zir = zir.nullTerminatedString(zir.extra[extra_index]);
         extra_index += 1;
 
@@ -4653,7 +4706,7 @@ pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) InnerError!void {
         }
     }
 
-    // TODO resolve the union tag type
+    // TODO resolve the union tag_type_ref
 }
 
 /// Called from `performAllTheWork`, after all AstGen workers have finished,
src/Sema.zig
@@ -350,16 +350,12 @@ pub fn analyzeBody(
             .trunc => try sema.zirUnaryMath(block, inst),
             .round => try sema.zirUnaryMath(block, inst),
 
-            .struct_decl             => try sema.zirStructDecl(block, inst, .Auto),
-            .struct_decl_packed      => try sema.zirStructDecl(block, inst, .Packed),
-            .struct_decl_extern      => try sema.zirStructDecl(block, inst, .Extern),
-            .enum_decl               => try sema.zirEnumDecl(block, inst, false),
-            .enum_decl_nonexhaustive => try sema.zirEnumDecl(block, inst, true),
-            .union_decl              => try sema.zirUnionDecl(block, inst, .Auto),
-            .union_decl_packed       => try sema.zirUnionDecl(block, inst, .Packed),
-            .union_decl_extern       => try sema.zirUnionDecl(block, inst, .Extern),
-            .opaque_decl             => try sema.zirOpaqueDecl(block, inst),
-            .error_set_decl          => try sema.zirErrorSetDecl(block, inst),
+            .opaque_decl         => try sema.zirOpaqueDecl(block, inst, .parent),
+            .opaque_decl_anon    => try sema.zirOpaqueDecl(block, inst, .anon),
+            .opaque_decl_func    => try sema.zirOpaqueDecl(block, inst, .func),
+            .error_set_decl      => try sema.zirErrorSetDecl(block, inst, .parent),
+            .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon),
+            .error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func),
 
             .add     => try sema.zirArithmetic(block, inst),
             .addwrap => try sema.zirArithmetic(block, inst),
@@ -515,6 +511,9 @@ fn zirExtended(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerErro
         // zig fmt: off
         .func               => return sema.zirFuncExtended(      block, extended, inst),
         .variable           => return sema.zirVarExtended(       block, extended),
+        .struct_decl        => return sema.zirStructDecl(        block, extended, inst),
+        .enum_decl          => return sema.zirEnumDecl(          block, extended),
+        .union_decl         => return sema.zirUnionDecl(         block, extended, inst),
         .ret_ptr            => return sema.zirRetPtr(            block, extended),
         .ret_type           => return sema.zirRetType(           block, extended),
         .this               => return sema.zirThis(              block, extended),
@@ -686,21 +685,34 @@ pub fn analyzeStructDecl(
     inst: Zir.Inst.Index,
     struct_obj: *Module.Struct,
 ) InnerError!void {
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const extra = sema.code.extraData(Zir.Inst.StructDecl, inst_data.payload_index);
-    const decls_len = extra.data.decls_len;
+    const extended = sema.code.instructions.items(.data)[inst].extended;
+    assert(extended.opcode == .struct_decl);
+    const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
+
+    var extra_index: usize = extended.operand;
+    extra_index += @boolToInt(small.has_src_node);
+    extra_index += @boolToInt(small.has_body_len);
+    extra_index += @boolToInt(small.has_fields_len);
+    const decls_len = if (small.has_decls_len) blk: {
+        const decls_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        break :blk decls_len;
+    } else 0;
 
-    _ = try sema.mod.scanNamespace(&struct_obj.namespace, extra.end, decls_len, new_decl);
+    _ = try sema.mod.scanNamespace(&struct_obj.namespace, extra_index, decls_len, new_decl);
 }
 
 fn zirStructDecl(
     sema: *Sema,
     block: *Scope.Block,
+    extended: Zir.Inst.Extended.InstData,
     inst: Zir.Inst.Index,
-    layout: std.builtin.TypeInfo.ContainerLayout,
 ) InnerError!*Inst {
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src = inst_data.src();
+    const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
+    const src: LazySrcLoc = if (small.has_src_node) blk: {
+        const node_offset = @bitCast(i32, sema.code.extra[extended.operand]);
+        break :blk .{ .node_offset = node_offset };
+    } else sema.src;
 
     var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
 
@@ -714,9 +726,9 @@ fn zirStructDecl(
     struct_obj.* = .{
         .owner_decl = new_decl,
         .fields = .{},
-        .node_offset = inst_data.src_node,
+        .node_offset = src.node_offset,
         .zir_index = inst,
-        .layout = layout,
+        .layout = small.layout,
         .status = .none,
         .namespace = .{
             .parent = sema.owner_decl.namespace,
@@ -735,27 +747,53 @@ fn zirStructDecl(
 fn zirEnumDecl(
     sema: *Sema,
     block: *Scope.Block,
-    inst: Zir.Inst.Index,
-    nonexhaustive: bool,
+    extended: Zir.Inst.Extended.InstData,
 ) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
     const gpa = sema.gpa;
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src = inst_data.src();
-    const extra = sema.code.extraData(Zir.Inst.EnumDecl, inst_data.payload_index);
-    const fields_len = extra.data.fields_len;
-    const decls_len = extra.data.decls_len;
+    const small = @bitCast(Zir.Inst.EnumDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = if (small.has_src_node) blk: {
+        const node_offset = @bitCast(i32, sema.code.extra[extra_index]);
+        extra_index += 1;
+        break :blk .{ .node_offset = node_offset };
+    } else sema.src;
+
+    const tag_type_ref = if (small.has_tag_type) blk: {
+        const tag_type_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
+        extra_index += 1;
+        break :blk tag_type_ref;
+    } else .none;
+
+    const body_len = if (small.has_body_len) blk: {
+        const body_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        break :blk body_len;
+    } else 0;
+
+    const fields_len = if (small.has_fields_len) blk: {
+        const fields_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        break :blk fields_len;
+    } else 0;
+
+    const decls_len = if (small.has_decls_len) blk: {
+        const decls_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        break :blk decls_len;
+    } else 0;
 
     var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
 
     const tag_ty = blk: {
-        if (extra.data.tag_type != .none) {
+        if (tag_type_ref != .none) {
             // TODO better source location
             // TODO (needs AstGen fix too) move this eval to the block so it gets allocated
             // in the new decl arena.
-            break :blk try sema.resolveType(block, src, extra.data.tag_type);
+            break :blk try sema.resolveType(block, src, tag_type_ref);
         }
         const bits = std.math.log2_int_ceil(usize, fields_len);
         break :blk try Type.Tag.int_unsigned.create(&new_decl_arena.allocator, bits);
@@ -764,7 +802,7 @@ fn zirEnumDecl(
     const enum_obj = try new_decl_arena.allocator.create(Module.EnumFull);
     const enum_ty_payload = try new_decl_arena.allocator.create(Type.Payload.EnumFull);
     enum_ty_payload.* = .{
-        .base = .{ .tag = if (nonexhaustive) .enum_nonexhaustive else .enum_full },
+        .base = .{ .tag = if (small.nonexhaustive) .enum_nonexhaustive else .enum_full },
         .data = enum_obj,
     };
     const enum_ty = Type.initPayload(&enum_ty_payload.base);
@@ -778,7 +816,7 @@ fn zirEnumDecl(
         .tag_ty = tag_ty,
         .fields = .{},
         .values = .{},
-        .node_offset = inst_data.src_node,
+        .node_offset = src.node_offset,
         .namespace = .{
             .parent = sema.owner_decl.namespace,
             .ty = enum_ty,
@@ -789,14 +827,9 @@ fn zirEnumDecl(
         &enum_obj.namespace, new_decl, new_decl.name,
     });
 
-    var extra_index: usize = try sema.mod.scanNamespace(
-        &enum_obj.namespace,
-        extra.end,
-        decls_len,
-        new_decl,
-    );
+    extra_index = try sema.mod.scanNamespace(&enum_obj.namespace, extra_index, decls_len, new_decl);
 
-    const body = sema.code.extra[extra_index..][0..extra.data.body_len];
+    const body = sema.code.extra[extra_index..][0..body_len];
     if (fields_len == 0) {
         assert(body.len == 0);
         try new_decl.finalizeNewArena(&new_decl_arena);
@@ -894,16 +927,30 @@ fn zirEnumDecl(
 fn zirUnionDecl(
     sema: *Sema,
     block: *Scope.Block,
+    extended: Zir.Inst.Extended.InstData,
     inst: Zir.Inst.Index,
-    layout: std.builtin.TypeInfo.ContainerLayout,
 ) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
-    const src = inst_data.src();
-    const extra = sema.code.extraData(Zir.Inst.UnionDecl, inst_data.payload_index);
-    const decls_len = extra.data.decls_len;
+    const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small);
+    var extra_index: usize = extended.operand;
+
+    const src: LazySrcLoc = if (small.has_src_node) blk: {
+        const node_offset = @bitCast(i32, sema.code.extra[extra_index]);
+        extra_index += 1;
+        break :blk .{ .node_offset = node_offset };
+    } else sema.src;
+
+    extra_index += @boolToInt(small.has_tag_type);
+    extra_index += @boolToInt(small.has_body_len);
+    extra_index += @boolToInt(small.has_fields_len);
+
+    const decls_len = if (small.has_decls_len) blk: {
+        const decls_len = sema.code.extra[extra_index];
+        extra_index += 1;
+        break :blk decls_len;
+    } else 0;
 
     var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
 
@@ -918,9 +965,9 @@ fn zirUnionDecl(
         .owner_decl = new_decl,
         .tag_ty = Type.initTag(.@"null"),
         .fields = .{},
-        .node_offset = inst_data.src_node,
+        .node_offset = src.node_offset,
         .zir_index = inst,
-        .layout = layout,
+        .layout = small.layout,
         .status = .none,
         .namespace = .{
             .parent = sema.owner_decl.namespace,
@@ -932,13 +979,18 @@ fn zirUnionDecl(
         &union_obj.namespace, new_decl, new_decl.name,
     });
 
-    _ = try sema.mod.scanNamespace(&union_obj.namespace, extra.end, decls_len, new_decl);
+    _ = try sema.mod.scanNamespace(&union_obj.namespace, extra_index, decls_len, new_decl);
 
     try new_decl.finalizeNewArena(&new_decl_arena);
     return sema.analyzeDeclVal(block, src, new_decl);
 }
 
-fn zirOpaqueDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
+fn zirOpaqueDecl(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: Zir.Inst.Index,
+    name_strategy: Zir.Inst.NameStrategy,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -949,7 +1001,12 @@ fn zirOpaqueDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerEr
     return sema.mod.fail(&block.base, sema.src, "TODO implement zirOpaqueDecl", .{});
 }
 
-fn zirErrorSetDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst {
+fn zirErrorSetDecl(
+    sema: *Sema,
+    block: *Scope.Block,
+    inst: Zir.Inst.Index,
+    name_strategy: Zir.Inst.NameStrategy,
+) InnerError!*Inst {
     const tracy = trace(@src());
     defer tracy.end();
 
src/Zir.zig
@@ -296,34 +296,16 @@ pub const Inst = struct {
         /// only the taken branch is analyzed. The then block and else block must
         /// terminate with an "inline" variant of a noreturn instruction.
         condbr_inline,
-        /// A struct type definition. Contains references to ZIR instructions for
-        /// the field types, defaults, and alignments.
-        /// Uses the `pl_node` union field. Payload is `StructDecl`.
-        struct_decl,
-        /// Same as `struct_decl`, except has the `packed` layout.
-        struct_decl_packed,
-        /// Same as `struct_decl`, except has the `extern` layout.
-        struct_decl_extern,
-        /// A union type definition. Contains references to ZIR instructions for
-        /// the field types and optional type tag expression.
-        /// Uses the `pl_node` union field. Payload is `UnionDecl`.
-        union_decl,
-        /// Same as `union_decl`, except has the `packed` layout.
-        union_decl_packed,
-        /// Same as `union_decl`, except has the `extern` layout.
-        union_decl_extern,
-        /// An enum type definition. Contains references to ZIR instructions for
-        /// the field value expressions and optional type tag expression.
-        /// Uses the `pl_node` union field. Payload is `EnumDecl`.
-        enum_decl,
-        /// Same as `enum_decl`, except the enum is non-exhaustive.
-        enum_decl_nonexhaustive,
         /// An opaque type definition. Provides an AST node only.
         /// Uses the `pl_node` union field. Payload is `OpaqueDecl`.
         opaque_decl,
+        opaque_decl_anon,
+        opaque_decl_func,
         /// An error set type definition. Contains a list of field names.
         /// Uses the `pl_node` union field. Payload is `ErrorSetDecl`.
         error_set_decl,
+        error_set_decl_anon,
+        error_set_decl_func,
         /// Declares the beginning of a statement. Used for debug info.
         /// Uses the `dbg_stmt` union field. The line and column are offset
         /// from the parent declaration.
@@ -1011,16 +993,12 @@ pub const Inst = struct {
                 .cmp_gt,
                 .cmp_neq,
                 .coerce_result_ptr,
-                .struct_decl,
-                .struct_decl_packed,
-                .struct_decl_extern,
-                .union_decl,
-                .union_decl_packed,
-                .union_decl_extern,
-                .enum_decl,
-                .enum_decl_nonexhaustive,
                 .opaque_decl,
+                .opaque_decl_anon,
+                .opaque_decl_func,
                 .error_set_decl,
+                .error_set_decl_anon,
+                .error_set_decl_func,
                 .dbg_stmt,
                 .decl_ref,
                 .decl_val,
@@ -1271,16 +1249,12 @@ pub const Inst = struct {
                 .coerce_result_ptr = .bin,
                 .condbr = .pl_node,
                 .condbr_inline = .pl_node,
-                .struct_decl = .pl_node,
-                .struct_decl_packed = .pl_node,
-                .struct_decl_extern = .pl_node,
-                .union_decl = .pl_node,
-                .union_decl_packed = .pl_node,
-                .union_decl_extern = .pl_node,
-                .enum_decl = .pl_node,
-                .enum_decl_nonexhaustive = .pl_node,
                 .opaque_decl = .pl_node,
+                .opaque_decl_anon = .pl_node,
+                .opaque_decl_func = .pl_node,
                 .error_set_decl = .pl_node,
+                .error_set_decl_anon = .pl_node,
+                .error_set_decl_func = .pl_node,
                 .dbg_stmt = .dbg_stmt,
                 .decl_ref = .str_tok,
                 .decl_val = .str_tok,
@@ -1507,6 +1481,21 @@ pub const Inst = struct {
         /// `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`.
+        /// `small` is `StructDecl.Small`.
+        struct_decl,
+        /// An enum type definition. Contains references to ZIR instructions for
+        /// the field value expressions and optional type tag expression.
+        /// `operand` is payload index to `EnumDecl`.
+        /// `small` is `EnumDecl.Small`.
+        enum_decl,
+        /// A union type definition. Contains references to ZIR instructions for
+        /// the field types and optional type tag expression.
+        /// `operand` is payload index to `UnionDecl`.
+        /// `small` is `UnionDecl.Small`.
+        union_decl,
         /// Obtains a pointer to the return value.
         /// `operand` is `src_node: i32`.
         ret_ptr,
@@ -2251,9 +2240,9 @@ pub const Inst = struct {
         body_len: u32,
 
         pub const SrcLocs = struct {
-            /// Absolute line number in the source file.
+            /// Absolute line index in the source file.
             lbrace_line: u32,
-            /// Absolute line number in the source file.
+            /// Absolute line index in the source file.
             rbrace_line: u32,
             /// lbrace_column is least significant bits u16
             /// rbrace_column is most significant bits u16
@@ -2414,13 +2403,17 @@ pub const Inst = struct {
     };
 
     /// Trailing:
-    /// 0. decl_bits: u32 // for every 8 decls
+    /// 0. src_node: i32, // if has_src_node
+    /// 1. body_len: u32, // if has_body_len
+    /// 2. fields_len: u32, // if has_fields_len
+    /// 3. decls_len: u32, // if has_decls_len
+    /// 4. decl_bits: u32 // for every 8 decls
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding decl is pub
     ///      0b00X0: whether corresponding decl is exported
     ///      0b0X00: whether corresponding decl has an align expression
     ///      0bX000: whether corresponding decl has a linksection expression
-    /// 1. decl: { // for every decls_len
+    /// 5. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
     ///        name: u32, // null terminated string index
@@ -2433,14 +2426,14 @@ pub const Inst = struct {
     ///        align: Ref, // if corresponding bit is set
     ///        link_section: Ref, // if corresponding bit is set
     ///    }
-    /// 2. inst: Index // for every body_len
-    /// 3. flags: u32 // for every 8 fields
+    /// 6. inst: Index // for every body_len
+    /// 7. flags: u32 // for every 8 fields
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding field has an align expression
     ///      0b00X0: whether corresponding field has a default expression
     ///      0b0X00: whether corresponding field is comptime
     ///      0bX000: unused
-    /// 4. fields: { // for every fields_len
+    /// 8. fields: { // for every fields_len
     ///        field_name: u32,
     ///        field_type: Ref,
     ///        - if none, means `anytype`.
@@ -2448,19 +2441,42 @@ pub const Inst = struct {
     ///        default_value: Ref, // if corresponding bit is set
     ///    }
     pub const StructDecl = struct {
-        body_len: u32,
-        fields_len: u32,
-        decls_len: u32,
+        pub const Small = packed struct {
+            has_src_node: bool,
+            has_body_len: bool,
+            has_fields_len: bool,
+            has_decls_len: bool,
+            name_strategy: NameStrategy,
+            layout: std.builtin.TypeInfo.ContainerLayout,
+            _: u8 = undefined,
+        };
+    };
+
+    pub const NameStrategy = enum(u2) {
+        /// Use the same name as the parent declaration name.
+        /// e.g. `const Foo = struct {...};`.
+        parent,
+        /// Use the name of the currently executing comptime function call,
+        /// with the current parameters. e.g. `ArrayList(i32)`.
+        func,
+        /// Create an anonymous name for this declaration.
+        /// Like this: "ParentDeclName_struct_69"
+        anon,
     };
 
     /// Trailing:
-    /// 0. decl_bits: u32 // for every 8 decls
+    /// 0. src_node: i32, // if has_src_node
+    /// 1. tag_type: Ref, // if has_tag_type
+    /// 2. body_len: u32, // if has_body_len
+    /// 3. fields_len: u32, // if has_fields_len
+    /// 4. decls_len: u32, // if has_decls_len
+    /// 5. decl_bits: u32 // for every 8 decls
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding decl is pub
     ///      0b00X0: whether corresponding decl is exported
     ///      0b0X00: whether corresponding decl has an align expression
     ///      0bX000: whether corresponding decl has a linksection expression
-    /// 1. decl: { // for every decls_len
+    /// 6. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
     ///        name: u32, // null terminated string index
@@ -2473,29 +2489,39 @@ pub const Inst = struct {
     ///        align: Ref, // if corresponding bit is set
     ///        link_section: Ref, // if corresponding bit is set
     ///    }
-    /// 2. inst: Index // for every body_len
-    /// 3. has_bits: u32 // for every 32 fields
+    /// 7. inst: Index // for every body_len
+    /// 8. has_bits: u32 // for every 32 fields
     ///    - the bit is whether corresponding field has an value expression
-    /// 4. fields: { // for every fields_len
+    /// 9. fields: { // for every fields_len
     ///        field_name: u32,
     ///        value: Ref, // if corresponding bit is set
     ///    }
     pub const EnumDecl = struct {
-        /// Can be `Ref.none`.
-        tag_type: Ref,
-        body_len: u32,
-        fields_len: u32,
-        decls_len: u32,
+        pub const Small = packed struct {
+            has_src_node: bool,
+            has_tag_type: bool,
+            has_body_len: bool,
+            has_fields_len: bool,
+            has_decls_len: bool,
+            name_strategy: NameStrategy,
+            nonexhaustive: bool,
+            _: u8 = undefined,
+        };
     };
 
     /// Trailing:
-    /// 0. decl_bits: u32 // for every 8 decls
+    /// 0. src_node: i32, // if has_src_node
+    /// 1. tag_type: Ref, // if has_tag_type
+    /// 2. body_len: u32, // if has_body_len
+    /// 3. fields_len: u32, // if has_fields_len
+    /// 4. decls_len: u32, // if has_decls_len
+    /// 5. decl_bits: u32 // for every 8 decls
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding decl is pub
     ///      0b00X0: whether corresponding decl is exported
     ///      0b0X00: whether corresponding decl has an align expression
     ///      0bX000: whether corresponding decl has a linksection expression
-    /// 1. decl: { // for every decls_len
+    /// 6. decl: { // for every decls_len
     ///        src_hash: [4]u32, // hash of source bytes
     ///        line: u32, // line number of decl, relative to parent
     ///        name: u32, // null terminated string index
@@ -2508,29 +2534,33 @@ pub const Inst = struct {
     ///        align: Ref, // if corresponding bit is set
     ///        link_section: Ref, // if corresponding bit is set
     ///    }
-    /// 2. inst: Index // for every body_len
-    /// 3. has_bits: u32 // for every 8 fields
+    /// 7. inst: Index // for every body_len
+    /// 8. has_bits: u32 // for every 8 fields
     ///    - sets of 4 bits:
     ///      0b000X: whether corresponding field has a type expression
     ///      0b00X0: whether corresponding field has a align expression
     ///      0b0X00: whether corresponding field has a tag value expression
-    ///      0bX000: unused(*)
-    ///    * the first unused bit (the unused bit of the first field) is used
-    ///      to indicate whether auto enum tag is enabled.
-    ///      0 = union(tag_type)
-    ///      1 = union(enum(tag_type))
-    /// 4. fields: { // for every fields_len
+    ///      0bX000: unused
+    /// 9. fields: { // for every fields_len
     ///        field_name: u32, // null terminated string index
     ///        field_type: Ref, // if corresponding bit is set
     ///        align: Ref, // if corresponding bit is set
     ///        tag_value: Ref, // if corresponding bit is set
     ///    }
     pub const UnionDecl = struct {
-        /// Can be `Ref.none`.
-        tag_type: Ref,
-        body_len: u32,
-        fields_len: u32,
-        decls_len: u32,
+        pub const Small = packed struct {
+            has_src_node: bool,
+            has_tag_type: bool,
+            has_body_len: bool,
+            has_fields_len: bool,
+            has_decls_len: bool,
+            name_strategy: NameStrategy,
+            layout: std.builtin.TypeInfo.ContainerLayout,
+            /// false: union(tag_type)
+            ///  true: union(enum(tag_type))
+            auto_enum_tag: bool,
+            _: u6 = undefined,
+        };
     };
 
     /// Trailing:
@@ -2901,8 +2931,6 @@ const Writer = struct {
             .field_type => try self.writeFieldType(stream, inst),
             .field_type_ref => try self.writeFieldTypeRef(stream, inst),
 
-            .error_set_decl => try self.writePlNodeErrorSetDecl(stream, inst),
-
             .add,
             .addwrap,
             .array_cat,
@@ -2980,21 +3008,13 @@ const Writer = struct {
             .condbr_inline,
             => try self.writePlNodeCondBr(stream, inst),
 
-            .struct_decl,
-            .struct_decl_packed,
-            .struct_decl_extern,
-            => try self.writeStructDecl(stream, inst),
-
-            .union_decl,
-            .union_decl_packed,
-            .union_decl_extern,
-            => try self.writeUnionDecl(stream, inst),
-
-            .enum_decl,
-            .enum_decl_nonexhaustive,
-            => try self.writeEnumDecl(stream, inst),
+            .opaque_decl => try self.writeOpaqueDecl(stream, inst, .parent),
+            .opaque_decl_anon => try self.writeOpaqueDecl(stream, inst, .anon),
+            .opaque_decl_func => try self.writeOpaqueDecl(stream, inst, .func),
 
-            .opaque_decl => try self.writeOpaqueDecl(stream, inst),
+            .error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent),
+            .error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon),
+            .error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func),
 
             .switch_block => try self.writePlNodeSwitchBr(stream, inst, .none),
             .switch_block_else => try self.writePlNodeSwitchBr(stream, inst, .@"else"),
@@ -3080,6 +3100,10 @@ const Writer = struct {
             .shl_with_overflow,
             => try self.writeOverflowArithmetic(stream, extended),
 
+            .struct_decl => try self.writeStructDecl(stream, extended),
+            .union_decl => try self.writeUnionDecl(stream, extended),
+            .enum_decl => try self.writeEnumDecl(stream, extended),
+
             .alloc,
             .builtin_extern,
             .c_undef,
@@ -3315,25 +3339,6 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
-    fn writePlNodeErrorSetDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
-        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const extra = self.code.extraData(Inst.ErrorSetDecl, inst_data.payload_index);
-        const fields = self.code.extra[extra.end..][0..extra.data.fields_len];
-
-        try stream.writeAll("{\n");
-        self.indent += 2;
-        for (fields) |str_index| {
-            const name = self.code.nullTerminatedString(str_index);
-            try stream.writeByteNTimes(' ', self.indent);
-            try stream.print("{},\n", .{std.zig.fmtId(name)});
-        }
-        self.indent -= 2;
-        try stream.writeByteNTimes(' ', self.indent);
-        try stream.writeAll("}) ");
-
-        try self.writeSrc(stream, inst_data.src());
-    }
-
     fn writeNodeMultiOp(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void {
         const extra = self.code.extraData(Inst.NodeMultiOp, extended.operand);
         const src: LazySrcLoc = .{ .node_offset = extra.data.src_node };
@@ -3483,33 +3488,56 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
-    fn writeStructDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
-        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const extra = self.code.extraData(Inst.StructDecl, inst_data.payload_index);
-        const fields_len = extra.data.fields_len;
-        const decls_len = extra.data.decls_len;
+    fn writeStructDecl(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void {
+        const small = @bitCast(Inst.StructDecl.Small, extended.small);
+
+        var extra_index: usize = extended.operand;
+
+        const src_node: ?i32 = if (small.has_src_node) blk: {
+            const src_node = @bitCast(i32, self.code.extra[extra_index]);
+            extra_index += 1;
+            break :blk src_node;
+        } else null;
+
+        const body_len = if (small.has_body_len) blk: {
+            const body_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk body_len;
+        } else 0;
 
-        var extra_index: usize = undefined;
+        const fields_len = if (small.has_fields_len) blk: {
+            const fields_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk fields_len;
+        } else 0;
+
+        const decls_len = if (small.has_decls_len) blk: {
+            const decls_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk decls_len;
+        } else 0;
+
+        try stream.print("{s}, {s}, ", .{
+            @tagName(small.name_strategy), @tagName(small.layout),
+        });
 
         if (decls_len == 0) {
             try stream.writeAll("{}, ");
-            extra_index = extra.end;
         } else {
             try stream.writeAll("{\n");
             self.indent += 2;
-            extra_index = try self.writeDecls(stream, decls_len, extra.end);
+            extra_index = try self.writeDecls(stream, decls_len, extra_index);
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
             try stream.writeAll("}, ");
         }
 
-        const body = self.code.extra[extra_index..][0..extra.data.body_len];
+        const body = self.code.extra[extra_index..][0..body_len];
         extra_index += body.len;
 
         if (fields_len == 0) {
             assert(body.len == 0);
-            try stream.writeAll("{}, {}) ");
-            extra_index = extra.end;
+            try stream.writeAll("{}, {})");
         } else {
             self.indent += 2;
             if (body.len == 0) {
@@ -3575,41 +3603,70 @@ const Writer = struct {
 
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
-            try stream.writeAll("}) ");
+            try stream.writeAll("})");
         }
-        try self.writeSrc(stream, inst_data.src());
+        try self.writeSrcNode(stream, src_node);
     }
 
-    fn writeUnionDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
-        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const extra = self.code.extraData(Inst.UnionDecl, inst_data.payload_index);
-        const fields_len = extra.data.fields_len;
-        const decls_len = extra.data.decls_len;
-        const tag_type_ref = extra.data.tag_type;
+    fn writeUnionDecl(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void {
+        const small = @bitCast(Inst.UnionDecl.Small, extended.small);
+
+        var extra_index: usize = extended.operand;
+
+        const src_node: ?i32 = if (small.has_src_node) blk: {
+            const src_node = @bitCast(i32, self.code.extra[extra_index]);
+            extra_index += 1;
+            break :blk src_node;
+        } else null;
+
+        const tag_type_ref = if (small.has_tag_type) blk: {
+            const tag_type_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
+            extra_index += 1;
+            break :blk tag_type_ref;
+        } else .none;
+
+        const body_len = if (small.has_body_len) blk: {
+            const body_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk body_len;
+        } else 0;
+
+        const fields_len = if (small.has_fields_len) blk: {
+            const fields_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk fields_len;
+        } else 0;
 
-        var extra_index: usize = undefined;
+        const decls_len = if (small.has_decls_len) blk: {
+            const decls_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk decls_len;
+        } else 0;
+
+        try stream.print("{s}, {s}, ", .{
+            @tagName(small.name_strategy), @tagName(small.layout),
+        });
+        try self.writeFlag(stream, "autoenum, ", small.auto_enum_tag);
 
         if (decls_len == 0) {
             try stream.writeAll("{}, ");
-            extra_index = extra.end;
         } else {
             try stream.writeAll("{\n");
             self.indent += 2;
-            extra_index = try self.writeDecls(stream, decls_len, extra.end);
+            extra_index = try self.writeDecls(stream, decls_len, extra_index);
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
             try stream.writeAll("}, ");
         }
 
         assert(fields_len != 0);
-        var first_has_auto_enum: ?bool = null;
 
         if (tag_type_ref != .none) {
             try self.writeInstRef(stream, tag_type_ref);
             try stream.writeAll(", ");
         }
 
-        const body = self.code.extra[extra_index..][0..extra.data.body_len];
+        const body = self.code.extra[extra_index..][0..body_len];
         extra_index += body.len;
 
         self.indent += 2;
@@ -3642,12 +3699,10 @@ const Writer = struct {
             cur_bit_bag >>= 1;
             const has_value = @truncate(u1, cur_bit_bag) != 0;
             cur_bit_bag >>= 1;
-            const has_auto_enum = @truncate(u1, cur_bit_bag) != 0;
+            const unused = @truncate(u1, cur_bit_bag) != 0;
             cur_bit_bag >>= 1;
 
-            if (first_has_auto_enum == null) {
-                first_has_auto_enum = has_auto_enum;
-            }
+            _ = unused;
 
             const field_name = self.code.nullTerminatedString(self.code.extra[extra_index]);
             extra_index += 1;
@@ -3681,10 +3736,8 @@ const Writer = struct {
 
         self.indent -= 2;
         try stream.writeByteNTimes(' ', self.indent);
-        try stream.writeAll("}");
-        try self.writeFlag(stream, ", autoenum", first_has_auto_enum.?);
-        try stream.writeAll(") ");
-        try self.writeSrc(stream, inst_data.src());
+        try stream.writeAll("})");
+        try self.writeSrcNode(stream, src_node);
     }
 
     fn writeDecls(self: *Writer, stream: anytype, decls_len: u32, extra_start: usize) !usize {
@@ -3776,22 +3829,49 @@ const Writer = struct {
         return extra_index;
     }
 
-    fn writeEnumDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
-        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
-        const extra = self.code.extraData(Inst.EnumDecl, inst_data.payload_index);
-        const fields_len = extra.data.fields_len;
-        const decls_len = extra.data.decls_len;
-        const tag_type_ref = extra.data.tag_type;
+    fn writeEnumDecl(self: *Writer, stream: anytype, extended: Inst.Extended.InstData) !void {
+        const small = @bitCast(Inst.EnumDecl.Small, extended.small);
+        var extra_index: usize = extended.operand;
+
+        const src_node: ?i32 = if (small.has_src_node) blk: {
+            const src_node = @bitCast(i32, self.code.extra[extra_index]);
+            extra_index += 1;
+            break :blk src_node;
+        } else null;
+
+        const tag_type_ref = if (small.has_tag_type) blk: {
+            const tag_type_ref = @intToEnum(Zir.Inst.Ref, self.code.extra[extra_index]);
+            extra_index += 1;
+            break :blk tag_type_ref;
+        } else .none;
+
+        const body_len = if (small.has_body_len) blk: {
+            const body_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk body_len;
+        } else 0;
+
+        const fields_len = if (small.has_fields_len) blk: {
+            const fields_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk fields_len;
+        } else 0;
+
+        const decls_len = if (small.has_decls_len) blk: {
+            const decls_len = self.code.extra[extra_index];
+            extra_index += 1;
+            break :blk decls_len;
+        } else 0;
 
-        var extra_index: usize = undefined;
+        try stream.print("{s}, ", .{@tagName(small.name_strategy)});
+        try self.writeFlag(stream, "nonexhaustive, ", small.nonexhaustive);
 
         if (decls_len == 0) {
             try stream.writeAll("{}, ");
-            extra_index = extra.end;
         } else {
             try stream.writeAll("{\n");
             self.indent += 2;
-            extra_index = try self.writeDecls(stream, decls_len, extra.end);
+            extra_index = try self.writeDecls(stream, decls_len, extra_index);
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
             try stream.writeAll("}, ");
@@ -3802,12 +3882,12 @@ const Writer = struct {
             try stream.writeAll(", ");
         }
 
-        const body = self.code.extra[extra_index..][0..extra.data.body_len];
+        const body = self.code.extra[extra_index..][0..body_len];
         extra_index += body.len;
 
         if (fields_len == 0) {
             assert(body.len == 0);
-            try stream.writeAll("{}, {}) ");
+            try stream.writeAll("{}, {})");
         } else {
             self.indent += 2;
             if (body.len == 0) {
@@ -3851,16 +3931,23 @@ const Writer = struct {
             }
             self.indent -= 2;
             try stream.writeByteNTimes(' ', self.indent);
-            try stream.writeAll("}) ");
+            try stream.writeAll("})");
         }
-        try self.writeSrc(stream, inst_data.src());
+        try self.writeSrcNode(stream, src_node);
     }
 
-    fn writeOpaqueDecl(self: *Writer, stream: anytype, inst: Inst.Index) !void {
+    fn writeOpaqueDecl(
+        self: *Writer,
+        stream: anytype,
+        inst: Inst.Index,
+        name_strategy: Inst.NameStrategy,
+    ) !void {
         const inst_data = self.code.instructions.items(.data)[inst].pl_node;
         const extra = self.code.extraData(Inst.OpaqueDecl, inst_data.payload_index);
         const decls_len = extra.data.decls_len;
 
+        try stream.print("{s}, ", .{@tagName(name_strategy)});
+
         if (decls_len == 0) {
             try stream.writeAll("}) ");
         } else {
@@ -3874,6 +3961,32 @@ const Writer = struct {
         try self.writeSrc(stream, inst_data.src());
     }
 
+    fn writeErrorSetDecl(
+        self: *Writer,
+        stream: anytype,
+        inst: Inst.Index,
+        name_strategy: Inst.NameStrategy,
+    ) !void {
+        const inst_data = self.code.instructions.items(.data)[inst].pl_node;
+        const extra = self.code.extraData(Inst.ErrorSetDecl, inst_data.payload_index);
+        const fields = self.code.extra[extra.end..][0..extra.data.fields_len];
+
+        try stream.print("{s}, ", .{@tagName(name_strategy)});
+
+        try stream.writeAll("{\n");
+        self.indent += 2;
+        for (fields) |str_index| {
+            const name = self.code.nullTerminatedString(str_index);
+            try stream.writeByteNTimes(' ', self.indent);
+            try stream.print("{},\n", .{std.zig.fmtId(name)});
+        }
+        self.indent -= 2;
+        try stream.writeByteNTimes(' ', self.indent);
+        try stream.writeAll("}) ");
+
+        try self.writeSrc(stream, inst_data.src());
+    }
+
     fn writePlNodeSwitchBr(
         self: *Writer,
         stream: anytype,
@@ -4337,6 +4450,13 @@ const Writer = struct {
         });
     }
 
+    fn writeSrcNode(self: *Writer, stream: anytype, src_node: ?i32) !void {
+        const node_offset = src_node orelse return;
+        const src: LazySrcLoc = .{ .node_offset = node_offset };
+        try stream.writeAll(" ");
+        return self.writeSrc(stream, src);
+    }
+
     fn writeBody(self: *Writer, stream: anytype, body: []const Inst.Index) !void {
         for (body) |inst| {
             try stream.writeByteNTimes(' ', self.indent);
@@ -4389,75 +4509,86 @@ pub const DeclIterator = struct {
 pub fn declIterator(zir: Zir, decl_inst: u32) DeclIterator {
     const tags = zir.instructions.items(.tag);
     const datas = zir.instructions.items(.data);
-    const decl_info: struct {
-        extra_index: usize,
-        decls_len: u32,
-    } = switch (tags[decl_inst]) {
-        .struct_decl,
-        .struct_decl_packed,
-        .struct_decl_extern,
-        => blk: {
-            const inst_data = datas[decl_inst].pl_node;
-            const extra = zir.extraData(Inst.StructDecl, inst_data.payload_index);
-            break :blk .{
-                .extra_index = extra.end,
-                .decls_len = extra.data.decls_len,
-            };
-        },
-
-        .union_decl,
-        .union_decl_packed,
-        .union_decl_extern,
-        => blk: {
-            const inst_data = datas[decl_inst].pl_node;
-            const extra = zir.extraData(Inst.UnionDecl, inst_data.payload_index);
-            break :blk .{
-                .extra_index = extra.end,
-                .decls_len = extra.data.decls_len,
-            };
-        },
-
-        .enum_decl,
-        .enum_decl_nonexhaustive,
-        => blk: {
-            const inst_data = datas[decl_inst].pl_node;
-            const extra = zir.extraData(Inst.EnumDecl, inst_data.payload_index);
-            break :blk .{
-                .extra_index = extra.end,
-                .decls_len = extra.data.decls_len,
-            };
-        },
-
-        .opaque_decl => blk: {
+    switch (tags[decl_inst]) {
+        .opaque_decl,
+        .opaque_decl_anon,
+        .opaque_decl_func,
+        => {
             const inst_data = datas[decl_inst].pl_node;
             const extra = zir.extraData(Inst.OpaqueDecl, inst_data.payload_index);
-            break :blk .{
-                .extra_index = extra.end,
-                .decls_len = extra.data.decls_len,
-            };
+            return declIteratorInner(zir, extra.end, extra.data.decls_len);
         },
 
         // Functions are allowed and yield no iterations.
+        // There is one case matching this in the extended instruction set below.
         .func,
         .func_inferred,
-        .extended, // assume also a function
-        => .{
-            .extra_index = 0,
-            .decls_len = 0,
-        },
+        => return declIteratorInner(zir, 0, 0),
 
+        .extended => {
+            const extended = datas[decl_inst].extended;
+            switch (extended.opcode) {
+                .func => return declIteratorInner(zir, 0, 0),
+                .struct_decl => {
+                    const small = @bitCast(Inst.StructDecl.Small, extended.small);
+                    var extra_index: usize = extended.operand;
+                    extra_index += @boolToInt(small.has_src_node);
+                    extra_index += @boolToInt(small.has_body_len);
+                    extra_index += @boolToInt(small.has_fields_len);
+                    const decls_len = if (small.has_decls_len) decls_len: {
+                        const decls_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :decls_len decls_len;
+                    } else 0;
+
+                    return declIteratorInner(zir, extra_index, decls_len);
+                },
+                .enum_decl => {
+                    const small = @bitCast(Inst.EnumDecl.Small, extended.small);
+                    var extra_index: usize = extended.operand;
+                    extra_index += @boolToInt(small.has_src_node);
+                    extra_index += @boolToInt(small.has_tag_type);
+                    extra_index += @boolToInt(small.has_body_len);
+                    extra_index += @boolToInt(small.has_fields_len);
+                    const decls_len = if (small.has_decls_len) decls_len: {
+                        const decls_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :decls_len decls_len;
+                    } else 0;
+
+                    return declIteratorInner(zir, extra_index, decls_len);
+                },
+                .union_decl => {
+                    const small = @bitCast(Inst.UnionDecl.Small, extended.small);
+                    var extra_index: usize = extended.operand;
+                    extra_index += @boolToInt(small.has_src_node);
+                    extra_index += @boolToInt(small.has_tag_type);
+                    extra_index += @boolToInt(small.has_body_len);
+                    extra_index += @boolToInt(small.has_fields_len);
+                    const decls_len = if (small.has_decls_len) decls_len: {
+                        const decls_len = zir.extra[extra_index];
+                        extra_index += 1;
+                        break :decls_len decls_len;
+                    } else 0;
+
+                    return declIteratorInner(zir, extra_index, decls_len);
+                },
+                else => unreachable,
+            }
+        },
         else => unreachable,
-    };
-
-    const bit_bags_count = std.math.divCeil(usize, decl_info.decls_len, 8) catch unreachable;
+    }
+}
 
+pub fn declIteratorInner(zir: Zir, extra_index: usize, decls_len: u32) DeclIterator {
+    const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable;
     return .{
         .zir = zir,
-        .extra_index = decl_info.extra_index + bit_bags_count,
-        .bit_bag_index = decl_info.extra_index,
+        .extra_index = extra_index + bit_bags_count,
+        .bit_bag_index = extra_index,
         .cur_bit_bag = undefined,
         .decl_i = 0,
-        .decls_len = decl_info.decls_len,
+        .decls_len = decls_len,
     };
 }
 
@@ -4480,15 +4611,10 @@ fn findDeclsInner(
 
     switch (tags[inst]) {
         // Decl instructions are interesting but have no body.
-        .struct_decl,
-        .struct_decl_packed,
-        .struct_decl_extern,
-        .union_decl,
-        .union_decl_packed,
-        .union_decl_extern,
-        .enum_decl,
-        .enum_decl_nonexhaustive,
+        // TODO yes they do have a body actually. recurse over them just like block instructions.
         .opaque_decl,
+        .opaque_decl_anon,
+        .opaque_decl_func,
         => return list.append(inst),
 
         // Functions instructions are interesting and have a body.
@@ -4505,19 +4631,28 @@ fn findDeclsInner(
         },
         .extended => {
             const extended = datas[inst].extended;
-            if (extended.opcode != .func) return;
+            switch (extended.opcode) {
+                .func => {
+                    try list.append(inst);
+
+                    const extra = zir.extraData(Inst.ExtendedFunc, extended.operand);
+                    const small = @bitCast(Inst.ExtendedFunc.Small, extended.small);
+                    var extra_index: usize = extra.end;
+                    extra_index += @boolToInt(small.has_lib_name);
+                    extra_index += @boolToInt(small.has_cc);
+                    extra_index += @boolToInt(small.has_align);
+                    extra_index += extra.data.param_types_len;
+                    const body = zir.extra[extra_index..][0..extra.data.body_len];
+                    return zir.findDeclsBody(list, body);
+                },
 
-            try list.append(inst);
+                .struct_decl,
+                .union_decl,
+                .enum_decl,
+                => return list.append(inst),
 
-            const extra = zir.extraData(Inst.ExtendedFunc, extended.operand);
-            const small = @bitCast(Inst.ExtendedFunc.Small, extended.small);
-            var extra_index: usize = extra.end;
-            extra_index += @boolToInt(small.has_lib_name);
-            extra_index += @boolToInt(small.has_cc);
-            extra_index += @boolToInt(small.has_align);
-            extra_index += extra.data.param_types_len;
-            const body = zir.extra[extra_index..][0..extra.data.body_len];
-            return zir.findDeclsBody(list, body);
+                else => return,
+            }
         },
 
         // Block instructions, recurse over the bodies.
BRANCH_TODO
@@ -65,3 +65,6 @@
  * use ZIR memory for decl names where possible and also for keys
    - this will require more sophisticated changelist detection which does some
      pre-emptive deletion of decls from the parent namespace
+
+ * better anonymous Decl naming convention
+   - avoid the global atomic integer for the number because of contention