Commit c0b5512544

Andrew Kelley <andrew@ziglang.org>
2023-09-25 04:22:25
compiler: start handling anonymous decls differently
Instead of explicitly creating a `Module.Decl` object for each anonymous declaration, each `InternPool.Index` value is implicitly understood to be an anonymous declaration when encountered by backend codegen. The memory management strategy for these anonymous decls then becomes to garbage collect them along with standard InternPool garbage. In the interest of a smooth transition, this commit only implements this new scheme for string literals and leaves all the previous mechanisms in place.
1 parent 4df7f7c
src/arch/wasm/CodeGen.zig
@@ -3075,6 +3075,7 @@ fn lowerParentPtr(func: *CodeGen, ptr_val: Value, offset: u32) InnerError!WValue
         .decl => |decl_index| {
             return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
         },
+        .anon_decl => @panic("TODO"),
         .mut_decl => |mut_decl| {
             const decl_index = mut_decl.decl;
             return func.lowerParentPtrDecl(ptr_val, decl_index, offset);
src/codegen/c.zig
@@ -604,6 +604,7 @@ pub const DeclGen = struct {
                 },
                 location,
             ),
+            .anon_decl => @panic("TODO"),
             .int => |int| {
                 try writer.writeByte('(');
                 try dg.renderCType(writer, ptr_cty);
@@ -1155,6 +1156,7 @@ pub const DeclGen = struct {
                         },
                         ptr_location,
                     ),
+                    .anon_decl => @panic("TODO"),
                     .int => |int| {
                         try writer.writeAll("((");
                         try dg.renderType(writer, ptr_ty);
src/codegen/llvm.zig
@@ -810,6 +810,8 @@ pub const Object = struct {
     /// * it works for functions not all globals.
     /// Therefore, this table keeps track of the mapping.
     decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, Builder.Global.Index),
+    /// Same deal as `decl_map` but for anonymous declarations, which are always global constants.
+    anon_decl_map: std.AutoHashMapUnmanaged(InternPool.Index, Builder.Global.Index),
     /// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction.
     named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, Builder.Function.Index),
     /// Maps Zig types to LLVM types. The table memory is backed by the GPA of
@@ -993,6 +995,7 @@ pub const Object = struct {
             .target_data = target_data,
             .target = options.target,
             .decl_map = .{},
+            .anon_decl_map = .{},
             .named_enum_map = .{},
             .type_map = .{},
             .di_type_map = .{},
@@ -1011,6 +1014,7 @@ pub const Object = struct {
             self.target_machine.dispose();
         }
         self.decl_map.deinit(gpa);
+        self.anon_decl_map.deinit(gpa);
         self.named_enum_map.deinit(gpa);
         self.type_map.deinit(gpa);
         self.extern_collisions.deinit(gpa);
@@ -3038,6 +3042,31 @@ pub const Object = struct {
         }
     }
 
+    fn resolveGlobalAnonDecl(
+        o: *Object,
+        decl_val: InternPool.Index,
+        llvm_addr_space: Builder.AddrSpace,
+    ) Error!Builder.Variable.Index {
+        const gop = try o.anon_decl_map.getOrPut(o.gpa, decl_val);
+        if (gop.found_existing) return gop.value_ptr.ptr(&o.builder).kind.variable;
+        errdefer assert(o.anon_decl_map.remove(decl_val));
+
+        const mod = o.module;
+        const decl_ty = mod.intern_pool.typeOf(decl_val);
+
+        const variable_index = try o.builder.addVariable(
+            try o.builder.fmt("__anon_{d}", .{@intFromEnum(decl_val)}),
+            try o.lowerType(decl_ty.toType()),
+            llvm_addr_space,
+        );
+        gop.value_ptr.* = variable_index.ptrConst(&o.builder).global;
+
+        try variable_index.setInitializer(try o.lowerValue(decl_val), &o.builder);
+        variable_index.setLinkage(.internal, &o.builder);
+        variable_index.setUnnamedAddr(.unnamed_addr, &o.builder);
+        return variable_index;
+    }
+
     fn resolveGlobalDecl(
         o: *Object,
         decl_index: Module.Decl.Index,
@@ -3764,6 +3793,7 @@ pub const Object = struct {
                 const ptr_val = switch (ptr.addr) {
                     .decl => |decl| try o.lowerDeclRefValue(ptr_ty, decl),
                     .mut_decl => |mut_decl| try o.lowerDeclRefValue(ptr_ty, mut_decl.decl),
+                    .anon_decl => |anon_decl| try o.lowerAnonDeclRef(ptr_ty, anon_decl),
                     .int => |int| try o.lowerIntAsPtr(int),
                     .eu_payload,
                     .opt_payload,
@@ -4216,10 +4246,12 @@ pub const Object = struct {
         return o.builder.bigIntConst(try o.builder.intType(ty.intInfo(mod).bits), bigint);
     }
 
-    const ParentPtr = struct {
-        ty: Type,
-        llvm_ptr: Builder.Value,
-    };
+    fn lowerParentPtrAnonDecl(o: *Object, decl_val: InternPool.Index) Error!Builder.Constant {
+        const mod = o.module;
+        const decl_ty = mod.intern_pool.typeOf(decl_val).toType();
+        const ptr_ty = try mod.singleMutPtrType(decl_ty);
+        return o.lowerAnonDeclRef(ptr_ty, decl_val);
+    }
 
     fn lowerParentPtrDecl(o: *Object, decl_index: Module.Decl.Index) Allocator.Error!Builder.Constant {
         const mod = o.module;
@@ -4229,13 +4261,14 @@ pub const Object = struct {
         return o.lowerDeclRefValue(ptr_ty, decl_index);
     }
 
-    fn lowerParentPtr(o: *Object, ptr_val: Value) Allocator.Error!Builder.Constant {
+    fn lowerParentPtr(o: *Object, ptr_val: Value) Error!Builder.Constant {
         const mod = o.module;
         const ip = &mod.intern_pool;
         const ptr = ip.indexToKey(ptr_val.toIntern()).ptr;
         return switch (ptr.addr) {
-            .decl => |decl| o.lowerParentPtrDecl(decl),
-            .mut_decl => |mut_decl| o.lowerParentPtrDecl(mut_decl.decl),
+            .decl => |decl| try o.lowerParentPtrDecl(decl),
+            .mut_decl => |mut_decl| try o.lowerParentPtrDecl(mut_decl.decl),
+            .anon_decl => |anon_decl| try o.lowerParentPtrAnonDecl(anon_decl),
             .int => |int| try o.lowerIntAsPtr(int),
             .eu_payload => |eu_ptr| {
                 const parent_ptr = try o.lowerParentPtr(eu_ptr.toValue());
@@ -4349,6 +4382,49 @@ pub const Object = struct {
         };
     }
 
+    /// This logic is very similar to `lowerDeclRefValue` but for anonymous declarations.
+    /// Maybe the logic could be unified.
+    fn lowerAnonDeclRef(
+        o: *Object,
+        ptr_ty: Type,
+        decl_val: InternPool.Index,
+    ) Error!Builder.Constant {
+        const mod = o.module;
+        const ip = &mod.intern_pool;
+        const decl_ty = ip.typeOf(decl_val).toType();
+        const target = mod.getTarget();
+
+        if (decl_val.toValue().getFunction(mod)) |func| {
+            _ = func;
+            @panic("TODO");
+        } else if (decl_val.toValue().getExternFunc(mod)) |func| {
+            _ = func;
+            @panic("TODO");
+        }
+
+        const is_fn_body = decl_ty.zigTypeTag(mod) == .Fn;
+        if ((!is_fn_body and !decl_ty.hasRuntimeBits(mod)) or
+            (is_fn_body and mod.typeToFunc(decl_ty).?.is_generic)) return o.lowerPtrToVoid(ptr_ty);
+
+        if (is_fn_body)
+            @panic("TODO");
+
+        const addr_space = target_util.defaultAddressSpace(target, .global_constant);
+        const llvm_addr_space = toLlvmAddressSpace(addr_space, target);
+        const llvm_global = (try o.resolveGlobalAnonDecl(decl_val, llvm_addr_space)).ptrConst(&o.builder).global;
+
+        const llvm_val = try o.builder.convConst(
+            .unneeded,
+            llvm_global.toConst(),
+            try o.builder.ptrType(llvm_addr_space),
+        );
+
+        return o.builder.convConst(if (ptr_ty.isAbiInt(mod)) switch (ptr_ty.intInfo(mod).signedness) {
+            .signed => .signed,
+            .unsigned => .unsigned,
+        } else .unneeded, llvm_val, try o.lowerType(ptr_ty));
+    }
+
     fn lowerDeclRefValue(o: *Object, ty: Type, decl_index: Module.Decl.Index) Allocator.Error!Builder.Constant {
         const mod = o.module;
 
src/codegen/spirv.zig
@@ -818,6 +818,7 @@ pub const DeclGen = struct {
         const mod = self.module;
         switch (mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr) {
             .decl => |decl| return try self.constructDeclRef(ptr_ty, decl),
+            .anon_decl => @panic("TODO"),
             .mut_decl => |decl_mut| return try self.constructDeclRef(ptr_ty, decl_mut.decl),
             .int => |int| {
                 const ptr_id = self.spv.allocId();
src/codegen.zig
@@ -655,6 +655,7 @@ fn lowerParentPtr(
             debug_output,
             reloc_info,
         ),
+        .anon_decl => @panic("TODO"),
         .int => |int| try generateSymbol(bin_file, src_loc, .{
             .ty = Type.usize,
             .val = int.toValue(),
src/InternPool.zig
@@ -1074,6 +1074,7 @@ pub const Key = union(enum) {
 
             decl: Module.Decl.Index,
             mut_decl: MutDecl,
+            anon_decl: Index,
             comptime_field: Index,
             int: Index,
             eu_payload: Index,
@@ -1230,10 +1231,12 @@ pub const Key = union(enum) {
                         asBytes(&x.decl) ++ asBytes(&x.runtime_index),
                     ),
 
-                    .int, .eu_payload, .opt_payload, .comptime_field => |int| Hash.hash(
-                        seed2,
-                        asBytes(&int),
-                    ),
+                    .anon_decl,
+                    .int,
+                    .eu_payload,
+                    .opt_payload,
+                    .comptime_field,
+                    => |int| Hash.hash(seed2, asBytes(&int)),
 
                     .elem, .field => |x| Hash.hash(
                         seed2,
@@ -1497,6 +1500,7 @@ pub const Key = union(enum) {
                 return switch (a_info.addr) {
                     .decl => |a_decl| a_decl == b_info.addr.decl,
                     .mut_decl => |a_mut_decl| std.meta.eql(a_mut_decl, b_info.addr.mut_decl),
+                    .anon_decl => |a_decl| a_decl == b_info.addr.anon_decl,
                     .int => |a_int| a_int == b_info.addr.int,
                     .eu_payload => |a_eu_payload| a_eu_payload == b_info.addr.eu_payload,
                     .opt_payload => |a_opt_payload| a_opt_payload == b_info.addr.opt_payload,
@@ -2123,6 +2127,7 @@ pub const Index = enum(u32) {
         simple_value: struct { data: SimpleValue },
         ptr_decl: struct { data: *PtrDecl },
         ptr_mut_decl: struct { data: *PtrMutDecl },
+        ptr_anon_decl: struct { data: *PtrAnonDecl },
         ptr_comptime_field: struct { data: *PtrComptimeField },
         ptr_int: struct { data: *PtrBase },
         ptr_eu_payload: struct { data: *PtrBase },
@@ -2572,6 +2577,9 @@ pub const Tag = enum(u8) {
     /// A pointer to a decl that can be mutated at comptime.
     /// data is extra index of `PtrMutDecl`, which contains the type and address.
     ptr_mut_decl,
+    /// A pointer to an anonymous decl.
+    /// data is extra index of `PtrAnonDecl`, which contains the type and decl value.
+    ptr_anon_decl,
     /// data is extra index of `PtrComptimeField`, which contains the pointer type and field value.
     ptr_comptime_field,
     /// A pointer with an integer value.
@@ -2767,6 +2775,7 @@ pub const Tag = enum(u8) {
             .simple_value => unreachable,
             .ptr_decl => PtrDecl,
             .ptr_mut_decl => PtrMutDecl,
+            .ptr_anon_decl => PtrAnonDecl,
             .ptr_comptime_field => PtrComptimeField,
             .ptr_int => PtrBase,
             .ptr_eu_payload => PtrBase,
@@ -3364,6 +3373,11 @@ pub const PtrDecl = struct {
     decl: Module.Decl.Index,
 };
 
+pub const PtrAnonDecl = struct {
+    ty: Index,
+    val: Index,
+};
+
 pub const PtrMutDecl = struct {
     ty: Index,
     decl: Module.Decl.Index,
@@ -3713,6 +3727,13 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
                 } },
             } };
         },
+        .ptr_anon_decl => {
+            const info = ip.extraData(PtrAnonDecl, data);
+            return .{ .ptr = .{
+                .ty = info.ty,
+                .addr = .{ .anon_decl = info.val },
+            } };
+        },
         .ptr_comptime_field => {
             const info = ip.extraData(PtrComptimeField, data);
             return .{ .ptr = .{
@@ -3790,6 +3811,9 @@ pub fn indexToKey(ip: *const InternPool, index: Index) Key {
                                 .runtime_index = sub_info.runtime_index,
                             } };
                         },
+                        .ptr_anon_decl => .{
+                            .anon_decl = ip.extraData(PtrAnonDecl, ptr_item.data).val,
+                        },
                         .ptr_comptime_field => .{
                             .comptime_field = ip.extraData(PtrComptimeField, ptr_item.data).field_val,
                         },
@@ -4542,6 +4566,13 @@ pub fn get(ip: *InternPool, gpa: Allocator, key: Key) Allocator.Error!Index {
                                 .runtime_index = mut_decl.runtime_index,
                             }),
                         }),
+                        .anon_decl => |anon_decl| ip.items.appendAssumeCapacity(.{
+                            .tag = .ptr_anon_decl,
+                            .data = try ip.addExtra(gpa, PtrAnonDecl{
+                                .ty = ptr.ty,
+                                .val = anon_decl,
+                            }),
+                        }),
                         .comptime_field => |field_val| {
                             assert(field_val != .none);
                             ip.items.appendAssumeCapacity(.{
@@ -7147,6 +7178,7 @@ fn dumpStatsFallible(ip: *const InternPool, arena: Allocator) anyerror!void {
             .simple_value => 0,
             .ptr_decl => @sizeOf(PtrDecl),
             .ptr_mut_decl => @sizeOf(PtrMutDecl),
+            .ptr_anon_decl => @sizeOf(PtrAnonDecl),
             .ptr_comptime_field => @sizeOf(PtrComptimeField),
             .ptr_int => @sizeOf(PtrBase),
             .ptr_eu_payload => @sizeOf(PtrBase),
@@ -7276,6 +7308,7 @@ fn dumpAllFallible(ip: *const InternPool) anyerror!void {
             .runtime_value,
             .ptr_decl,
             .ptr_mut_decl,
+            .ptr_anon_decl,
             .ptr_comptime_field,
             .ptr_int,
             .ptr_eu_payload,
@@ -7656,6 +7689,7 @@ pub fn typeOf(ip: *const InternPool, index: Index) Index {
 
             inline .ptr_decl,
             .ptr_mut_decl,
+            .ptr_anon_decl,
             .ptr_comptime_field,
             .ptr_int,
             .ptr_eu_payload,
@@ -7816,6 +7850,7 @@ pub fn getBackingAddrTag(ip: *const InternPool, val: Index) ?Key.Ptr.Addr.Tag {
         switch (ip.items.items(.tag)[base]) {
             .ptr_decl => return .decl,
             .ptr_mut_decl => return .mut_decl,
+            .ptr_anon_decl => return .anon_decl,
             .ptr_comptime_field => return .comptime_field,
             .ptr_int => return .int,
             inline .ptr_eu_payload,
@@ -7991,6 +8026,7 @@ pub fn zigTypeTagOrPoison(ip: *const InternPool, index: Index) error{GenericPois
             .simple_value,
             .ptr_decl,
             .ptr_mut_decl,
+            .ptr_anon_decl,
             .ptr_comptime_field,
             .ptr_int,
             .ptr_eu_payload,
src/Module.zig
@@ -109,9 +109,6 @@ comptime_capture_scopes: std.AutoArrayHashMapUnmanaged(CaptureScope.Key, InternP
 /// This memory lives until the Module is destroyed.
 tmp_hack_arena: std.heap.ArenaAllocator,
 
-/// This is currently only used for string literals.
-memoized_decls: std.AutoHashMapUnmanaged(InternPool.Index, Decl.Index) = .{},
-
 /// We optimize memory usage for a compilation with no compile errors by storing the
 /// error messages and mapping outside of `Decl`.
 /// The ErrorMsg memory is owned by the decl, using Module's general purpose allocator.
@@ -2627,7 +2624,6 @@ pub fn deinit(mod: *Module) void {
     mod.global_assembly.deinit(gpa);
     mod.reference_table.deinit(gpa);
 
-    mod.memoized_decls.deinit(gpa);
     mod.intern_pool.deinit(gpa);
     mod.tmp_hack_arena.deinit();
 
@@ -5814,6 +5810,7 @@ pub fn markReferencedDeclsAlive(mod: *Module, val: Value) Allocator.Error!void {
         .ptr => |ptr| {
             switch (ptr.addr) {
                 .decl => |decl| try mod.markDeclIndexAlive(decl),
+                .anon_decl => {},
                 .mut_decl => |mut_decl| try mod.markDeclIndexAlive(mut_decl.decl),
                 .int, .comptime_field => {},
                 .eu_payload, .opt_payload => |parent| try mod.markReferencedDeclsAlive(parent.toValue()),
src/Sema.zig
@@ -1091,7 +1091,7 @@ fn analyzeBodyInner(
             .slice_sentinel               => try sema.zirSliceSentinel(block, inst),
             .slice_start                  => try sema.zirSliceStart(block, inst),
             .slice_length                 => try sema.zirSliceLength(block, inst),
-            .str                          => try sema.zirStr(block, inst),
+            .str                          => try sema.zirStr(inst),
             .switch_block                 => try sema.zirSwitchBlock(block, inst, false),
             .switch_block_ref             => try sema.zirSwitchBlock(block, inst, true),
             .type_info                    => try sema.zirTypeInfo(block, inst),
@@ -2185,7 +2185,7 @@ fn resolveMaybeUndefValIntable(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Va
     if (val.ip_index == .none) return val;
     if (sema.mod.intern_pool.isVariable(val.toIntern())) return null;
     if (sema.mod.intern_pool.getBackingAddrTag(val.toIntern())) |addr| switch (addr) {
-        .decl, .mut_decl, .comptime_field => return null,
+        .decl, .anon_decl, .mut_decl, .comptime_field => return null,
         .int => {},
         .eu_payload, .opt_payload, .elem, .field => unreachable,
     };
@@ -5501,38 +5501,40 @@ fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!v
     return sema.storePtr2(block, src, ptr, ptr_src, operand, operand_src, air_tag);
 }
 
-fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
-    const tracy = trace(@src());
-    defer tracy.end();
-
+fn zirStr(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const bytes = sema.code.instructions.items(.data)[inst].str.get(sema.code);
-    return sema.addStrLit(block, bytes);
+    return sema.addStrLitNoAlias(bytes);
 }
 
-fn addStrLit(sema: *Sema, block: *Block, bytes: []const u8) CompileError!Air.Inst.Ref {
-    const mod = sema.mod;
-    const gpa = sema.gpa;
-    // TODO: write something like getCoercedInts to avoid needing to dupe
+fn addStrLit(sema: *Sema, bytes: []const u8) CompileError!Air.Inst.Ref {
     const duped_bytes = try sema.arena.dupe(u8, bytes);
-    const ty = try mod.arrayType(.{
+    return addStrLitNoAlias(sema, duped_bytes);
+}
+
+/// Safe to call when `bytes` does not point into `InternPool`.
+fn addStrLitNoAlias(sema: *Sema, bytes: []const u8) CompileError!Air.Inst.Ref {
+    const mod = sema.mod;
+    const array_ty = try mod.arrayType(.{
         .len = bytes.len,
         .sentinel = .zero_u8,
         .child = .u8_type,
     });
     const val = try mod.intern(.{ .aggregate = .{
-        .ty = ty.toIntern(),
-        .storage = .{ .bytes = duped_bytes },
+        .ty = array_ty.toIntern(),
+        .storage = .{ .bytes = bytes },
     } });
-    const gop = try mod.memoized_decls.getOrPut(gpa, val);
-    if (!gop.found_existing) {
-        const new_decl_index = try mod.createAnonymousDecl(block, .{
-            .ty = ty,
-            .val = val.toValue(),
-        });
-        gop.value_ptr.* = new_decl_index;
-        try mod.finalizeAnonDecl(new_decl_index);
-    }
-    return sema.analyzeDeclRef(gop.value_ptr.*);
+    const ptr_ty = try sema.ptrType(.{
+        .child = array_ty.toIntern(),
+        .flags = .{
+            .alignment = .none,
+            .is_const = true,
+            .address_space = .generic,
+        },
+    });
+    return Air.internedToRef((try mod.intern(.{ .ptr = .{
+        .ty = ptr_ty.toIntern(),
+        .addr = .{ .anon_decl = val },
+    } })));
 }
 
 fn zirInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
@@ -12907,7 +12909,7 @@ fn maybeErrorUnwrap(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, op
                 try sema.zirSaveErrRetIndex(block, inst);
                 continue;
             },
-            .str => try sema.zirStr(block, inst),
+            .str => try sema.zirStr(inst),
             .as_node => try sema.zirAsNode(block, inst),
             .field_val => try sema.zirFieldVal(block, inst),
             .@"unreachable" => {
@@ -20170,7 +20172,7 @@ fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 
     if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
         const err_name = sema.mod.intern_pool.indexToKey(val.toIntern()).err.name;
-        return sema.addStrLit(block, sema.mod.intern_pool.stringToSlice(err_name));
+        return sema.addStrLit(sema.mod.intern_pool.stringToSlice(err_name));
     }
 
     // Similar to zirTagName, we have special AIR instruction for the error name in case an optimimzation pass
@@ -20288,7 +20290,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
         .EnumLiteral => {
             const val = try sema.resolveConstValue(block, .unneeded, operand, undefined);
             const tag_name = ip.indexToKey(val.toIntern()).enum_literal;
-            return sema.addStrLit(block, ip.stringToSlice(tag_name));
+            return sema.addStrLit(ip.stringToSlice(tag_name));
         },
         .Enum => operand_ty,
         .Union => operand_ty.unionTagType(mod) orelse {
@@ -20330,7 +20332,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
         };
         // TODO: write something like getCoercedInts to avoid needing to dupe
         const field_name = enum_ty.enumFieldName(field_index, mod);
-        return sema.addStrLit(block, ip.stringToSlice(field_name));
+        return sema.addStrLit(ip.stringToSlice(field_name));
     }
     try sema.requireRuntimeBlock(block, src, operand_src);
     if (block.wantSafety() and sema.mod.backendSupportsFeature(.is_named_enum_value)) {
@@ -29859,7 +29861,7 @@ fn beginComptimePtrMutation(
     const mod = sema.mod;
     const ptr = mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr;
     switch (ptr.addr) {
-        .decl, .int => unreachable, // isComptimeMutablePtr has been checked already
+        .decl, .anon_decl, .int => unreachable, // isComptimeMutablePtr has been checked already
         .mut_decl => |mut_decl| {
             const decl = mod.declPtr(mut_decl.decl);
             return sema.beginComptimePtrMutationInner(block, src, decl.ty, &decl.val, ptr_elem_ty, mut_decl);
@@ -30455,9 +30457,10 @@ fn beginComptimePtrLoad(
     maybe_array_ty: ?Type,
 ) ComptimePtrLoadError!ComptimePtrLoadKit {
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const target = mod.getTarget();
 
-    var deref: ComptimePtrLoadKit = switch (mod.intern_pool.indexToKey(ptr_val.toIntern())) {
+    var deref: ComptimePtrLoadKit = switch (ip.indexToKey(ptr_val.toIntern())) {
         .ptr => |ptr| switch (ptr.addr) {
             .decl, .mut_decl => blk: {
                 const decl_index = switch (ptr.addr) {
@@ -30478,9 +30481,21 @@ fn beginComptimePtrLoad(
                     .ty_without_well_defined_layout = if (!layout_defined) decl.ty else null,
                 };
             },
+            .anon_decl => |decl_val| blk: {
+                if (decl_val.toValue().getVariable(mod) != null) return error.RuntimeLoad;
+                const decl_ty = ip.typeOf(decl_val).toType();
+                const decl_tv: TypedValue = .{ .ty = decl_ty, .val = decl_val.toValue() };
+                const layout_defined = decl_ty.hasWellDefinedLayout(mod);
+                break :blk ComptimePtrLoadKit{
+                    .parent = if (layout_defined) .{ .tv = decl_tv, .byte_offset = 0 } else null,
+                    .pointee = decl_tv,
+                    .is_mutable = false,
+                    .ty_without_well_defined_layout = if (!layout_defined) decl_ty else null,
+                };
+            },
             .int => return error.RuntimeLoad,
             .eu_payload, .opt_payload => |container_ptr| blk: {
-                const container_ty = mod.intern_pool.typeOf(container_ptr).toType().childType(mod);
+                const container_ty = ip.typeOf(container_ptr).toType().childType(mod);
                 const payload_ty = switch (ptr.addr) {
                     .eu_payload => container_ty.errorUnionPayload(mod),
                     .opt_payload => container_ty.optionalChild(mod),
@@ -30502,13 +30517,13 @@ fn beginComptimePtrLoad(
                         const payload_val = switch (tv.val.ip_index) {
                             .none => tv.val.cast(Value.Payload.SubValue).?.data,
                             .null_value => return sema.fail(block, src, "attempt to use null value", .{}),
-                            else => switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
+                            else => switch (ip.indexToKey(tv.val.toIntern())) {
                                 .error_union => |error_union| switch (error_union.val) {
                                     .err_name => |err_name| return sema.fail(
                                         block,
                                         src,
                                         "attempt to unwrap error: {}",
-                                        .{err_name.fmt(&mod.intern_pool)},
+                                        .{err_name.fmt(ip)},
                                     ),
                                     .payload => |payload| payload,
                                 },
@@ -30527,7 +30542,7 @@ fn beginComptimePtrLoad(
                 break :blk deref;
             },
             .comptime_field => |comptime_field| blk: {
-                const field_ty = mod.intern_pool.typeOf(comptime_field).toType();
+                const field_ty = ip.typeOf(comptime_field).toType();
                 break :blk ComptimePtrLoadKit{
                     .parent = null,
                     .pointee = .{ .ty = field_ty, .val = comptime_field.toValue() },
@@ -30536,15 +30551,15 @@ fn beginComptimePtrLoad(
                 };
             },
             .elem => |elem_ptr| blk: {
-                const elem_ty = mod.intern_pool.typeOf(elem_ptr.base).toType().elemType2(mod);
+                const elem_ty = ip.typeOf(elem_ptr.base).toType().elemType2(mod);
                 var deref = try sema.beginComptimePtrLoad(block, src, elem_ptr.base.toValue(), null);
 
                 // This code assumes that elem_ptrs have been "flattened" in order for direct dereference
                 // to succeed, meaning that elem ptrs of the same elem_ty are coalesced. Here we check that
                 // our parent is not an elem_ptr with the same elem_ty, since that would be "unflattened"
-                switch (mod.intern_pool.indexToKey(elem_ptr.base)) {
+                switch (ip.indexToKey(elem_ptr.base)) {
                     .ptr => |base_ptr| switch (base_ptr.addr) {
-                        .elem => |base_elem| assert(!mod.intern_pool.typeOf(base_elem.base).toType().elemType2(mod).eql(elem_ty, mod)),
+                        .elem => |base_elem| assert(!ip.typeOf(base_elem.base).toType().elemType2(mod).eql(elem_ty, mod)),
                         else => {},
                     },
                     else => {},
@@ -30616,7 +30631,7 @@ fn beginComptimePtrLoad(
             },
             .field => |field_ptr| blk: {
                 const field_index: u32 = @intCast(field_ptr.index);
-                const container_ty = mod.intern_pool.typeOf(field_ptr.base).toType().childType(mod);
+                const container_ty = ip.typeOf(field_ptr.base).toType().childType(mod);
                 var deref = try sema.beginComptimePtrLoad(block, src, field_ptr.base.toValue(), container_ty);
 
                 if (container_ty.hasWellDefinedLayout(mod)) {
@@ -30655,7 +30670,7 @@ fn beginComptimePtrLoad(
                         },
                         Value.slice_len_index => TypedValue{
                             .ty = Type.usize,
-                            .val = mod.intern_pool.indexToKey(try tv.val.intern(tv.ty, mod)).ptr.len.toValue(),
+                            .val = ip.indexToKey(try tv.val.intern(tv.ty, mod)).ptr.len.toValue(),
                         },
                         else => unreachable,
                     };
@@ -34529,7 +34544,7 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value {
                 else => (try sema.resolveLazyValue(ptr.len.toValue())).toIntern(),
             };
             switch (ptr.addr) {
-                .decl, .mut_decl => return if (resolved_len == ptr.len)
+                .decl, .mut_decl, .anon_decl => return if (resolved_len == ptr.len)
                     val
                 else
                     (try mod.intern(.{ .ptr = .{
@@ -34537,6 +34552,7 @@ fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value {
                         .addr = switch (ptr.addr) {
                             .decl => |decl| .{ .decl = decl },
                             .mut_decl => |mut_decl| .{ .mut_decl = mut_decl },
+                            .anon_decl => |anon_decl| .{ .anon_decl = anon_decl },
                             else => unreachable,
                         },
                         .len = resolved_len,
@@ -36568,6 +36584,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
             .runtime_value,
             .simple_value,
             .ptr_decl,
+            .ptr_anon_decl,
             .ptr_mut_decl,
             .ptr_comptime_field,
             .ptr_int,
src/TypedValue.zig
@@ -321,6 +321,15 @@ pub fn print(
                             .val = decl.val,
                         }, writer, level - 1, mod);
                     },
+                    .anon_decl => |decl_val| {
+                        if (level == 0) return writer.print("(anon decl '{d}')", .{
+                            @intFromEnum(decl_val),
+                        });
+                        return print(.{
+                            .ty = ip.typeOf(decl_val).toType(),
+                            .val = decl_val.toValue(),
+                        }, writer, level - 1, mod);
+                    },
                     .mut_decl => |mut_decl| {
                         const decl = mod.declPtr(mut_decl.decl);
                         if (level == 0) return writer.print("(mut decl '{}')", .{decl.name.fmt(ip)});
src/value.zig
@@ -1565,12 +1565,14 @@ pub const Value = struct {
     }
 
     pub fn sliceLen(val: Value, mod: *Module) u64 {
-        const ptr = mod.intern_pool.indexToKey(val.toIntern()).ptr;
+        const ip = &mod.intern_pool;
+        const ptr = ip.indexToKey(val.toIntern()).ptr;
         return switch (ptr.len) {
-            .none => switch (mod.intern_pool.indexToKey(switch (ptr.addr) {
+            .none => switch (ip.indexToKey(switch (ptr.addr) {
                 .decl => |decl| mod.declPtr(decl).ty.toIntern(),
                 .mut_decl => |mut_decl| mod.declPtr(mut_decl.decl).ty.toIntern(),
-                .comptime_field => |comptime_field| mod.intern_pool.typeOf(comptime_field),
+                .anon_decl => |anon_decl| ip.typeOf(anon_decl),
+                .comptime_field => |comptime_field| ip.typeOf(comptime_field),
                 else => unreachable,
             })) {
                 .array_type => |array_type| array_type.len,
@@ -1602,6 +1604,7 @@ pub const Value = struct {
                 })).toValue(),
                 .ptr => |ptr| switch (ptr.addr) {
                     .decl => |decl| mod.declPtr(decl).val.maybeElemValue(mod, index),
+                    .anon_decl => |anon_decl| anon_decl.toValue().maybeElemValue(mod, index),
                     .mut_decl => |mut_decl| (try mod.declPtr(mut_decl.decl).internValue(mod))
                         .toValue().maybeElemValue(mod, index),
                     .int, .eu_payload => null,