Commit 69b7b91092

Andrew Kelley <andrew@ziglang.org>
2023-06-02 08:46:04
compiler: eliminate Decl.value_arena and Sema.perm_arena
The main motivation for this commit is eliminating Decl.value_arena. Everything else is dominoes. Decl.name used to be stored in the GPA, now it is stored in InternPool. It ended up being simpler to migrate other strings to be interned as well, such as struct field names, union field names, and a few others. This ended up requiring a big diff, sorry about that. But the changes are pretty nice, we finally start to take advantage of InternPool's existence. global_error_set and error_name_list are simplified. Now it is a single ArrayHashMap(NullTerminatedString, void) and the index is the error tag value. Module.tmp_hack_arena is re-introduced (it was removed in eeff407941560ce8eb5b737b2436dfa93cfd3a0c) in order to deal with comptime_args, optimized_order, and struct and union fields. After structs and unions get moved into InternPool properly, tmp_hack_arena can be deleted again.
1 parent 34dae73
src/arch/aarch64/CodeGen.zig
@@ -4350,7 +4350,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
                 .data = .{ .reg = .x30 },
             });
         } else if (func_value.getExternFunc(mod)) |extern_func| {
-            const decl_name = mem.sliceTo(mod.declPtr(extern_func.decl).name, 0);
+            const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name);
             const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name);
             if (self.bin_file.cast(link.File.MachO)) |macho_file| {
                 const sym_index = try macho_file.getGlobalSymbol(decl_name, lib_name);
src/arch/sparc64/CodeGen.zig
@@ -276,8 +276,6 @@ pub fn generate(
     assert(fn_owner_decl.has_tv);
     const fn_type = fn_owner_decl.ty;
 
-    log.debug("fn {s}", .{fn_owner_decl.name});
-
     var branch_stack = std.ArrayList(Branch).init(bin_file.allocator);
     defer {
         assert(branch_stack.items.len == 1);
src/arch/wasm/CodeGen.zig
@@ -2208,7 +2208,7 @@ fn airCall(func: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif
             const atom = func.bin_file.getAtomPtr(atom_index);
             const type_index = try func.bin_file.storeDeclType(extern_func.decl, func_type);
             try func.bin_file.addOrUpdateImport(
-                mem.sliceTo(ext_decl.name, 0),
+                mod.intern_pool.stringToSlice(ext_decl.name),
                 atom.getSymbolIndex().?,
                 mod.intern_pool.stringToSliceUnwrap(ext_decl.getOwnedExternFunc(mod).?.lib_name),
                 type_index,
@@ -3180,9 +3180,8 @@ fn lowerConstant(func: *CodeGen, arg_val: Value, ty: Type) InnerError!WValue {
             }
         },
         .err => |err| {
-            const name = mod.intern_pool.stringToSlice(err.name);
-            const kv = try mod.getErrorValue(name);
-            return WValue{ .imm32 = kv.value };
+            const int = try mod.getErrorValue(err.name);
+            return WValue{ .imm32 = int };
         },
         .error_union => |error_union| {
             const err_tv: TypedValue = switch (error_union.val) {
@@ -3320,18 +3319,15 @@ fn valueAsI32(func: *const CodeGen, val: Value, ty: Type) i32 {
             .enum_tag => |enum_tag| intIndexAsI32(&mod.intern_pool, enum_tag.int, mod),
             .int => |int| intStorageAsI32(int.storage, mod),
             .ptr => |ptr| intIndexAsI32(&mod.intern_pool, ptr.addr.int, mod),
-            .err => |err| @bitCast(i32, mod.global_error_set.get(mod.intern_pool.stringToSlice(err.name)).?),
+            .err => |err| @bitCast(i32, @intCast(Module.ErrorInt, mod.global_error_set.getIndex(err.name).?)),
             else => unreachable,
         },
     }
 
-    switch (ty.zigTypeTag(mod)) {
-        .ErrorSet => {
-            const kv = func.bin_file.base.options.module.?.getErrorValue(val.getError(mod).?) catch unreachable; // passed invalid `Value` to function
-            return @bitCast(i32, kv.value);
-        },
+    return switch (ty.zigTypeTag(mod)) {
+        .ErrorSet => @bitCast(i32, val.getErrorInt(mod)),
         else => unreachable, // Programmer called this function for an illegal type
-    }
+    };
 }
 
 fn intIndexAsI32(ip: *const InternPool, int: InternPool.Index, mod: *Module) i32 {
@@ -6874,8 +6870,7 @@ fn getTagNameFunction(func: *CodeGen, enum_ty: Type) InnerError!u32 {
     defer arena_allocator.deinit();
     const arena = arena_allocator.allocator();
 
-    const fqn = try mod.declPtr(enum_decl_index).getFullyQualifiedName(mod);
-    defer mod.gpa.free(fqn);
+    const fqn = mod.intern_pool.stringToSlice(try mod.declPtr(enum_decl_index).getFullyQualifiedName(mod));
     const func_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn});
 
     // check if we already generated code for this.
@@ -7037,9 +7032,8 @@ fn airErrorSetHasValue(func: *CodeGen, inst: Air.Inst.Index) InnerError!void {
 
     var lowest: ?u32 = null;
     var highest: ?u32 = null;
-    for (names) |name_ip| {
-        const name = mod.intern_pool.stringToSlice(name_ip);
-        const err_int = mod.global_error_set.get(name).?;
+    for (names) |name| {
+        const err_int = @intCast(Module.ErrorInt, mod.global_error_set.getIndex(name).?);
         if (lowest) |*l| {
             if (err_int < l.*) {
                 l.* = err_int;
src/arch/x86_64/CodeGen.zig
@@ -8132,7 +8132,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
                 }));
             } else unreachable;
         } else if (func_value.getExternFunc(mod)) |extern_func| {
-            const decl_name = mem.sliceTo(mod.declPtr(extern_func.decl).name, 0);
+            const decl_name = mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name);
             const lib_name = mod.intern_pool.stringToSliceUnwrap(extern_func.lib_name);
             if (self.bin_file.cast(link.File.Coff)) |coff_file| {
                 const atom_index = try self.owner.getSymbolIndex(self);
src/codegen/c/type.zig
@@ -1953,11 +1953,11 @@ pub const CType = extern union {
                             .name = try if (ty.isSimpleTuple(mod))
                                 std.fmt.allocPrintZ(arena, "f{}", .{field_i})
                             else
-                                arena.dupeZ(u8, switch (zig_ty_tag) {
+                                arena.dupeZ(u8, mod.intern_pool.stringToSlice(switch (zig_ty_tag) {
                                     .Struct => ty.structFieldName(field_i, mod),
                                     .Union => ty.unionFields(mod).keys()[field_i],
                                     else => unreachable,
-                                }),
+                                })),
                             .type = store.set.typeToIndex(field_ty, mod, switch (kind) {
                                 .forward, .forward_parameter => .forward,
                                 .complete, .parameter, .payload => .complete,
@@ -2102,12 +2102,13 @@ pub const CType = extern union {
                                 }) or !mem.eql(
                                     u8,
                                     if (ty.isSimpleTuple(mod))
-                                        std.fmt.bufPrint(&name_buf, "f{}", .{field_i}) catch unreachable
-                                    else switch (zig_ty_tag) {
-                                        .Struct => ty.structFieldName(field_i, mod),
-                                        .Union => ty.unionFields(mod).keys()[field_i],
-                                        else => unreachable,
-                                    },
+                                        std.fmt.bufPrintZ(&name_buf, "f{}", .{field_i}) catch unreachable
+                                    else
+                                        mod.intern_pool.stringToSlice(switch (zig_ty_tag) {
+                                            .Struct => ty.structFieldName(field_i, mod),
+                                            .Union => ty.unionFields(mod).keys()[field_i],
+                                            else => unreachable,
+                                        }),
                                     mem.span(c_field.name),
                                 ) or AlignAs.fieldAlign(ty, field_i, mod).@"align" !=
                                     c_field.alignas.@"align") return false;
@@ -2225,11 +2226,12 @@ pub const CType = extern union {
                                 });
                                 hasher.update(if (ty.isSimpleTuple(mod))
                                     std.fmt.bufPrint(&name_buf, "f{}", .{field_i}) catch unreachable
-                                else switch (zig_ty_tag) {
-                                    .Struct => ty.structFieldName(field_i, mod),
-                                    .Union => ty.unionFields(mod).keys()[field_i],
-                                    else => unreachable,
-                                });
+                                else
+                                    mod.intern_pool.stringToSlice(switch (zig_ty_tag) {
+                                        .Struct => ty.structFieldName(field_i, mod),
+                                        .Union => ty.unionFields(mod).keys()[field_i],
+                                        else => unreachable,
+                                    }));
                                 autoHash(hasher, AlignAs.fieldAlign(ty, field_i, mod).@"align");
                             }
                         },
src/codegen/c.zig
@@ -452,6 +452,7 @@ pub const Function = struct {
             var promoted = f.object.dg.ctypes.promote(gpa);
             defer f.object.dg.ctypes.demote(promoted);
             const arena = promoted.arena.allocator();
+            const mod = f.object.dg.module;
 
             gop.value_ptr.* = .{
                 .fn_name = switch (key) {
@@ -460,7 +461,7 @@ pub const Function = struct {
                     .never_inline,
                     => |owner_decl| try std.fmt.allocPrint(arena, "zig_{s}_{}__{d}", .{
                         @tagName(key),
-                        fmtIdent(mem.span(f.object.dg.module.declPtr(owner_decl).name)),
+                        fmtIdent(mod.intern_pool.stringToSlice(mod.declPtr(owner_decl).name)),
                         @enumToInt(owner_decl),
                     }),
                 },
@@ -1465,7 +1466,7 @@ pub const DeclGen = struct {
                     try writer.writeAll(" .payload = {");
                 }
                 if (field_ty.hasRuntimeBits(mod)) {
-                    try writer.print(" .{ } = ", .{fmtIdent(field_name)});
+                    try writer.print(" .{ } = ", .{fmtIdent(mod.intern_pool.stringToSlice(field_name))});
                     try dg.renderValue(writer, field_ty, un.val.toValue(), initializer_type);
                     try writer.writeByte(' ');
                 } else for (ty.unionFields(mod).values()) |field| {
@@ -1849,9 +1850,9 @@ pub const DeclGen = struct {
         try mod.markDeclAlive(decl);
 
         if (mod.decl_exports.get(decl_index)) |exports| {
-            try writer.writeAll(exports.items[export_index].options.name);
+            try writer.writeAll(mod.intern_pool.stringToSlice(exports.items[export_index].name));
         } else if (decl.isExtern(mod)) {
-            try writer.writeAll(mem.span(decl.name));
+            try writer.writeAll(mod.intern_pool.stringToSlice(decl.name));
         } else {
             // MSVC has a limit of 4095 character token length limit, and fmtIdent can (worst case),
             // expand to 3x the length of its input, but let's cut it off at a much shorter limit.
@@ -1987,7 +1988,7 @@ fn renderTypeName(
             try w.print("{s} {s}{}__{d}", .{
                 @tagName(tag)["fwd_".len..],
                 attributes,
-                fmtIdent(mem.span(mod.declPtr(owner_decl).name)),
+                fmtIdent(mod.intern_pool.stringToSlice(mod.declPtr(owner_decl).name)),
                 @enumToInt(owner_decl),
             });
         },
@@ -2406,11 +2407,12 @@ pub fn genErrDecls(o: *Object) !void {
     try writer.writeAll("enum {\n");
     o.indent_writer.pushIndent();
     var max_name_len: usize = 0;
-    for (mod.error_name_list.items[1..], 1..) |name, value| {
-        max_name_len = std.math.max(name.len, max_name_len);
+    for (mod.global_error_set.keys()[1..], 1..) |name_nts, value| {
+        const name = mod.intern_pool.stringToSlice(name_nts);
+        max_name_len = @max(name.len, max_name_len);
         const err_val = try mod.intern(.{ .err = .{
             .ty = .anyerror_type,
-            .name = mod.intern_pool.getString(name).unwrap().?,
+            .name = name_nts,
         } });
         try o.dg.renderValue(writer, Type.anyerror, err_val.toValue(), .Other);
         try writer.print(" = {d}u,\n", .{value});
@@ -2424,7 +2426,8 @@ pub fn genErrDecls(o: *Object) !void {
     defer o.dg.gpa.free(name_buf);
 
     @memcpy(name_buf[0..name_prefix.len], name_prefix);
-    for (mod.error_name_list.items) |name| {
+    for (mod.global_error_set.keys()) |name_nts| {
+        const name = mod.intern_pool.stringToSlice(name_nts);
         @memcpy(name_buf[name_prefix.len..][0..name.len], name);
         const identifier = name_buf[0 .. name_prefix.len + name.len];
 
@@ -2446,14 +2449,15 @@ pub fn genErrDecls(o: *Object) !void {
     }
 
     const name_array_ty = try mod.arrayType(.{
-        .len = mod.error_name_list.items.len,
+        .len = mod.global_error_set.count(),
         .child = .slice_const_u8_sentinel_0_type,
     });
 
     try writer.writeAll("static ");
     try o.dg.renderTypeAndName(writer, name_array_ty, .{ .identifier = array_identifier }, Const, 0, .complete);
     try writer.writeAll(" = {");
-    for (mod.error_name_list.items, 0..) |name, value| {
+    for (mod.global_error_set.keys(), 0..) |name_nts, value| {
+        const name = mod.intern_pool.stringToSlice(name_nts);
         if (value != 0) try writer.writeByte(',');
 
         const len_val = try mod.intValue(Type.usize, name.len);
@@ -2469,14 +2473,16 @@ fn genExports(o: *Object) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
+    const mod = o.dg.module;
+    const ip = &mod.intern_pool;
     const fwd_decl_writer = o.dg.fwd_decl.writer();
-    if (o.dg.module.decl_exports.get(o.dg.decl_index.unwrap().?)) |exports| {
+    if (mod.decl_exports.get(o.dg.decl_index.unwrap().?)) |exports| {
         for (exports.items[1..], 1..) |@"export", i| {
             try fwd_decl_writer.writeAll("zig_export(");
             try o.dg.renderFunctionSignature(fwd_decl_writer, o.dg.decl_index.unwrap().?, .forward, .{ .export_index = @intCast(u32, i) });
             try fwd_decl_writer.print(", {s}, {s});\n", .{
-                fmtStringLiteral(exports.items[0].options.name, null),
-                fmtStringLiteral(@"export".options.name, null),
+                fmtStringLiteral(ip.stringToSlice(exports.items[0].name), null),
+                fmtStringLiteral(ip.stringToSlice(@"export".name), null),
             });
         }
     }
@@ -2680,9 +2686,10 @@ pub fn genDecl(o: *Object) !void {
         if (!is_global) try w.writeAll("static ");
         if (variable.is_threadlocal) try w.writeAll("zig_threadlocal ");
         if (variable.is_weak_linkage) try w.writeAll("zig_weak_linkage ");
-        if (decl.@"linksection") |section| try w.print("zig_linksection(\"{s}\", ", .{section});
+        if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |s|
+            try w.print("zig_linksection(\"{s}\", ", .{s});
         try o.dg.renderTypeAndName(w, tv.ty, decl_c_value, .{}, decl.@"align", .complete);
-        if (decl.@"linksection" != null) try w.writeAll(", read, write)");
+        if (decl.@"linksection" != .none) try w.writeAll(", read, write)");
         try w.writeAll(" = ");
         try o.dg.renderValue(w, tv.ty, variable.init.toValue(), .StaticInitializer);
         try w.writeByte(';');
@@ -2697,9 +2704,10 @@ pub fn genDecl(o: *Object) !void {
 
         const w = o.writer();
         if (!is_global) try w.writeAll("static ");
-        if (decl.@"linksection") |section| try w.print("zig_linksection(\"{s}\", ", .{section});
+        if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |s|
+            try w.print("zig_linksection(\"{s}\", ", .{s});
         try o.dg.renderTypeAndName(w, tv.ty, decl_c_value, Const, decl.@"align", .complete);
-        if (decl.@"linksection" != null) try w.writeAll(", read)");
+        if (decl.@"linksection" != .none) try w.writeAll(", read)");
         try w.writeAll(" = ");
         try o.dg.renderValue(w, tv.ty, tv.val, .StaticInitializer);
         try w.writeAll(";\n");
@@ -4229,7 +4237,9 @@ fn airDbgInline(f: *Function, inst: Air.Inst.Index) !CValue {
     const mod = f.object.dg.module;
     const writer = f.object.writer();
     const function = mod.funcPtr(ty_fn.func);
-    try writer.print("/* dbg func:{s} */\n", .{mod.declPtr(function.owner_decl).name});
+    try writer.print("/* dbg func:{s} */\n", .{
+        mod.intern_pool.stringToSlice(mod.declPtr(function.owner_decl).name),
+    });
     return .none;
 }
 
@@ -5176,6 +5186,7 @@ fn fieldLocation(
     byte_offset: u32,
     end: void,
 } {
+    const ip = &mod.intern_pool;
     return switch (container_ty.zigTypeTag(mod)) {
         .Struct => switch (container_ty.containerLayout(mod)) {
             .Auto, .Extern => for (field_index..container_ty.structFieldCount(mod)) |next_field_index| {
@@ -5186,7 +5197,7 @@ fn fieldLocation(
                 break .{ .field = if (container_ty.isSimpleTuple(mod))
                     .{ .field = next_field_index }
                 else
-                    .{ .identifier = container_ty.structFieldName(next_field_index, mod) } };
+                    .{ .identifier = ip.stringToSlice(container_ty.structFieldName(next_field_index, mod)) } };
             } else if (container_ty.hasRuntimeBitsIgnoreComptime(mod)) .end else .begin,
             .Packed => if (field_ptr_ty.ptrInfo(mod).host_size == 0)
                 .{ .byte_offset = container_ty.packedStructFieldByteOffset(field_index, mod) }
@@ -5204,9 +5215,9 @@ fn fieldLocation(
                         .begin;
                 const field_name = container_ty.unionFields(mod).keys()[field_index];
                 return .{ .field = if (container_ty.unionTagTypeSafety(mod)) |_|
-                    .{ .payload_identifier = field_name }
+                    .{ .payload_identifier = ip.stringToSlice(field_name) }
                 else
-                    .{ .identifier = field_name } };
+                    .{ .identifier = ip.stringToSlice(field_name) } };
             },
             .Packed => .begin,
         },
@@ -5347,6 +5358,7 @@ fn fieldPtr(
 
 fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
     const mod = f.object.dg.module;
+    const ip = &mod.intern_pool;
     const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
     const extra = f.air.extraData(Air.StructField, ty_pl.payload).data;
 
@@ -5369,7 +5381,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
             .Auto, .Extern => if (struct_ty.isSimpleTuple(mod))
                 .{ .field = extra.field_index }
             else
-                .{ .identifier = struct_ty.structFieldName(extra.field_index, mod) },
+                .{ .identifier = ip.stringToSlice(struct_ty.structFieldName(extra.field_index, mod)) },
             .Packed => {
                 const struct_obj = mod.typeToStruct(struct_ty).?;
                 const int_info = struct_ty.intInfo(mod);
@@ -5431,7 +5443,7 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
         .anon_struct_type => |anon_struct_type| if (anon_struct_type.names.len == 0)
             .{ .field = extra.field_index }
         else
-            .{ .identifier = struct_ty.structFieldName(extra.field_index, mod) },
+            .{ .identifier = ip.stringToSlice(struct_ty.structFieldName(extra.field_index, mod)) },
 
         .union_type => |union_type| field_name: {
             const union_obj = mod.unionPtr(union_type.index);
@@ -5462,9 +5474,9 @@ fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue {
             } else {
                 const name = union_obj.fields.keys()[extra.field_index];
                 break :field_name if (union_type.hasTag()) .{
-                    .payload_identifier = name,
+                    .payload_identifier = ip.stringToSlice(name),
                 } else .{
-                    .identifier = name,
+                    .identifier = ip.stringToSlice(name),
                 };
             }
         },
@@ -6723,6 +6735,7 @@ fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue {
 
 fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
     const mod = f.object.dg.module;
+    const ip = &mod.intern_pool;
     const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
     const inst_ty = f.typeOfIndex(inst);
     const len = @intCast(usize, inst_ty.arrayLen(mod));
@@ -6773,7 +6786,7 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
                 try f.writeCValueMember(writer, local, if (inst_ty.isSimpleTuple(mod))
                     .{ .field = field_i }
                 else
-                    .{ .identifier = inst_ty.structFieldName(field_i, mod) });
+                    .{ .identifier = ip.stringToSlice(inst_ty.structFieldName(field_i, mod)) });
                 try a.assign(f, writer);
                 try f.writeCValue(writer, element, .Other);
                 try a.end(f, writer);
@@ -6851,6 +6864,7 @@ fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue {
 
 fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue {
     const mod = f.object.dg.module;
+    const ip = &mod.intern_pool;
     const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
     const extra = f.air.extraData(Air.UnionInit, ty_pl.payload).data;
 
@@ -6886,8 +6900,8 @@ fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue {
             try writer.print("{}", .{try f.fmtIntLiteral(tag_ty, int_val)});
             try a.end(f, writer);
         }
-        break :field .{ .payload_identifier = field_name };
-    } else .{ .identifier = field_name };
+        break :field .{ .payload_identifier = ip.stringToSlice(field_name) };
+    } else .{ .identifier = ip.stringToSlice(field_name) };
 
     const a = try Assignment.start(f, writer, payload_ty);
     try f.writeCValueMember(writer, local, field);
src/codegen/llvm.zig
@@ -585,13 +585,13 @@ pub const Object = struct {
         const slice_ty = Type.slice_const_u8_sentinel_0;
         const slice_alignment = slice_ty.abiAlignment(mod);
 
-        const error_name_list = mod.error_name_list.items;
+        const error_name_list = mod.global_error_set.keys();
         const llvm_errors = try mod.gpa.alloc(*llvm.Value, error_name_list.len);
         defer mod.gpa.free(llvm_errors);
 
         llvm_errors[0] = llvm_slice_ty.getUndef();
-        for (llvm_errors[1..], 0..) |*llvm_error, i| {
-            const name = error_name_list[1..][i];
+        for (llvm_errors[1..], error_name_list[1..]) |*llvm_error, name_nts| {
+            const name = mod.intern_pool.stringToSlice(name_nts);
             const str_init = self.context.constString(name.ptr, @intCast(c_uint, name.len), .False);
             const str_global = self.llvm_module.addGlobal(str_init.typeOf(), "");
             str_global.setInitializer(str_init);
@@ -671,7 +671,7 @@ pub const Object = struct {
             const llvm_global = entry.value_ptr.*;
             // Same logic as below but for externs instead of exports.
             const decl = mod.declPtr(decl_index);
-            const other_global = object.getLlvmGlobal(decl.name) orelse continue;
+            const other_global = object.getLlvmGlobal(mod.intern_pool.stringToSlice(decl.name)) orelse continue;
             if (other_global == llvm_global) continue;
 
             llvm_global.replaceAllUsesWith(other_global);
@@ -689,8 +689,7 @@ pub const Object = struct {
                 // case, we need to replace all uses of it with this exported global.
                 // TODO update std.builtin.ExportOptions to have the name be a
                 // null-terminated slice.
-                const exp_name_z = try mod.gpa.dupeZ(u8, exp.options.name);
-                defer mod.gpa.free(exp_name_z);
+                const exp_name_z = mod.intern_pool.stringToSlice(exp.name);
 
                 const other_global = object.getLlvmGlobal(exp_name_z.ptr) orelse continue;
                 if (other_global == llvm_global) continue;
@@ -923,9 +922,8 @@ pub const Object = struct {
             dg.addFnAttrString(llvm_func, "no-stack-arg-probe", "");
         }
 
-        if (decl.@"linksection") |section| {
+        if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |section|
             llvm_func.setSection(section);
-        }
 
         // Remove all the basic blocks of a function in order to start over, generating
         // LLVM IR from an empty function body.
@@ -1173,7 +1171,7 @@ pub const Object = struct {
                 0;
             const subprogram = dib.createFunction(
                 di_file.?.toScope(),
-                decl.name,
+                mod.intern_pool.stringToSlice(decl.name),
                 llvm_func.getValueName(),
                 di_file.?,
                 line_number,
@@ -1273,22 +1271,26 @@ pub const Object = struct {
         if (decl.isExtern(mod)) {
             var free_decl_name = false;
             const decl_name = decl_name: {
+                const decl_name = mod.intern_pool.stringToSlice(decl.name);
+
                 if (mod.getTarget().isWasm() and try decl.isFunction(mod)) {
                     if (mod.intern_pool.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| {
                         if (!std.mem.eql(u8, lib_name, "c")) {
                             free_decl_name = true;
-                            break :decl_name try std.fmt.allocPrintZ(gpa, "{s}|{s}", .{ decl.name, lib_name });
+                            break :decl_name try std.fmt.allocPrintZ(gpa, "{s}|{s}", .{
+                                decl_name, lib_name,
+                            });
                         }
                     }
                 }
-                break :decl_name std.mem.span(decl.name);
+
+                break :decl_name decl_name;
             };
             defer if (free_decl_name) gpa.free(decl_name);
 
             llvm_global.setValueName(decl_name);
             if (self.getLlvmGlobal(decl_name)) |other_global| {
                 if (other_global != llvm_global) {
-                    log.debug("updateDeclExports isExtern()=true setValueName({s}) conflict", .{decl.name});
                     try self.extern_collisions.put(gpa, decl_index, {});
                 }
             }
@@ -1298,11 +1300,11 @@ pub const Object = struct {
             if (self.di_map.get(decl)) |di_node| {
                 if (try decl.isFunction(mod)) {
                     const di_func = @ptrCast(*llvm.DISubprogram, di_node);
-                    const linkage_name = llvm.MDString.get(self.context, decl.name, std.mem.len(decl.name));
+                    const linkage_name = llvm.MDString.get(self.context, decl_name.ptr, decl_name.len);
                     di_func.replaceLinkageName(linkage_name);
                 } else {
                     const di_global = @ptrCast(*llvm.DIGlobalVariable, di_node);
-                    const linkage_name = llvm.MDString.get(self.context, decl.name, std.mem.len(decl.name));
+                    const linkage_name = llvm.MDString.get(self.context, decl_name.ptr, decl_name.len);
                     di_global.replaceLinkageName(linkage_name);
                 }
             }
@@ -1317,7 +1319,7 @@ pub const Object = struct {
                 }
             }
         } else if (exports.len != 0) {
-            const exp_name = exports[0].options.name;
+            const exp_name = mod.intern_pool.stringToSlice(exports[0].name);
             llvm_global.setValueName2(exp_name.ptr, exp_name.len);
             llvm_global.setUnnamedAddr(.False);
             if (mod.wantDllExports()) llvm_global.setDLLStorageClass(.DLLExport);
@@ -1332,21 +1334,19 @@ pub const Object = struct {
                     di_global.replaceLinkageName(linkage_name);
                 }
             }
-            switch (exports[0].options.linkage) {
+            switch (exports[0].linkage) {
                 .Internal => unreachable,
                 .Strong => llvm_global.setLinkage(.External),
                 .Weak => llvm_global.setLinkage(.WeakODR),
                 .LinkOnce => llvm_global.setLinkage(.LinkOnceODR),
             }
-            switch (exports[0].options.visibility) {
+            switch (exports[0].visibility) {
                 .default => llvm_global.setVisibility(.Default),
                 .hidden => llvm_global.setVisibility(.Hidden),
                 .protected => llvm_global.setVisibility(.Protected),
             }
-            if (exports[0].options.section) |section| {
-                const section_z = try gpa.dupeZ(u8, section);
-                defer gpa.free(section_z);
-                llvm_global.setSection(section_z);
+            if (mod.intern_pool.stringToSliceUnwrap(exports[0].section)) |section| {
+                llvm_global.setSection(section);
             }
             if (decl.val.getVariable(mod)) |variable| {
                 if (variable.is_threadlocal) {
@@ -1356,13 +1356,12 @@ pub const Object = struct {
 
             // If a Decl is exported more than one time (which is rare),
             // we add aliases for all but the first export.
-            // TODO LLVM C API does not support deleting aliases. We need to
-            // patch it to support this or figure out how to wrap the C++ API ourselves.
+            // TODO LLVM C API does not support deleting aliases.
+            // The planned solution to this is https://github.com/ziglang/zig/issues/13265
             // Until then we iterate over existing aliases and make them point
             // to the correct decl, or otherwise add a new alias. Old aliases are leaked.
             for (exports[1..]) |exp| {
-                const exp_name_z = try gpa.dupeZ(u8, exp.options.name);
-                defer gpa.free(exp_name_z);
+                const exp_name_z = mod.intern_pool.stringToSlice(exp.name);
 
                 if (self.llvm_module.getNamedGlobalAlias(exp_name_z.ptr, exp_name_z.len)) |alias| {
                     alias.setAliasee(llvm_global);
@@ -1376,8 +1375,7 @@ pub const Object = struct {
                 }
             }
         } else {
-            const fqn = try decl.getFullyQualifiedName(mod);
-            defer gpa.free(fqn);
+            const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
             llvm_global.setValueName2(fqn.ptr, fqn.len);
             llvm_global.setLinkage(.Internal);
             if (mod.wantDllExports()) llvm_global.setDLLStorageClass(.Default);
@@ -2092,8 +2090,7 @@ pub const Object = struct {
                     const field_offset = std.mem.alignForwardGeneric(u64, offset, field_align);
                     offset = field_offset + field_size;
 
-                    const field_name = try gpa.dupeZ(u8, fields.keys()[field_and_index.index]);
-                    defer gpa.free(field_name);
+                    const field_name = mod.intern_pool.stringToSlice(fields.keys()[field_and_index.index]);
 
                     try di_fields.append(gpa, dib.createMemberType(
                         fwd_decl.toScope(),
@@ -2200,12 +2197,9 @@ pub const Object = struct {
                     const field_size = field.ty.abiSize(mod);
                     const field_align = field.normalAlignment(mod);
 
-                    const field_name_copy = try gpa.dupeZ(u8, field_name);
-                    defer gpa.free(field_name_copy);
-
                     di_fields.appendAssumeCapacity(dib.createMemberType(
                         fwd_decl.toScope(),
-                        field_name_copy,
+                        mod.intern_pool.stringToSlice(field_name),
                         null, // file
                         0, // line
                         field_size * 8, // size in bits
@@ -2327,7 +2321,7 @@ pub const Object = struct {
                 if (fn_info.return_type.toType().isError(mod) and
                     o.module.comp.bin_file.options.error_return_tracing)
                 {
-                    const ptr_ty = try mod.singleMutPtrType(o.getStackTraceType());
+                    const ptr_ty = try mod.singleMutPtrType(try o.getStackTraceType());
                     try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
                 }
 
@@ -2384,7 +2378,7 @@ pub const Object = struct {
         const fields: [0]*llvm.DIType = .{};
         return o.di_builder.?.createStructType(
             try o.namespaceToDebugScope(decl.src_namespace),
-            decl.name, // TODO use fully qualified name
+            mod.intern_pool.stringToSlice(decl.name), // TODO use fully qualified name
             try o.getDIFile(o.gpa, mod.namespacePtr(decl.src_namespace).file_scope),
             decl.src_line + 1,
             0, // size in bits
@@ -2399,18 +2393,18 @@ pub const Object = struct {
         );
     }
 
-    fn getStackTraceType(o: *Object) Type {
+    fn getStackTraceType(o: *Object) Allocator.Error!Type {
         const mod = o.module;
 
         const std_pkg = mod.main_pkg.table.get("std").?;
         const std_file = (mod.importPkg(std_pkg) catch unreachable).file;
 
-        const builtin_str: []const u8 = "builtin";
+        const builtin_str = try mod.intern_pool.getOrPutString(mod.gpa, "builtin");
         const std_namespace = mod.namespacePtr(mod.declPtr(std_file.root_decl.unwrap().?).src_namespace);
         const builtin_decl = std_namespace.decls
             .getKeyAdapted(builtin_str, Module.DeclAdapter{ .mod = mod }).?;
 
-        const stack_trace_str: []const u8 = "StackTrace";
+        const stack_trace_str = try mod.intern_pool.getOrPutString(mod.gpa, "StackTrace");
         // buffer is only used for int_type, `builtin` is a struct.
         const builtin_ty = mod.declPtr(builtin_decl).val.toType();
         const builtin_namespace = builtin_ty.getNamespace(mod).?;
@@ -2452,16 +2446,13 @@ pub const DeclGen = struct {
         const decl_index = dg.decl_index;
         assert(decl.has_tv);
 
-        log.debug("gen: {s} type: {}, value: {}", .{
-            decl.name, decl.ty.fmtDebug(), decl.val.fmtDebug(),
-        });
         if (decl.val.getExternFunc(mod)) |extern_func| {
             _ = try dg.resolveLlvmFunction(extern_func.decl);
         } else {
             const target = mod.getTarget();
             var global = try dg.resolveGlobalDecl(decl_index);
             global.setAlignment(decl.getAlignment(mod));
-            if (decl.@"linksection") |section| global.setSection(section);
+            if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |s| global.setSection(s);
             assert(decl.has_tv);
             const init_val = if (decl.val.getVariable(mod)) |variable| init_val: {
                 break :init_val variable.init;
@@ -2495,7 +2486,8 @@ pub const DeclGen = struct {
                     new_global.setLinkage(global.getLinkage());
                     new_global.setUnnamedAddr(global.getUnnamedAddress());
                     new_global.setAlignment(global.getAlignment());
-                    if (decl.@"linksection") |section| new_global.setSection(section);
+                    if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |s|
+                        new_global.setSection(s);
                     new_global.setInitializer(llvm_init);
                     // TODO: How should this work then the address space of a global changed?
                     global.replaceAllUsesWith(new_global);
@@ -2513,7 +2505,7 @@ pub const DeclGen = struct {
                 const is_internal_linkage = !dg.module.decl_exports.contains(decl_index);
                 const di_global = dib.createGlobalVariableExpression(
                     di_file.toScope(),
-                    decl.name,
+                    mod.intern_pool.stringToSlice(decl.name),
                     global.getValueName(),
                     di_file,
                     line_number,
@@ -2544,8 +2536,7 @@ pub const DeclGen = struct {
 
         const fn_type = try dg.lowerType(zig_fn_type);
 
-        const fqn = try decl.getFullyQualifiedName(mod);
-        defer dg.gpa.free(fqn);
+        const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
         const llvm_addrspace = toLlvmAddressSpace(decl.@"addrspace", target);
         const llvm_fn = dg.llvmModule().addFunctionInAddressSpace(fqn, fn_type, llvm_addrspace);
@@ -2557,7 +2548,7 @@ pub const DeclGen = struct {
             llvm_fn.setUnnamedAddr(.True);
         } else {
             if (target.isWasm()) {
-                dg.addFnAttrString(llvm_fn, "wasm-import-name", std.mem.sliceTo(decl.name, 0));
+                dg.addFnAttrString(llvm_fn, "wasm-import-name", mod.intern_pool.stringToSlice(decl.name));
                 if (mod.intern_pool.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| {
                     if (!std.mem.eql(u8, lib_name, "c")) {
                         dg.addFnAttrString(llvm_fn, "wasm-import-module", lib_name);
@@ -2699,8 +2690,7 @@ pub const DeclGen = struct {
 
         const mod = dg.module;
         const decl = mod.declPtr(decl_index);
-        const fqn = try decl.getFullyQualifiedName(mod);
-        defer dg.gpa.free(fqn);
+        const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
         const target = mod.getTarget();
 
@@ -2716,7 +2706,7 @@ pub const DeclGen = struct {
 
         // This is needed for declarations created by `@extern`.
         if (decl.isExtern(mod)) {
-            llvm_global.setValueName(decl.name);
+            llvm_global.setValueName(mod.intern_pool.stringToSlice(decl.name));
             llvm_global.setUnnamedAddr(.False);
             llvm_global.setLinkage(.External);
             if (decl.val.getVariable(mod)) |variable| {
@@ -2811,8 +2801,7 @@ pub const DeclGen = struct {
                 if (gop.found_existing) return gop.value_ptr.*;
 
                 const opaque_type = mod.intern_pool.indexToKey(t.toIntern()).opaque_type;
-                const name = try mod.opaqueFullyQualifiedName(opaque_type);
-                defer gpa.free(name);
+                const name = mod.intern_pool.stringToSlice(try mod.opaqueFullyQualifiedName(opaque_type));
 
                 const llvm_struct_ty = dg.context.structCreateNamed(name);
                 gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
@@ -2963,8 +2952,7 @@ pub const DeclGen = struct {
                     return int_llvm_ty;
                 }
 
-                const name = try struct_obj.getFullyQualifiedName(mod);
-                defer gpa.free(name);
+                const name = mod.intern_pool.stringToSlice(try struct_obj.getFullyQualifiedName(mod));
 
                 const llvm_struct_ty = dg.context.structCreateNamed(name);
                 gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
@@ -3040,8 +3028,7 @@ pub const DeclGen = struct {
                     return enum_tag_llvm_ty;
                 }
 
-                const name = try union_obj.getFullyQualifiedName(mod);
-                defer gpa.free(name);
+                const name = mod.intern_pool.stringToSlice(try union_obj.getFullyQualifiedName(mod));
 
                 const llvm_union_ty = dg.context.structCreateNamed(name);
                 gop.value_ptr.* = llvm_union_ty; // must be done before any recursive calls
@@ -3119,7 +3106,7 @@ pub const DeclGen = struct {
         if (fn_info.return_type.toType().isError(mod) and
             mod.comp.bin_file.options.error_return_tracing)
         {
-            const ptr_ty = try mod.singleMutPtrType(dg.object.getStackTraceType());
+            const ptr_ty = try mod.singleMutPtrType(try dg.object.getStackTraceType());
             try llvm_params.append(try dg.lowerType(ptr_ty));
         }
 
@@ -3266,9 +3253,8 @@ pub const DeclGen = struct {
             },
             .err => |err| {
                 const llvm_ty = try dg.lowerType(Type.anyerror);
-                const name = mod.intern_pool.stringToSlice(err.name);
-                const kv = try mod.getErrorValue(name);
-                return llvm_ty.constInt(kv.value, .False);
+                const int = try mod.getErrorValue(err.name);
+                return llvm_ty.constInt(int, .False);
             },
             .error_union => |error_union| {
                 const err_tv: TypedValue = switch (error_union.val) {
@@ -5960,8 +5946,7 @@ pub const FuncGen = struct {
             .base_line = self.base_line,
         });
 
-        const fqn = try decl.getFullyQualifiedName(mod);
-        defer self.gpa.free(fqn);
+        const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
         const is_internal_linkage = !mod.decl_exports.contains(decl_index);
         const fn_ty = try mod.funcType(.{
@@ -5981,7 +5966,7 @@ pub const FuncGen = struct {
         });
         const subprogram = dib.createFunction(
             di_file.toScope(),
-            decl.name,
+            mod.intern_pool.stringToSlice(decl.name),
             fqn,
             di_file,
             line_number,
@@ -8629,9 +8614,8 @@ pub const FuncGen = struct {
         const end_block = self.context.appendBasicBlock(self.llvm_func, "End");
         const switch_instr = self.builder.buildSwitch(operand, invalid_block, @intCast(c_uint, names.len));
 
-        for (names) |name_ip| {
-            const name = mod.intern_pool.stringToSlice(name_ip);
-            const err_int = mod.global_error_set.get(name).?;
+        for (names) |name| {
+            const err_int = @intCast(Module.ErrorInt, mod.global_error_set.getIndex(name).?);
             const this_tag_int_value = try self.dg.lowerValue(.{
                 .ty = Type.err_int,
                 .val = try mod.intValue(Type.err_int, err_int),
@@ -8681,8 +8665,7 @@ pub const FuncGen = struct {
         defer arena_allocator.deinit();
         const arena = arena_allocator.allocator();
 
-        const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
-        defer self.gpa.free(fqn);
+        const fqn = mod.intern_pool.stringToSlice(try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod));
         const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{s}", .{fqn});
 
         const param_types = [_]*llvm.Type{try self.dg.lowerType(enum_type.tag_ty.toType())};
@@ -8754,8 +8737,7 @@ pub const FuncGen = struct {
         defer arena_allocator.deinit();
         const arena = arena_allocator.allocator();
 
-        const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
-        defer self.gpa.free(fqn);
+        const fqn = mod.intern_pool.stringToSlice(try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod));
         const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{fqn});
 
         const slice_ty = Type.slice_const_u8_sentinel_0;
src/codegen/spirv.zig
@@ -593,7 +593,6 @@ pub const DeclGen = struct {
                 .extern_func => unreachable, // TODO
                 else => {
                     const result_id = dg.spv.allocId();
-                    log.debug("addDeclRef: id = {}, index = {}, name = {s}", .{ result_id.id, @enumToInt(spv_decl_index), decl.name });
 
                     try self.decl_deps.put(spv_decl_index, {});
 
@@ -664,9 +663,8 @@ pub const DeclGen = struct {
                 => unreachable, // non-runtime values
                 .int => try self.addInt(ty, val),
                 .err => |err| {
-                    const name = mod.intern_pool.stringToSlice(err.name);
-                    const kv = try mod.getErrorValue(name);
-                    try self.addConstInt(u16, @intCast(u16, kv.value));
+                    const int = try mod.getErrorValue(err.name);
+                    try self.addConstInt(u16, @intCast(u16, int));
                 },
                 .error_union => |error_union| {
                     const payload_ty = ty.errorUnionPayload(mod);
@@ -1288,8 +1286,7 @@ pub const DeclGen = struct {
                     member_index += 1;
                 }
 
-                const name = try struct_obj.getFullyQualifiedName(self.module);
-                defer self.module.gpa.free(name);
+                const name = mod.intern_pool.stringToSlice(try struct_obj.getFullyQualifiedName(self.module));
 
                 return try self.spv.resolve(.{ .struct_type = .{
                     .name = try self.spv.resolveString(name),
@@ -1500,7 +1497,6 @@ pub const DeclGen = struct {
         const spv_decl_index = try self.resolveDecl(self.decl_index);
 
         const decl_id = self.spv.declPtr(spv_decl_index).result_id;
-        log.debug("genDecl: id = {}, index = {}, name = {s}", .{ decl_id.id, @enumToInt(spv_decl_index), decl.name });
 
         if (decl.val.getFunction(mod)) |_| {
             assert(decl.ty.zigTypeTag(mod) == .Fn);
@@ -1542,8 +1538,7 @@ pub const DeclGen = struct {
             try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
             try self.spv.addFunction(spv_decl_index, self.func);
 
-            const fqn = try decl.getFullyQualifiedName(self.module);
-            defer self.module.gpa.free(fqn);
+            const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(self.module));
 
             try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{
                 .target = decl_id,
src/link/C.zig
@@ -6,6 +6,7 @@ const fs = std.fs;
 
 const C = @This();
 const Module = @import("../Module.zig");
+const InternPool = @import("../InternPool.zig");
 const Compilation = @import("../Compilation.zig");
 const codegen = @import("../codegen/c.zig");
 const link = @import("../link.zig");
@@ -289,11 +290,11 @@ pub fn flushModule(self: *C, _: *Compilation, prog_node: *std.Progress.Node) !vo
     }
 
     {
-        var export_names = std.StringHashMapUnmanaged(void){};
+        var export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{};
         defer export_names.deinit(gpa);
         try export_names.ensureTotalCapacity(gpa, @intCast(u32, module.decl_exports.entries.len));
         for (module.decl_exports.values()) |exports| for (exports.items) |@"export"|
-            try export_names.put(gpa, @"export".options.name, {});
+            try export_names.put(gpa, @"export".name, {});
 
         while (f.remaining_decls.popOrNull()) |kv| {
             const decl_index = kv.key;
@@ -553,7 +554,7 @@ fn flushDecl(
     self: *C,
     f: *Flush,
     decl_index: Module.Decl.Index,
-    export_names: std.StringHashMapUnmanaged(void),
+    export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
 ) FlushDeclError!void {
     const gpa = self.base.allocator;
     const mod = self.base.options.module.?;
@@ -571,7 +572,7 @@ fn flushDecl(
 
     try self.flushLazyFns(f, decl_block.lazy_fns);
     try f.all_buffers.ensureUnusedCapacity(gpa, 1);
-    if (!(decl.isExtern(mod) and export_names.contains(mem.span(decl.name))))
+    if (!(decl.isExtern(mod) and export_names.contains(decl.name)))
         f.appendBufAssumeCapacity(decl_block.fwd_decl.items);
 }
 
src/link/Coff.zig
@@ -1097,8 +1097,7 @@ pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.In
     const atom_index = try self.createAtom();
 
     const sym_name = blk: {
-        const decl_name = try decl.getFullyQualifiedName(mod);
-        defer gpa.free(decl_name);
+        const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
         const index = unnamed_consts.items.len;
         break :blk try std.fmt.allocPrint(gpa, "__unnamed_{s}_{d}", .{ decl_name, index });
@@ -1324,12 +1323,10 @@ fn getDeclOutputSection(self: *Coff, decl_index: Module.Decl.Index) u16 {
 }
 
 fn updateDeclCode(self: *Coff, decl_index: Module.Decl.Index, code: []u8, complex_type: coff.ComplexType) !void {
-    const gpa = self.base.allocator;
     const mod = self.base.options.module.?;
     const decl = mod.declPtr(decl_index);
 
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer gpa.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     log.debug("updateDeclCode {s}{*}", .{ decl_name, decl });
     const required_alignment = decl.getAlignment(mod);
@@ -1420,6 +1417,8 @@ pub fn updateDeclExports(
         @panic("Attempted to compile for object format that was disabled by build configuration");
     }
 
+    const ip = &mod.intern_pool;
+
     if (build_options.have_llvm) {
         // Even in the case of LLVM, we need to notice certain exported symbols in order to
         // detect the default subsystem.
@@ -1431,20 +1430,20 @@ pub fn updateDeclExports(
                 else => std.builtin.CallingConvention.C,
             };
             const decl_cc = exported_decl.ty.fnCallingConvention(mod);
-            if (decl_cc == .C and mem.eql(u8, exp.options.name, "main") and
+            if (decl_cc == .C and ip.stringEqlSlice(exp.name, "main") and
                 self.base.options.link_libc)
             {
                 mod.stage1_flags.have_c_main = true;
             } else if (decl_cc == winapi_cc and self.base.options.target.os.tag == .windows) {
-                if (mem.eql(u8, exp.options.name, "WinMain")) {
+                if (ip.stringEqlSlice(exp.name, "WinMain")) {
                     mod.stage1_flags.have_winmain = true;
-                } else if (mem.eql(u8, exp.options.name, "wWinMain")) {
+                } else if (ip.stringEqlSlice(exp.name, "wWinMain")) {
                     mod.stage1_flags.have_wwinmain = true;
-                } else if (mem.eql(u8, exp.options.name, "WinMainCRTStartup")) {
+                } else if (ip.stringEqlSlice(exp.name, "WinMainCRTStartup")) {
                     mod.stage1_flags.have_winmain_crt_startup = true;
-                } else if (mem.eql(u8, exp.options.name, "wWinMainCRTStartup")) {
+                } else if (ip.stringEqlSlice(exp.name, "wWinMainCRTStartup")) {
                     mod.stage1_flags.have_wwinmain_crt_startup = true;
-                } else if (mem.eql(u8, exp.options.name, "DllMainCRTStartup")) {
+                } else if (ip.stringEqlSlice(exp.name, "DllMainCRTStartup")) {
                     mod.stage1_flags.have_dllmain_crt_startup = true;
                 }
             }
@@ -1453,9 +1452,6 @@ pub fn updateDeclExports(
         if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(mod, decl_index, exports);
     }
 
-    const tracy = trace(@src());
-    defer tracy.end();
-
     const gpa = self.base.allocator;
 
     const decl = mod.declPtr(decl_index);
@@ -1465,12 +1461,13 @@ pub fn updateDeclExports(
     const decl_metadata = self.decls.getPtr(decl_index).?;
 
     for (exports) |exp| {
-        log.debug("adding new export '{s}'", .{exp.options.name});
+        const exp_name = mod.intern_pool.stringToSlice(exp.name);
+        log.debug("adding new export '{s}'", .{exp_name});
 
-        if (exp.options.section) |section_name| {
+        if (mod.intern_pool.stringToSliceUnwrap(exp.section)) |section_name| {
             if (!mem.eql(u8, section_name, ".text")) {
                 try mod.failed_exports.putNoClobber(
-                    mod.gpa,
+                    gpa,
                     exp,
                     try Module.ErrorMsg.create(
                         gpa,
@@ -1483,9 +1480,9 @@ pub fn updateDeclExports(
             }
         }
 
-        if (exp.options.linkage == .LinkOnce) {
+        if (exp.linkage == .LinkOnce) {
             try mod.failed_exports.putNoClobber(
-                mod.gpa,
+                gpa,
                 exp,
                 try Module.ErrorMsg.create(
                     gpa,
@@ -1497,19 +1494,19 @@ pub fn updateDeclExports(
             continue;
         }
 
-        const sym_index = decl_metadata.getExport(self, exp.options.name) orelse blk: {
+        const sym_index = decl_metadata.getExport(self, exp_name) orelse blk: {
             const sym_index = try self.allocateSymbol();
             try decl_metadata.exports.append(gpa, sym_index);
             break :blk sym_index;
         };
         const sym_loc = SymbolWithLoc{ .sym_index = sym_index, .file = null };
         const sym = self.getSymbolPtr(sym_loc);
-        try self.setSymbolName(sym, exp.options.name);
+        try self.setSymbolName(sym, exp_name);
         sym.value = decl_sym.value;
         sym.section_number = @intToEnum(coff.SectionNumber, self.text_section_index.? + 1);
         sym.type = .{ .complex_type = .FUNCTION, .base_type = .NULL };
 
-        switch (exp.options.linkage) {
+        switch (exp.linkage) {
             .Strong => {
                 sym.storage_class = .EXTERNAL;
             },
@@ -1522,9 +1519,15 @@ pub fn updateDeclExports(
     }
 }
 
-pub fn deleteDeclExport(self: *Coff, decl_index: Module.Decl.Index, name: []const u8) void {
+pub fn deleteDeclExport(
+    self: *Coff,
+    decl_index: Module.Decl.Index,
+    name_ip: InternPool.NullTerminatedString,
+) void {
     if (self.llvm_object) |_| return;
     const metadata = self.decls.getPtr(decl_index) orelse return;
+    const mod = self.base.options.module.?;
+    const name = mod.intern_pool.stringToSlice(name_ip);
     const sym_index = metadata.getExportPtr(self, name) orelse return;
 
     const gpa = self.base.allocator;
@@ -2540,6 +2543,7 @@ const ImportTable = @import("Coff/ImportTable.zig");
 const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
 const Module = @import("../Module.zig");
+const InternPool = @import("../InternPool.zig");
 const Object = @import("Coff/Object.zig");
 const Relocation = @import("Coff/Relocation.zig");
 const TableSection = @import("table_section.zig").TableSection;
src/link/Dwarf.zig
@@ -358,8 +358,9 @@ pub const DeclState = struct {
                             struct_obj.fields.keys(),
                             struct_obj.fields.values(),
                             0..,
-                        ) |field_name, field, field_index| {
+                        ) |field_name_ip, field, field_index| {
                             if (!field.ty.hasRuntimeBits(mod)) continue;
+                            const field_name = mod.intern_pool.stringToSlice(field_name_ip);
                             // DW.AT.member
                             try dbg_info_buffer.ensureUnusedCapacity(field_name.len + 2);
                             dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.struct_member));
@@ -469,7 +470,8 @@ pub const DeclState = struct {
                     // DW.AT.member
                     try dbg_info_buffer.append(@enumToInt(AbbrevKind.struct_member));
                     // DW.AT.name, DW.FORM.string
-                    try dbg_info_buffer.writer().print("{s}\x00", .{field_name});
+                    try dbg_info_buffer.appendSlice(mod.intern_pool.stringToSlice(field_name));
+                    try dbg_info_buffer.append(0);
                     // DW.AT.type, DW.FORM.ref4
                     const index = dbg_info_buffer.items.len;
                     try dbg_info_buffer.resize(index + 4);
@@ -949,8 +951,7 @@ pub fn initDeclState(self: *Dwarf, mod: *Module, decl_index: Module.Decl.Index)
     defer tracy.end();
 
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer self.allocator.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     log.debug("initDeclState {s}{*}", .{ decl_name, decl });
 
@@ -1273,7 +1274,6 @@ pub fn commitDeclState(
         }
     }
 
-    log.debug("updateDeclDebugInfoAllocation for '{s}'", .{decl.name});
     try self.updateDeclDebugInfoAllocation(di_atom_index, @intCast(u32, dbg_info_buffer.items.len));
 
     while (decl_state.abbrev_relocs.popOrNull()) |reloc| {
@@ -1345,7 +1345,6 @@ pub fn commitDeclState(
         }
     }
 
-    log.debug("writeDeclDebugInfo for '{s}", .{decl.name});
     try self.writeDeclDebugInfo(di_atom_index, dbg_info_buffer.items);
 }
 
@@ -2523,15 +2522,7 @@ pub fn flushModule(self: *Dwarf, module: *Module) !void {
 
         // TODO: don't create a zig type for this, just make the dwarf info
         // without touching the zig type system.
-        const names = try arena.alloc(InternPool.NullTerminatedString, module.global_error_set.count());
-        {
-            var it = module.global_error_set.keyIterator();
-            var i: usize = 0;
-            while (it.next()) |key| : (i += 1) {
-                names[i] = module.intern_pool.getString(key.*).unwrap().?;
-            }
-        }
-
+        const names = try arena.dupe(InternPool.NullTerminatedString, module.global_error_set.keys());
         std.mem.sort(InternPool.NullTerminatedString, names, {}, InternPool.NullTerminatedString.indexLessThan);
 
         const error_ty = try module.intern(.{ .error_set_type = .{ .names = names } });
@@ -2682,8 +2673,8 @@ fn addDbgInfoErrorSet(
 
     const error_names = ty.errorSetNames(mod);
     for (error_names) |error_name_ip| {
+        const int = try mod.getErrorValue(error_name_ip);
         const error_name = mod.intern_pool.stringToSlice(error_name_ip);
-        const kv = mod.getErrorValue(error_name) catch unreachable;
         // DW.AT.enumerator
         try dbg_info_buffer.ensureUnusedCapacity(error_name.len + 2 + @sizeOf(u64));
         dbg_info_buffer.appendAssumeCapacity(@enumToInt(AbbrevKind.enum_variant));
@@ -2691,7 +2682,7 @@ fn addDbgInfoErrorSet(
         dbg_info_buffer.appendSliceAssumeCapacity(error_name);
         dbg_info_buffer.appendAssumeCapacity(0);
         // DW.AT.const_value, DW.FORM.data8
-        mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), kv.value, target_endian);
+        mem.writeInt(u64, dbg_info_buffer.addManyAsArrayAssumeCapacity(8), int, target_endian);
     }
 
     // DW.AT.enumeration_type delimit children
src/link/Elf.zig
@@ -28,6 +28,7 @@ const File = link.File;
 const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
 const Module = @import("../Module.zig");
+const InternPool = @import("../InternPool.zig");
 const Package = @import("../Package.zig");
 const StringTable = @import("strtab.zig").StringTable;
 const TableSection = @import("table_section.zig").TableSection;
@@ -2480,8 +2481,7 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s
     const mod = self.base.options.module.?;
     const decl = mod.declPtr(decl_index);
 
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer self.base.allocator.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     log.debug("updateDeclCode {s}{*}", .{ decl_name, decl });
     const required_alignment = decl.getAlignment(mod);
@@ -2802,8 +2802,7 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl_index: Module
 
     const decl = mod.declPtr(decl_index);
     const name_str_index = blk: {
-        const decl_name = try decl.getFullyQualifiedName(mod);
-        defer gpa.free(decl_name);
+        const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
         const index = unnamed_consts.items.len;
         const name = try std.fmt.allocPrint(gpa, "__unnamed_{s}_{d}", .{ decl_name, index });
         defer gpa.free(name);
@@ -2880,7 +2879,8 @@ pub fn updateDeclExports(
     try self.global_symbols.ensureUnusedCapacity(gpa, exports.len);
 
     for (exports) |exp| {
-        if (exp.options.section) |section_name| {
+        const exp_name = mod.intern_pool.stringToSlice(exp.name);
+        if (mod.intern_pool.stringToSliceUnwrap(exp.section)) |section_name| {
             if (!mem.eql(u8, section_name, ".text")) {
                 try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1);
                 mod.failed_exports.putAssumeCapacityNoClobber(
@@ -2890,11 +2890,11 @@ pub fn updateDeclExports(
                 continue;
             }
         }
-        const stb_bits: u8 = switch (exp.options.linkage) {
+        const stb_bits: u8 = switch (exp.linkage) {
             .Internal => elf.STB_LOCAL,
             .Strong => blk: {
                 const entry_name = self.base.options.entry orelse "_start";
-                if (mem.eql(u8, exp.options.name, entry_name)) {
+                if (mem.eql(u8, exp_name, entry_name)) {
                     self.entry_addr = decl_sym.st_value;
                 }
                 break :blk elf.STB_GLOBAL;
@@ -2910,10 +2910,10 @@ pub fn updateDeclExports(
             },
         };
         const stt_bits: u8 = @truncate(u4, decl_sym.st_info);
-        if (decl_metadata.getExport(self, exp.options.name)) |i| {
+        if (decl_metadata.getExport(self, exp_name)) |i| {
             const sym = &self.global_symbols.items[i];
             sym.* = .{
-                .st_name = try self.shstrtab.insert(gpa, exp.options.name),
+                .st_name = try self.shstrtab.insert(gpa, exp_name),
                 .st_info = (stb_bits << 4) | stt_bits,
                 .st_other = 0,
                 .st_shndx = shdr_index,
@@ -2927,7 +2927,7 @@ pub fn updateDeclExports(
             };
             try decl_metadata.exports.append(gpa, @intCast(u32, i));
             self.global_symbols.items[i] = .{
-                .st_name = try self.shstrtab.insert(gpa, exp.options.name),
+                .st_name = try self.shstrtab.insert(gpa, exp_name),
                 .st_info = (stb_bits << 4) | stt_bits,
                 .st_other = 0,
                 .st_shndx = shdr_index,
@@ -2944,8 +2944,7 @@ pub fn updateDeclLineNumber(self: *Elf, mod: *Module, decl_index: Module.Decl.In
     defer tracy.end();
 
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer self.base.allocator.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     log.debug("updateDeclLineNumber {s}{*}", .{ decl_name, decl });
 
@@ -2955,11 +2954,15 @@ pub fn updateDeclLineNumber(self: *Elf, mod: *Module, decl_index: Module.Decl.In
     }
 }
 
-pub fn deleteDeclExport(self: *Elf, decl_index: Module.Decl.Index, name: []const u8) void {
+pub fn deleteDeclExport(
+    self: *Elf,
+    decl_index: Module.Decl.Index,
+    name: InternPool.NullTerminatedString,
+) void {
     if (self.llvm_object) |_| return;
     const metadata = self.decls.getPtr(decl_index) orelse return;
-    const sym_index = metadata.getExportPtr(self, name) orelse return;
-    log.debug("deleting export '{s}'", .{name});
+    const mod = self.base.options.module.?;
+    const sym_index = metadata.getExportPtr(self, mod.intern_pool.stringToSlice(name)) orelse return;
     self.global_symbol_free_list.append(self.base.allocator, sym_index.*) catch {};
     self.global_symbols.items[sym_index.*].st_info = 0;
     sym_index.* = 0;
src/link/MachO.zig
@@ -40,6 +40,7 @@ const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
 const Md5 = std.crypto.hash.Md5;
 const Module = @import("../Module.zig");
+const InternPool = @import("../InternPool.zig");
 const Relocation = @import("MachO/Relocation.zig");
 const StringTable = @import("strtab.zig").StringTable;
 const TableSection = @import("table_section.zig").TableSection;
@@ -1921,8 +1922,7 @@ pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: Modu
     const unnamed_consts = gop.value_ptr;
 
     const decl = mod.declPtr(decl_index);
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer gpa.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     const name_str_index = blk: {
         const index = unnamed_consts.items.len;
@@ -2206,8 +2206,7 @@ fn updateThreadlocalVariable(self: *MachO, module: *Module, decl_index: Module.D
 
     const required_alignment = decl.getAlignment(mod);
 
-    const decl_name = try decl.getFullyQualifiedName(module);
-    defer gpa.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(module));
 
     const init_sym_name = try std.fmt.allocPrint(gpa, "{s}$tlv$init", .{decl_name});
     defer gpa.free(init_sym_name);
@@ -2306,8 +2305,7 @@ fn updateDeclCode(self: *MachO, decl_index: Module.Decl.Index, code: []u8) !u64
 
     const required_alignment = decl.getAlignment(mod);
 
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer gpa.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     const decl_metadata = self.decls.get(decl_index).?;
     const atom_index = decl_metadata.atom;
@@ -2403,12 +2401,14 @@ pub fn updateDeclExports(
     const decl_metadata = self.decls.getPtr(decl_index).?;
 
     for (exports) |exp| {
-        const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{exp.options.name});
+        const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{
+            mod.intern_pool.stringToSlice(exp.name),
+        });
         defer gpa.free(exp_name);
 
         log.debug("adding new export '{s}'", .{exp_name});
 
-        if (exp.options.section) |section_name| {
+        if (mod.intern_pool.stringToSliceUnwrap(exp.section)) |section_name| {
             if (!mem.eql(u8, section_name, "__text")) {
                 try mod.failed_exports.putNoClobber(
                     mod.gpa,
@@ -2424,7 +2424,7 @@ pub fn updateDeclExports(
             }
         }
 
-        if (exp.options.linkage == .LinkOnce) {
+        if (exp.linkage == .LinkOnce) {
             try mod.failed_exports.putNoClobber(
                 mod.gpa,
                 exp,
@@ -2453,7 +2453,7 @@ pub fn updateDeclExports(
             .n_value = decl_sym.n_value,
         };
 
-        switch (exp.options.linkage) {
+        switch (exp.linkage) {
             .Internal => {
                 // Symbol should be hidden, or in MachO lingo, private extern.
                 // We should also mark the symbol as Weak: n_desc == N_WEAK_DEF.
@@ -2488,12 +2488,17 @@ pub fn updateDeclExports(
     }
 }
 
-pub fn deleteDeclExport(self: *MachO, decl_index: Module.Decl.Index, name: []const u8) Allocator.Error!void {
+pub fn deleteDeclExport(
+    self: *MachO,
+    decl_index: Module.Decl.Index,
+    name: InternPool.NullTerminatedString,
+) Allocator.Error!void {
     if (self.llvm_object) |_| return;
     const metadata = self.decls.getPtr(decl_index) orelse return;
 
     const gpa = self.base.allocator;
-    const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{name});
+    const mod = self.base.options.module.?;
+    const exp_name = try std.fmt.allocPrint(gpa, "_{s}", .{mod.intern_pool.stringToSlice(name)});
     defer gpa.free(exp_name);
     const sym_index = metadata.getExportPtr(self, exp_name) orelse return;
 
src/link/Plan9.zig
@@ -287,7 +287,6 @@ pub fn updateFunc(self: *Plan9, mod: *Module, func_index: Module.Fn.Index, air:
     self.freeUnnamedConsts(decl_index);
 
     _ = try self.seeDecl(decl_index);
-    log.debug("codegen decl {*} ({s})", .{ decl, decl.name });
 
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
@@ -345,8 +344,7 @@ pub fn lowerUnnamedConst(self: *Plan9, tv: TypedValue, decl_index: Module.Decl.I
     }
     const unnamed_consts = gop.value_ptr;
 
-    const decl_name = try decl.getFullyQualifiedName(mod);
-    defer self.base.allocator.free(decl_name);
+    const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
     const index = unnamed_consts.items.len;
     // name is freed when the unnamed const is freed
@@ -403,8 +401,6 @@ pub fn updateDecl(self: *Plan9, mod: *Module, decl_index: Module.Decl.Index) !vo
 
     _ = try self.seeDecl(decl_index);
 
-    log.debug("codegen decl {*} ({s}) ({d})", .{ decl, decl.name, decl_index });
-
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
     const decl_val = if (decl.val.getVariable(mod)) |variable| variable.init.toValue() else decl.val;
@@ -435,7 +431,6 @@ fn updateFinish(self: *Plan9, decl_index: Module.Decl.Index) !void {
     const mod = self.base.options.module.?;
     const decl = mod.declPtr(decl_index);
     const is_fn = (decl.ty.zigTypeTag(mod) == .Fn);
-    log.debug("update the symbol table and got for decl {*} ({s})", .{ decl, decl.name });
     const sym_t: aout.Sym.Type = if (is_fn) .t else .d;
 
     const decl_block = self.getDeclBlockPtr(self.decls.get(decl_index).?.index);
@@ -446,7 +441,7 @@ fn updateFinish(self: *Plan9, decl_index: Module.Decl.Index) !void {
     const sym: aout.Sym = .{
         .value = undefined, // the value of stuff gets filled in in flushModule
         .type = decl_block.type,
-        .name = mem.span(decl.name),
+        .name = mod.intern_pool.stringToSlice(decl.name),
     };
 
     if (decl_block.sym_index) |s| {
@@ -567,10 +562,8 @@ pub fn flushModule(self: *Plan9, comp: *Compilation, prog_node: *std.Progress.No
             var it = fentry.value_ptr.functions.iterator();
             while (it.next()) |entry| {
                 const decl_index = entry.key_ptr.*;
-                const decl = mod.declPtr(decl_index);
                 const decl_block = self.getDeclBlockPtr(self.decls.get(decl_index).?.index);
                 const out = entry.value_ptr.*;
-                log.debug("write text decl {*} ({s}), lines {d} to {d}", .{ decl, decl.name, out.start_line + 1, out.end_line });
                 {
                     // connect the previous decl to the next
                     const delta_line = @intCast(i32, out.start_line) - @intCast(i32, linecount);
@@ -616,10 +609,8 @@ pub fn flushModule(self: *Plan9, comp: *Compilation, prog_node: *std.Progress.No
         var it = self.data_decl_table.iterator();
         while (it.next()) |entry| {
             const decl_index = entry.key_ptr.*;
-            const decl = mod.declPtr(decl_index);
             const decl_block = self.getDeclBlockPtr(self.decls.get(decl_index).?.index);
             const code = entry.value_ptr.*;
-            log.debug("write data decl {*} ({s})", .{ decl, decl.name });
 
             foff += code.len;
             iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len };
@@ -695,15 +686,12 @@ pub fn flushModule(self: *Plan9, comp: *Compilation, prog_node: *std.Progress.No
             const source_decl = mod.declPtr(source_decl_index);
             for (kv.value_ptr.items) |reloc| {
                 const target_decl_index = reloc.target;
-                const target_decl = mod.declPtr(target_decl_index);
                 const target_decl_block = self.getDeclBlock(self.decls.get(target_decl_index).?.index);
                 const target_decl_offset = target_decl_block.offset.?;
 
                 const offset = reloc.offset;
                 const addend = reloc.addend;
 
-                log.debug("relocating the address of '{s}' + {d} into '{s}' + {d}", .{ target_decl.name, addend, source_decl.name, offset });
-
                 const code = blk: {
                     const is_fn = source_decl.ty.zigTypeTag(mod) == .Fn;
                     if (is_fn) {
@@ -737,8 +725,9 @@ fn addDeclExports(
     const decl_block = self.getDeclBlock(metadata.index);
 
     for (exports) |exp| {
+        const exp_name = mod.intern_pool.stringToSlice(exp.name);
         // plan9 does not support custom sections
-        if (exp.options.section) |section_name| {
+        if (mod.intern_pool.stringToSliceUnwrap(exp.section)) |section_name| {
             if (!mem.eql(u8, section_name, ".text") or !mem.eql(u8, section_name, ".data")) {
                 try mod.failed_exports.put(mod.gpa, exp, try Module.ErrorMsg.create(
                     self.base.allocator,
@@ -752,10 +741,10 @@ fn addDeclExports(
         const sym = .{
             .value = decl_block.offset.?,
             .type = decl_block.type.toGlobal(),
-            .name = exp.options.name,
+            .name = exp_name,
         };
 
-        if (metadata.getExport(self, exp.options.name)) |i| {
+        if (metadata.getExport(self, exp_name)) |i| {
             self.syms.items[i] = sym;
         } else {
             try self.syms.append(self.base.allocator, sym);
@@ -956,7 +945,10 @@ pub fn writeSym(self: *Plan9, w: anytype, sym: aout.Sym) !void {
     try w.writeAll(sym.name);
     try w.writeByte(0);
 }
+
 pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void {
+    const mod = self.base.options.module.?;
+    const ip = &mod.intern_pool;
     const writer = buf.writer();
     // write the f symbols
     {
@@ -980,7 +972,7 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void {
             const sym = self.syms.items[decl_block.sym_index.?];
             try self.writeSym(writer, sym);
             if (self.base.options.module.?.decl_exports.get(decl_index)) |exports| {
-                for (exports.items) |e| if (decl_metadata.getExport(self, e.options.name)) |exp_i| {
+                for (exports.items) |e| if (decl_metadata.getExport(self, ip.stringToSlice(e.name))) |exp_i| {
                     try self.writeSym(writer, self.syms.items[exp_i]);
                 };
             }
@@ -1006,7 +998,7 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void {
                 const sym = self.syms.items[decl_block.sym_index.?];
                 try self.writeSym(writer, sym);
                 if (self.base.options.module.?.decl_exports.get(decl_index)) |exports| {
-                    for (exports.items) |e| if (decl_metadata.getExport(self, e.options.name)) |exp_i| {
+                    for (exports.items) |e| if (decl_metadata.getExport(self, ip.stringToSlice(e.name))) |exp_i| {
                         const s = self.syms.items[exp_i];
                         if (mem.eql(u8, s.name, "_start"))
                             self.entry_val = s.value;
src/link/SpirV.zig
@@ -147,7 +147,7 @@ pub fn updateDeclExports(
         const spv_decl_index = entry.value_ptr.*;
 
         for (exports) |exp| {
-            try self.spv.declareEntryPoint(spv_decl_index, exp.options.name);
+            try self.spv.declareEntryPoint(spv_decl_index, mod.intern_pool.stringToSlice(exp.name));
         }
     }
 
@@ -190,7 +190,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No
     var error_info = std.ArrayList(u8).init(self.spv.arena);
     try error_info.appendSlice("zig_errors");
     const module = self.base.options.module.?;
-    for (module.error_name_list.items) |name| {
+    for (module.global_error_set.keys()) |name_nts| {
+        const name = module.intern_pool.stringToSlice(name_nts);
         // Errors can contain pretty much any character - to encode them in a string we must escape
         // them somehow. Easiest here is to use some established scheme, one which also preseves the
         // name if it contains no strange characters is nice for debugging. URI encoding fits the bill.
src/link/Wasm.zig
@@ -1416,7 +1416,7 @@ pub fn updateDecl(wasm: *Wasm, mod: *Module, decl_index: Module.Decl.Index) !voi
 
     if (decl.isExtern(mod)) {
         const variable = decl.getOwnedVariable(mod).?;
-        const name = mem.sliceTo(decl.name, 0);
+        const name = mod.intern_pool.stringToSlice(decl.name);
         const lib_name = mod.intern_pool.stringToSliceUnwrap(variable.lib_name);
         return wasm.addOrUpdateImport(name, atom.sym_index, lib_name, null);
     }
@@ -1453,8 +1453,7 @@ pub fn updateDeclLineNumber(wasm: *Wasm, mod: *Module, decl_index: Module.Decl.I
         defer tracy.end();
 
         const decl = mod.declPtr(decl_index);
-        const decl_name = try decl.getFullyQualifiedName(mod);
-        defer wasm.base.allocator.free(decl_name);
+        const decl_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
 
         log.debug("updateDeclLineNumber {s}{*}", .{ decl_name, decl });
         try dw.updateDeclLineNumber(mod, decl_index);
@@ -1467,8 +1466,7 @@ fn finishUpdateDecl(wasm: *Wasm, decl_index: Module.Decl.Index, code: []const u8
     const atom_index = wasm.decls.get(decl_index).?;
     const atom = wasm.getAtomPtr(atom_index);
     const symbol = &wasm.symbols.items[atom.sym_index];
-    const full_name = try decl.getFullyQualifiedName(mod);
-    defer wasm.base.allocator.free(full_name);
+    const full_name = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
     symbol.name = try wasm.string_table.put(wasm.base.allocator, full_name);
     try atom.code.appendSlice(wasm.base.allocator, code);
     try wasm.resolved_symbols.put(wasm.base.allocator, atom.symbolLoc(), {});
@@ -1535,9 +1533,10 @@ pub fn lowerUnnamedConst(wasm: *Wasm, tv: TypedValue, decl_index: Module.Decl.In
     const parent_atom = wasm.getAtomPtr(parent_atom_index);
     const local_index = parent_atom.locals.items.len;
     try parent_atom.locals.append(wasm.base.allocator, atom_index);
-    const fqdn = try decl.getFullyQualifiedName(mod);
-    defer wasm.base.allocator.free(fqdn);
-    const name = try std.fmt.allocPrintZ(wasm.base.allocator, "__unnamed_{s}_{d}", .{ fqdn, local_index });
+    const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
+    const name = try std.fmt.allocPrintZ(wasm.base.allocator, "__unnamed_{s}_{d}", .{
+        fqn, local_index,
+    });
     defer wasm.base.allocator.free(name);
     var value_bytes = std.ArrayList(u8).init(wasm.base.allocator);
     defer value_bytes.deinit();
@@ -1690,11 +1689,12 @@ pub fn updateDeclExports(
     const decl = mod.declPtr(decl_index);
     const atom_index = try wasm.getOrCreateAtomForDecl(decl_index);
     const atom = wasm.getAtom(atom_index);
+    const gpa = mod.gpa;
 
     for (exports) |exp| {
-        if (exp.options.section) |section| {
-            try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
-                mod.gpa,
+        if (mod.intern_pool.stringToSliceUnwrap(exp.section)) |section| {
+            try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
+                gpa,
                 decl.srcLoc(mod),
                 "Unimplemented: ExportOptions.section '{s}'",
                 .{section},
@@ -1702,24 +1702,24 @@ pub fn updateDeclExports(
             continue;
         }
 
-        const export_name = try wasm.string_table.put(wasm.base.allocator, exp.options.name);
+        const export_name = try wasm.string_table.put(wasm.base.allocator, mod.intern_pool.stringToSlice(exp.name));
         if (wasm.globals.getPtr(export_name)) |existing_loc| {
             if (existing_loc.index == atom.sym_index) continue;
             const existing_sym: Symbol = existing_loc.getSymbol(wasm).*;
 
-            const exp_is_weak = exp.options.linkage == .Internal or exp.options.linkage == .Weak;
+            const exp_is_weak = exp.linkage == .Internal or exp.linkage == .Weak;
             // When both the to-be-exported symbol and the already existing symbol
             // are strong symbols, we have a linker error.
             // In the other case we replace one with the other.
             if (!exp_is_weak and !existing_sym.isWeak()) {
-                try mod.failed_exports.put(mod.gpa, exp, try Module.ErrorMsg.create(
-                    mod.gpa,
+                try mod.failed_exports.put(gpa, exp, try Module.ErrorMsg.create(
+                    gpa,
                     decl.srcLoc(mod),
                     \\LinkError: symbol '{s}' defined multiple times
                     \\  first definition in '{s}'
                     \\  next definition in '{s}'
                 ,
-                    .{ exp.options.name, wasm.name, wasm.name },
+                    .{ mod.intern_pool.stringToSlice(exp.name), wasm.name, wasm.name },
                 ));
                 continue;
             } else if (exp_is_weak) {
@@ -1736,7 +1736,7 @@ pub fn updateDeclExports(
         const exported_atom = wasm.getAtom(exported_atom_index);
         const sym_loc = exported_atom.symbolLoc();
         const symbol = sym_loc.getSymbol(wasm);
-        switch (exp.options.linkage) {
+        switch (exp.linkage) {
             .Internal => {
                 symbol.setFlag(.WASM_SYM_VISIBILITY_HIDDEN);
             },
@@ -1745,8 +1745,8 @@ pub fn updateDeclExports(
             },
             .Strong => {}, // symbols are strong by default
             .LinkOnce => {
-                try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create(
-                    mod.gpa,
+                try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create(
+                    gpa,
                     decl.srcLoc(mod),
                     "Unimplemented: LinkOnce",
                     .{},
@@ -1755,7 +1755,7 @@ pub fn updateDeclExports(
             },
         }
         // Ensure the symbol will be exported using the given name
-        if (!mem.eql(u8, exp.options.name, sym_loc.getName(wasm))) {
+        if (!mod.intern_pool.stringEqlSlice(exp.name, sym_loc.getName(wasm))) {
             try wasm.export_names.put(wasm.base.allocator, sym_loc, export_name);
         }
 
@@ -1769,7 +1769,7 @@ pub fn updateDeclExports(
 
         // if the symbol was previously undefined, remove it as an import
         _ = wasm.imports.remove(sym_loc);
-        _ = wasm.undefs.swapRemove(exp.options.name);
+        _ = wasm.undefs.swapRemove(mod.intern_pool.stringToSlice(exp.name));
     }
 }
 
@@ -2987,7 +2987,8 @@ fn populateErrorNameTable(wasm: *Wasm) !void {
     // Addend for each relocation to the table
     var addend: u32 = 0;
     const mod = wasm.base.options.module.?;
-    for (mod.error_name_list.items) |error_name| {
+    for (mod.global_error_set.keys()) |error_name_nts| {
+        const error_name = mod.intern_pool.stringToSlice(error_name_nts);
         const len = @intCast(u32, error_name.len + 1); // names are 0-termianted
 
         const slice_ty = Type.slice_const_u8_sentinel_0;
src/codegen.zig
@@ -142,11 +142,12 @@ pub fn generateLazySymbol(
 
     if (lazy_sym.ty.isAnyError(mod)) {
         alignment.* = 4;
-        const err_names = mod.error_name_list.items;
+        const err_names = mod.global_error_set.keys();
         mem.writeInt(u32, try code.addManyAsArray(4), @intCast(u32, err_names.len), endian);
         var offset = code.items.len;
         try code.resize((1 + err_names.len + 1) * 4);
-        for (err_names) |err_name| {
+        for (err_names) |err_name_nts| {
+            const err_name = mod.intern_pool.stringToSlice(err_name_nts);
             mem.writeInt(u32, code.items[offset..][0..4], @intCast(u32, code.items.len), endian);
             offset += 4;
             try code.ensureUnusedCapacity(err_name.len + 1);
@@ -251,15 +252,13 @@ pub fn generateSymbol(
             val.writeTwosComplement(try code.addManyAsSlice(abi_size), endian);
         },
         .err => |err| {
-            const name = mod.intern_pool.stringToSlice(err.name);
-            const kv = try mod.getErrorValue(name);
-            try code.writer().writeInt(u16, @intCast(u16, kv.value), endian);
+            const int = try mod.getErrorValue(err.name);
+            try code.writer().writeInt(u16, @intCast(u16, int), endian);
         },
         .error_union => |error_union| {
             const payload_ty = typed_value.ty.errorUnionPayload(mod);
-
             const err_val = switch (error_union.val) {
-                .err_name => |err_name| @intCast(u16, (try mod.getErrorValue(mod.intern_pool.stringToSlice(err_name))).value),
+                .err_name => |err_name| @intCast(u16, try mod.getErrorValue(err_name)),
                 .payload => @as(u16, 0),
             };
 
@@ -974,11 +973,8 @@ pub fn genTypedValue(
             }, owner_decl_index);
         },
         .ErrorSet => {
-            const err_name = mod.intern_pool.stringToSlice(
-                mod.intern_pool.indexToKey(typed_value.val.toIntern()).err.name,
-            );
-            const global_error_set = mod.global_error_set;
-            const error_index = global_error_set.get(err_name).?;
+            const err_name = mod.intern_pool.indexToKey(typed_value.val.toIntern()).err.name;
+            const error_index = mod.global_error_set.getIndex(err_name).?;
             return GenResult.mcv(.{ .immediate = error_index });
         },
         .ErrorUnion => {
src/Compilation.zig
@@ -1317,7 +1317,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
                 .global_zir_cache = global_zir_cache,
                 .local_zir_cache = local_zir_cache,
                 .emit_h = emit_h,
-                .error_name_list = .{},
+                .tmp_hack_arena = std.heap.ArenaAllocator.init(gpa),
             };
             try module.init();
 
@@ -2627,7 +2627,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
             var it = module.failed_files.iterator();
             while (it.next()) |entry| {
                 if (entry.value_ptr.*) |msg| {
-                    try addModuleErrorMsg(&bundle, msg.*);
+                    try addModuleErrorMsg(module, &bundle, msg.*);
                 } else {
                     // Must be ZIR errors. Note that this may include AST errors.
                     // addZirErrorMessages asserts that the tree is loaded.
@@ -2640,7 +2640,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
             var it = module.failed_embed_files.iterator();
             while (it.next()) |entry| {
                 const msg = entry.value_ptr.*;
-                try addModuleErrorMsg(&bundle, msg.*);
+                try addModuleErrorMsg(module, &bundle, msg.*);
             }
         }
         {
@@ -2650,7 +2650,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                 // Skip errors for Decls within files that had a parse failure.
                 // We'll try again once parsing succeeds.
                 if (module.declFileScope(decl_index).okToReportErrors()) {
-                    try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
+                    try addModuleErrorMsg(module, &bundle, entry.value_ptr.*.*);
                     if (module.cimport_errors.get(entry.key_ptr.*)) |cimport_errors| for (cimport_errors) |c_error| {
                         try bundle.addRootErrorMessage(.{
                             .msg = try bundle.addString(std.mem.span(c_error.msg)),
@@ -2675,12 +2675,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                 // Skip errors for Decls within files that had a parse failure.
                 // We'll try again once parsing succeeds.
                 if (module.declFileScope(decl_index).okToReportErrors()) {
-                    try addModuleErrorMsg(&bundle, entry.value_ptr.*.*);
+                    try addModuleErrorMsg(module, &bundle, entry.value_ptr.*.*);
                 }
             }
         }
         for (module.failed_exports.values()) |value| {
-            try addModuleErrorMsg(&bundle, value.*);
+            try addModuleErrorMsg(module, &bundle, value.*);
         }
     }
 
@@ -2728,7 +2728,7 @@ pub fn getAllErrorsAlloc(self: *Compilation) !ErrorBundle {
                 };
             }
 
-            try addModuleErrorMsg(&bundle, err_msg);
+            try addModuleErrorMsg(module, &bundle, err_msg);
         }
     }
 
@@ -2784,8 +2784,9 @@ pub const ErrorNoteHashContext = struct {
     }
 };
 
-pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void {
+pub fn addModuleErrorMsg(mod: *Module, eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg) !void {
     const gpa = eb.gpa;
+    const ip = &mod.intern_pool;
     const err_source = module_err_msg.src_loc.file_scope.getSource(gpa) catch |err| {
         const file_path = try module_err_msg.src_loc.file_scope.fullPath(gpa);
         defer gpa.free(file_path);
@@ -2811,7 +2812,7 @@ pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg)
                 .src_loc = .none,
             });
             break;
-        } else if (module_reference.decl == null) {
+        } else if (module_reference.decl == .none) {
             try ref_traces.append(gpa, .{
                 .decl_name = 0,
                 .src_loc = .none,
@@ -2824,7 +2825,7 @@ pub fn addModuleErrorMsg(eb: *ErrorBundle.Wip, module_err_msg: Module.ErrorMsg)
         const rt_file_path = try module_reference.src_loc.file_scope.fullPath(gpa);
         defer gpa.free(rt_file_path);
         try ref_traces.append(gpa, .{
-            .decl_name = try eb.addString(std.mem.sliceTo(module_reference.decl.?, 0)),
+            .decl_name = try eb.addString(ip.stringToSliceUnwrap(module_reference.decl).?),
             .src_loc = try eb.addSourceLocation(.{
                 .src_path = try eb.addString(rt_file_path),
                 .span_start = span.start,
src/InternPool.zig
@@ -124,6 +124,8 @@ pub const String = enum(u32) {
 
 /// An index into `string_bytes`.
 pub const NullTerminatedString = enum(u32) {
+    /// This is distinct from `none` - it is a valid index that represents empty string.
+    empty = 0,
     _,
 
     pub fn toString(self: NullTerminatedString) String {
@@ -157,6 +159,8 @@ pub const NullTerminatedString = enum(u32) {
 
 /// An index into `string_bytes` which might be `none`.
 pub const OptionalNullTerminatedString = enum(u32) {
+    /// This is distinct from `none` - it is a valid index that represents empty string.
+    empty = 0,
     none = std.math.maxInt(u32),
     _,
 
@@ -2447,6 +2451,9 @@ pub const MemoizedCall = struct {
 pub fn init(ip: *InternPool, gpa: Allocator) !void {
     assert(ip.items.len == 0);
 
+    // Reserve string index 0 for an empty string.
+    assert((try ip.getOrPutString(gpa, "")) == .empty);
+
     // So that we can use `catch unreachable` below.
     try ip.items.ensureUnusedCapacity(gpa, static_keys.len);
     try ip.map.ensureUnusedCapacity(gpa, static_keys.len);
@@ -5222,6 +5229,28 @@ pub fn getOrPutString(
     return ip.getOrPutTrailingString(gpa, s.len + 1);
 }
 
+pub fn getOrPutStringFmt(
+    ip: *InternPool,
+    gpa: Allocator,
+    comptime format: []const u8,
+    args: anytype,
+) Allocator.Error!NullTerminatedString {
+    const start = ip.string_bytes.items.len;
+    try ip.string_bytes.writer(gpa).print(format, args);
+    try ip.string_bytes.append(gpa, 0);
+    return ip.getOrPutTrailingString(gpa, ip.string_bytes.items.len - start);
+}
+
+pub fn getOrPutStringOpt(
+    ip: *InternPool,
+    gpa: Allocator,
+    optional_string: ?[]const u8,
+) Allocator.Error!OptionalNullTerminatedString {
+    const s = optional_string orelse return .none;
+    const interned = try getOrPutString(ip, gpa, s);
+    return interned.toOptional();
+}
+
 /// Uses the last len bytes of ip.string_bytes as the key.
 pub fn getOrPutTrailingString(
     ip: *InternPool,
@@ -5273,6 +5302,10 @@ pub fn stringToSliceUnwrap(ip: *const InternPool, s: OptionalNullTerminatedStrin
     return ip.stringToSlice(s.unwrap() orelse return null);
 }
 
+pub fn stringEqlSlice(ip: *const InternPool, a: NullTerminatedString, b: []const u8) bool {
+    return std.mem.eql(u8, stringToSlice(ip, a), b);
+}
+
 pub fn typeOf(ip: *const InternPool, index: Index) Index {
     // This optimization of static keys is required so that typeOf can be called
     // on static keys that haven't been added yet during static key initialization.
src/link.zig
@@ -502,8 +502,6 @@ pub const File = struct {
     /// of the final binary.
     pub fn lowerUnnamedConst(base: *File, tv: TypedValue, decl_index: Module.Decl.Index) UpdateDeclError!u32 {
         if (build_options.only_c) @compileError("unreachable");
-        const decl = base.options.module.?.declPtr(decl_index);
-        log.debug("lowerUnnamedConst {*} ({s})", .{ decl, decl.name });
         switch (base.tag) {
             // zig fmt: off
             .coff  => return @fieldParentPtr(Coff,  "base", base).lowerUnnamedConst(tv, decl_index),
@@ -543,7 +541,6 @@ pub const File = struct {
     /// May be called before or after updateDeclExports for any given Decl.
     pub fn updateDecl(base: *File, module: *Module, decl_index: Module.Decl.Index) UpdateDeclError!void {
         const decl = module.declPtr(decl_index);
-        log.debug("updateDecl {*} ({s}), type={}", .{ decl, decl.name, decl.ty.fmt(module) });
         assert(decl.has_tv);
         if (build_options.only_c) {
             assert(base.tag == .c);
@@ -566,10 +563,6 @@ pub const File = struct {
     /// May be called before or after updateDeclExports for any given Decl.
     pub fn updateFunc(base: *File, module: *Module, func_index: Module.Fn.Index, air: Air, liveness: Liveness) UpdateDeclError!void {
         const func = module.funcPtr(func_index);
-        const owner_decl = module.declPtr(func.owner_decl);
-        log.debug("updateFunc {*} ({s}), type={}", .{
-            owner_decl, owner_decl.name, owner_decl.ty.fmt(module),
-        });
         if (build_options.only_c) {
             assert(base.tag == .c);
             return @fieldParentPtr(C, "base", base).updateFunc(module, func, air, liveness);
@@ -590,9 +583,6 @@ pub const File = struct {
 
     pub fn updateDeclLineNumber(base: *File, module: *Module, decl_index: Module.Decl.Index) UpdateDeclError!void {
         const decl = module.declPtr(decl_index);
-        log.debug("updateDeclLineNumber {*} ({s}), line={}", .{
-            decl, decl.name, decl.src_line + 1,
-        });
         assert(decl.has_tv);
         if (build_options.only_c) {
             assert(base.tag == .c);
@@ -868,7 +858,6 @@ pub const File = struct {
         exports: []const *Module.Export,
     ) UpdateDeclExportsError!void {
         const decl = module.declPtr(decl_index);
-        log.debug("updateDeclExports {*} ({s})", .{ decl, decl.name });
         assert(decl.has_tv);
         if (build_options.only_c) {
             assert(base.tag == .c);
src/Module.zig
@@ -88,6 +88,14 @@ embed_table: std.StringHashMapUnmanaged(*EmbedFile) = .{},
 /// Stores all Type and Value objects; periodically garbage collected.
 intern_pool: InternPool = .{},
 
+/// To be eliminated in a future commit by moving more data into InternPool.
+/// Current uses that must be eliminated:
+/// * Struct comptime_args
+/// * Struct optimized_order
+/// * Union fields
+/// 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) = .{},
 
@@ -125,13 +133,8 @@ cimport_errors: std.AutoArrayHashMapUnmanaged(Decl.Index, []CImportError) = .{},
 /// contains Decls that need to be deleted if they end up having no references to them.
 deletion_set: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{},
 
-/// Error tags and their values, tag names are duped with mod.gpa.
-/// Corresponds with `error_name_list`.
-global_error_set: std.StringHashMapUnmanaged(ErrorInt) = .{},
-
-/// ErrorInt -> []const u8 for fast lookups for @intToError at comptime
-/// Corresponds with `global_error_set`.
-error_name_list: ArrayListUnmanaged([]const u8),
+/// Key is the error name, index is the error tag value. Index 0 has a length-0 string.
+global_error_set: GlobalErrorSet = .{},
 
 /// Incrementing integer used to compare against the corresponding Decl
 /// field to determine whether a Decl's status applies to an ongoing update, or a
@@ -182,6 +185,8 @@ reference_table: std.AutoHashMapUnmanaged(Decl.Index, struct {
     src: LazySrcLoc,
 }) = .{},
 
+pub const GlobalErrorSet = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void);
+
 pub const CImportError = struct {
     offset: u32,
     line: u32,
@@ -248,7 +253,11 @@ pub const GlobalEmitH = struct {
 pub const ErrorInt = u32;
 
 pub const Export = struct {
-    options: std.builtin.ExportOptions,
+    name: InternPool.NullTerminatedString,
+    linkage: std.builtin.GlobalLinkage,
+    section: InternPool.OptionalNullTerminatedString,
+    visibility: std.builtin.SymbolVisibility,
+
     src: LazySrcLoc,
     /// The Decl that performs the export. Note that this is *not* the Decl being exported.
     owner_decl: Decl.Index,
@@ -392,8 +401,7 @@ const ValueArena = struct {
 };
 
 pub const Decl = struct {
-    /// Allocated with Module's allocator; outlives the ZIR code.
-    name: [*:0]const u8,
+    name: InternPool.NullTerminatedString,
     /// The most recent Type of the Decl after a successful semantic analysis.
     /// Populated when `has_tv`.
     ty: Type,
@@ -401,15 +409,11 @@ pub const Decl = struct {
     /// Populated when `has_tv`.
     val: Value,
     /// Populated when `has_tv`.
-    /// Points to memory inside value_arena.
-    @"linksection": ?[*:0]const u8,
+    @"linksection": InternPool.OptionalNullTerminatedString,
     /// Populated when `has_tv`.
     @"align": u32,
     /// Populated when `has_tv`.
     @"addrspace": std.builtin.AddressSpace,
-    /// The memory for ty, val, align, linksection, and captures.
-    /// If this is `null` then there is no memory management needed.
-    value_arena: ?*ValueArena = null,
     /// The direct parent namespace of the Decl.
     /// Reference to externally owned memory.
     /// In the case of the Decl corresponding to a file, this is
@@ -564,13 +568,7 @@ pub const Decl = struct {
         function_body,
     };
 
-    pub fn clearName(decl: *Decl, gpa: Allocator) void {
-        gpa.free(mem.sliceTo(decl.name, 0));
-        decl.name = undefined;
-    }
-
     pub fn clearValues(decl: *Decl, mod: *Module) void {
-        const gpa = mod.gpa;
         if (decl.getOwnedFunctionIndex(mod).unwrap()) |func| {
             _ = mod.align_stack_fns.remove(func);
             if (mod.funcPtr(func).comptime_args != null) {
@@ -579,19 +577,6 @@ pub const Decl = struct {
             mod.destroyFunc(func);
         }
         _ = mod.memoized_decls.remove(decl.val.ip_index);
-        if (decl.value_arena) |value_arena| {
-            value_arena.deinit(gpa);
-            decl.value_arena = null;
-            decl.has_tv = false;
-            decl.owns_tv = false;
-        }
-    }
-
-    pub fn finalizeNewArena(decl: *Decl, arena: *std.heap.ArenaAllocator) !void {
-        assert(decl.value_arena == null);
-        const value_arena = try arena.allocator().create(ValueArena);
-        value_arena.* = .{ .state = arena.state };
-        decl.value_arena = value_arena;
     }
 
     /// This name is relative to the containing namespace of the decl.
@@ -692,7 +677,7 @@ pub const Decl = struct {
     }
 
     pub fn renderFullyQualifiedName(decl: Decl, mod: *Module, writer: anytype) !void {
-        const unqualified_name = mem.sliceTo(decl.name, 0);
+        const unqualified_name = mod.intern_pool.stringToSlice(decl.name);
         if (decl.name_fully_qualified) {
             return writer.writeAll(unqualified_name);
         }
@@ -700,24 +685,27 @@ pub const Decl = struct {
     }
 
     pub fn renderFullyQualifiedDebugName(decl: Decl, mod: *Module, writer: anytype) !void {
-        const unqualified_name = mem.sliceTo(decl.name, 0);
+        const unqualified_name = mod.intern_pool.stringToSlice(decl.name);
         return mod.namespacePtr(decl.src_namespace).renderFullyQualifiedDebugName(mod, unqualified_name, writer);
     }
 
-    pub fn getFullyQualifiedName(decl: Decl, mod: *Module) ![:0]u8 {
-        var buffer = std.ArrayList(u8).init(mod.gpa);
-        defer buffer.deinit();
-        try decl.renderFullyQualifiedName(mod, buffer.writer());
+    pub fn getFullyQualifiedName(decl: Decl, mod: *Module) !InternPool.NullTerminatedString {
+        const gpa = mod.gpa;
+        const ip = &mod.intern_pool;
+        const start = ip.string_bytes.items.len;
+        try decl.renderFullyQualifiedName(mod, ip.string_bytes.writer(gpa));
 
         // Sanitize the name for nvptx which is more restrictive.
+        // TODO This should be handled by the backend, not the frontend. Have a
+        // look at how the C backend does it for inspiration.
         if (mod.comp.bin_file.options.target.cpu.arch.isNvptx()) {
-            for (buffer.items) |*byte| switch (byte.*) {
+            for (ip.string_bytes.items[start..]) |*byte| switch (byte.*) {
                 '{', '}', '*', '[', ']', '(', ')', ',', ' ', '\'' => byte.* = '_',
                 else => {},
             };
         }
 
-        return buffer.toOwnedSliceSentinel(0);
+        return ip.getOrPutTrailingString(gpa, ip.string_bytes.items.len - start);
     }
 
     pub fn typedValue(decl: Decl) error{AnalysisFail}!TypedValue {
@@ -804,11 +792,11 @@ pub const Decl = struct {
 
     pub fn dump(decl: *Decl) void {
         const loc = std.zig.findLineColumn(decl.scope.source.bytes, decl.src);
-        std.debug.print("{s}:{d}:{d} name={s} status={s}", .{
+        std.debug.print("{s}:{d}:{d} name={d} status={s}", .{
             decl.scope.sub_file_path,
             loc.line + 1,
             loc.column + 1,
-            mem.sliceTo(decl.name, 0),
+            @enumToInt(decl.name),
             @tagName(decl.analysis),
         });
         if (decl.has_tv) {
@@ -922,15 +910,15 @@ pub const Struct = struct {
         }
     };
 
-    pub const Fields = std.StringArrayHashMapUnmanaged(Field);
+    pub const Fields = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, Field);
 
     /// The `Type` and `Value` memory is owned by the arena of the Struct's owner_decl.
     pub const Field = struct {
         /// Uses `noreturn` to indicate `anytype`.
         /// undefined until `status` is >= `have_field_types`.
         ty: Type,
-        /// Uses `unreachable_value` to indicate no default.
-        default_val: Value,
+        /// Uses `none` to indicate no default.
+        default_val: InternPool.Index,
         /// Zero means to use the ABI alignment of the type.
         abi_align: u32,
         /// undefined until `status` is `have_layout`.
@@ -982,7 +970,7 @@ pub const Struct = struct {
     /// runtime version of the struct.
     pub const omitted_field = std.math.maxInt(u32);
 
-    pub fn getFullyQualifiedName(s: *Struct, mod: *Module) ![:0]u8 {
+    pub fn getFullyQualifiedName(s: *Struct, mod: *Module) !InternPool.NullTerminatedString {
         return mod.declPtr(s.owner_decl).getFullyQualifiedName(mod);
     }
 
@@ -1141,9 +1129,9 @@ pub const Union = struct {
         }
     };
 
-    pub const Fields = std.StringArrayHashMapUnmanaged(Field);
+    pub const Fields = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, Field);
 
-    pub fn getFullyQualifiedName(s: *Union, mod: *Module) ![:0]u8 {
+    pub fn getFullyQualifiedName(s: *Union, mod: *Module) !InternPool.NullTerminatedString {
         return mod.declPtr(s.owner_decl).getFullyQualifiedName(mod);
     }
 
@@ -1569,15 +1557,15 @@ pub const Fn = struct {
 pub const DeclAdapter = struct {
     mod: *Module,
 
-    pub fn hash(self: @This(), s: []const u8) u32 {
+    pub fn hash(self: @This(), s: InternPool.NullTerminatedString) u32 {
         _ = self;
-        return @truncate(u32, std.hash.Wyhash.hash(0, s));
+        return std.hash.uint32(@enumToInt(s));
     }
 
-    pub fn eql(self: @This(), a: []const u8, b_decl_index: Decl.Index, b_index: usize) bool {
+    pub fn eql(self: @This(), a: InternPool.NullTerminatedString, b_decl_index: Decl.Index, b_index: usize) bool {
         _ = b_index;
         const b_decl = self.mod.declPtr(b_decl_index);
-        return mem.eql(u8, a, mem.sliceTo(b_decl.name, 0));
+        return a == b_decl.name;
     }
 };
 
@@ -1628,16 +1616,14 @@ pub const Namespace = struct {
 
         pub fn hash(ctx: @This(), decl_index: Decl.Index) u32 {
             const decl = ctx.module.declPtr(decl_index);
-            return @truncate(u32, std.hash.Wyhash.hash(0, mem.sliceTo(decl.name, 0)));
+            return std.hash.uint32(@enumToInt(decl.name));
         }
 
         pub fn eql(ctx: @This(), a_decl_index: Decl.Index, b_decl_index: Decl.Index, b_index: usize) bool {
             _ = b_index;
             const a_decl = ctx.module.declPtr(a_decl_index);
             const b_decl = ctx.module.declPtr(b_decl_index);
-            const a_name = mem.sliceTo(a_decl.name, 0);
-            const b_name = mem.sliceTo(b_decl.name, 0);
-            return mem.eql(u8, a_name, b_name);
+            return a_decl.name == b_decl.name;
         }
     };
 
@@ -1649,8 +1635,6 @@ pub const Namespace = struct {
     pub fn destroyDecls(ns: *Namespace, mod: *Module) void {
         const gpa = mod.gpa;
 
-        log.debug("destroyDecls {*}", .{ns});
-
         var decls = ns.decls;
         ns.decls = .{};
 
@@ -1676,8 +1660,6 @@ pub const Namespace = struct {
     ) !void {
         const gpa = mod.gpa;
 
-        log.debug("deleteAllDecls {*}", .{ns});
-
         var decls = ns.decls;
         ns.decls = .{};
 
@@ -1712,7 +1694,8 @@ pub const Namespace = struct {
         if (ns.parent.unwrap()) |parent| {
             const decl_index = ns.getDeclIndex(mod);
             const decl = mod.declPtr(decl_index);
-            try mod.namespacePtr(parent).renderFullyQualifiedName(mod, mem.sliceTo(decl.name, 0), writer);
+            const decl_name = mod.intern_pool.stringToSlice(decl.name);
+            try mod.namespacePtr(parent).renderFullyQualifiedName(mod, decl_name, writer);
         } else {
             try ns.file_scope.renderFullyQualifiedName(writer);
         }
@@ -1733,7 +1716,8 @@ pub const Namespace = struct {
         if (ns.parent.unwrap()) |parent| {
             const decl_index = ns.getDeclIndex(mod);
             const decl = mod.declPtr(decl_index);
-            try mod.namespacePtr(parent).renderFullyQualifiedDebugName(mod, mem.sliceTo(decl.name, 0), writer);
+            const decl_name = mod.intern_pool.stringToSlice(decl.name);
+            try mod.namespacePtr(parent).renderFullyQualifiedDebugName(mod, decl_name, writer);
         } else {
             try ns.file_scope.renderFullyQualifiedDebugName(writer);
             separator_char = ':';
@@ -1927,11 +1911,11 @@ pub const File = struct {
         };
     }
 
-    pub fn fullyQualifiedNameZ(file: File, gpa: Allocator) ![:0]u8 {
-        var buf = std.ArrayList(u8).init(gpa);
-        defer buf.deinit();
-        try file.renderFullyQualifiedName(buf.writer());
-        return buf.toOwnedSliceSentinel(0);
+    pub fn fullyQualifiedName(file: File, mod: *Module) !InternPool.NullTerminatedString {
+        const ip = &mod.intern_pool;
+        const start = ip.string_bytes.items.len;
+        try file.renderFullyQualifiedName(ip.string_bytes.writer(mod.gpa));
+        return ip.getOrPutTrailingString(mod.gpa, ip.string_bytes.items.len - start);
     }
 
     /// Returns the full path to this file relative to its package.
@@ -2055,7 +2039,7 @@ pub const ErrorMsg = struct {
     reference_trace: []Trace = &.{},
 
     pub const Trace = struct {
-        decl: ?[*:0]const u8,
+        decl: InternPool.OptionalNullTerminatedString,
         src_loc: SrcLoc,
         hidden: u32 = 0,
     };
@@ -3180,8 +3164,8 @@ pub const CompileError = error{
 
 pub fn init(mod: *Module) !void {
     const gpa = mod.gpa;
-    try mod.error_name_list.append(gpa, "(no error)");
     try mod.intern_pool.init(gpa);
+    try mod.global_error_set.put(gpa, .empty, {});
 }
 
 pub fn deinit(mod: *Module) void {
@@ -3282,15 +3266,8 @@ pub fn deinit(mod: *Module) void {
     }
     mod.export_owners.deinit(gpa);
 
-    {
-        var it = mod.global_error_set.keyIterator();
-        while (it.next()) |key| {
-            gpa.free(key.*);
-        }
-        mod.global_error_set.deinit(gpa);
-    }
+    mod.global_error_set.deinit(gpa);
 
-    mod.error_name_list.deinit(gpa);
     mod.test_functions.deinit(gpa);
     mod.align_stack_fns.deinit(gpa);
     mod.monomorphed_funcs.deinit(gpa);
@@ -3305,13 +3282,13 @@ pub fn deinit(mod: *Module) void {
 
     mod.memoized_decls.deinit(gpa);
     mod.intern_pool.deinit(gpa);
+    mod.tmp_hack_arena.deinit();
 }
 
 pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
     const gpa = mod.gpa;
     {
         const decl = mod.declPtr(decl_index);
-        log.debug("destroy {*} ({s})", .{ decl, decl.name });
         _ = mod.test_functions.swapRemove(decl_index);
         if (decl.deletion_flag) {
             assert(mod.deletion_set.swapRemove(decl_index));
@@ -3329,7 +3306,6 @@ pub fn destroyDecl(mod: *Module, decl_index: Decl.Index) void {
         decl.clearValues(mod);
         decl.dependants.deinit(gpa);
         decl.dependencies.deinit(gpa);
-        decl.clearName(gpa);
         decl.* = undefined;
     }
     mod.decls_free_list.append(gpa, decl_index) catch {
@@ -3391,11 +3367,7 @@ pub fn declIsRoot(mod: *Module, decl_index: Decl.Index) bool {
 }
 
 fn freeExportList(gpa: Allocator, export_list: *ArrayListUnmanaged(*Export)) void {
-    for (export_list.items) |exp| {
-        gpa.free(exp.options.name);
-        if (exp.options.section) |s| gpa.free(s);
-        gpa.destroy(exp);
-    }
+    for (export_list.items) |exp| gpa.destroy(exp);
     export_list.deinit(gpa);
 }
 
@@ -3814,9 +3786,6 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
         if (decl.zir_decl_index != 0) {
             const old_zir_decl_index = decl.zir_decl_index;
             const new_zir_decl_index = extra_map.get(old_zir_decl_index) orelse {
-                log.debug("updateZirRefs {s}: delete {*} ({s})", .{
-                    file.sub_file_path, decl, decl.name,
-                });
                 try file.deleted_decls.append(gpa, decl_index);
                 continue;
             };
@@ -3824,14 +3793,7 @@ fn updateZirRefs(mod: *Module, file: *File, old_zir: Zir) !void {
             decl.zir_decl_index = new_zir_decl_index;
             const new_hash = decl.contentsHashZir(new_zir);
             if (!std.zig.srcHashEql(old_hash, new_hash)) {
-                log.debug("updateZirRefs {s}: outdated {*} ({s}) {d} => {d}", .{
-                    file.sub_file_path, decl, decl.name, old_zir_decl_index, new_zir_decl_index,
-                });
                 try file.outdated_decls.append(gpa, decl_index);
-            } else {
-                log.debug("updateZirRefs {s}: unchanged {*} ({s}) {d} => {d}", .{
-                    file.sub_file_path, decl, decl.name, old_zir_decl_index, new_zir_decl_index,
-                });
             }
         }
 
@@ -4031,8 +3993,6 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
         .complete => return,
 
         .outdated => blk: {
-            log.debug("re-analyzing {*} ({s})", .{ decl, decl.name });
-
             // The exports this Decl performs will be re-discovered, so we remove them here
             // prior to re-analysis.
             try mod.deleteDeclExports(decl_index);
@@ -4047,9 +4007,6 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
                 const dep = mod.declPtr(dep_index);
                 dep.removeDependant(decl_index);
                 if (dep.dependants.count() == 0 and !dep.deletion_flag) {
-                    log.debug("insert {*} ({s}) dependant {*} ({s}) into deletion set", .{
-                        decl, decl.name, dep, dep.name,
-                    });
                     try mod.markDeclForDeletion(dep_index);
                 }
             }
@@ -4061,7 +4018,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
         .unreferenced => false,
     };
 
-    var decl_prog_node = mod.sema_prog_node.start(mem.sliceTo(decl.name, 0), 0);
+    var decl_prog_node = mod.sema_prog_node.start(mod.intern_pool.stringToSlice(decl.name), 0);
     decl_prog_node.activate();
     defer decl_prog_node.end();
 
@@ -4190,14 +4147,11 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func_index: Fn.Index) SemaError!void
 
             if (no_bin_file and !dump_air and !dump_llvm_ir) return;
 
-            log.debug("analyze liveness of {s}", .{decl.name});
             var liveness = try Liveness.analyze(gpa, air, &mod.intern_pool);
             defer liveness.deinit(gpa);
 
             if (dump_air) {
-                const fqn = try decl.getFullyQualifiedName(mod);
-                defer mod.gpa.free(fqn);
-
+                const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
                 std.debug.print("# Begin Function AIR: {s}:\n", .{fqn});
                 @import("print_air.zig").dump(mod, air, liveness);
                 std.debug.print("# End Function AIR: {s}\n\n", .{fqn});
@@ -4354,9 +4308,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
     if (file.root_decl != .none) return;
 
     const gpa = mod.gpa;
-    var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
-    errdefer new_decl_arena.deinit();
-    const new_decl_arena_allocator = new_decl_arena.allocator();
 
     // Because these three things each reference each other, `undefined`
     // placeholders are used before being set after the struct type gains an
@@ -4394,7 +4345,7 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
     new_namespace.ty = struct_ty.toType();
     file.root_decl = new_decl_index.toOptional();
 
-    new_decl.name = try file.fullyQualifiedNameZ(gpa);
+    new_decl.name = try file.fullyQualifiedName(mod);
     new_decl.src_line = 0;
     new_decl.is_pub = true;
     new_decl.is_exported = false;
@@ -4403,7 +4354,7 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
     new_decl.ty = Type.type;
     new_decl.val = struct_ty.toValue();
     new_decl.@"align" = 0;
-    new_decl.@"linksection" = null;
+    new_decl.@"linksection" = .none;
     new_decl.has_tv = true;
     new_decl.owns_tv = true;
     new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive.
@@ -4431,7 +4382,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
             .mod = mod,
             .gpa = gpa,
             .arena = sema_arena_allocator,
-            .perm_arena = new_decl_arena_allocator,
             .code = file.zir,
             .owner_decl = new_decl,
             .owner_decl_index = new_decl_index,
@@ -4484,8 +4434,6 @@ pub fn semaFile(mod: *Module, file: *File) SemaError!void {
     } else {
         new_decl.analysis = .file_failure;
     }
-
-    try new_decl.finalizeNewArena(&new_decl_arena);
 }
 
 /// Returns `true` if the Decl type changed.
@@ -4507,28 +4455,8 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
 
     decl.analysis = .in_progress;
 
-    // We need the memory for the Type to go into the arena for the Decl
-    var decl_arena = std.heap.ArenaAllocator.init(gpa);
-    const decl_arena_allocator = decl_arena.allocator();
-    const decl_value_arena = blk: {
-        errdefer decl_arena.deinit();
-        const s = try decl_arena_allocator.create(ValueArena);
-        s.* = .{ .state = undefined };
-        break :blk s;
-    };
-    defer {
-        if (decl.value_arena) |value_arena| {
-            assert(value_arena.state_acquired == null);
-            decl_value_arena.prev = value_arena;
-        }
-
-        decl_value_arena.state = decl_arena.state;
-        decl.value_arena = decl_value_arena;
-    }
-
     var analysis_arena = std.heap.ArenaAllocator.init(gpa);
     defer analysis_arena.deinit();
-    const analysis_arena_allocator = analysis_arena.allocator();
 
     var comptime_mutable_decls = std.ArrayList(Decl.Index).init(gpa);
     defer comptime_mutable_decls.deinit();
@@ -4536,8 +4464,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
     var sema: Sema = .{
         .mod = mod,
         .gpa = gpa,
-        .arena = analysis_arena_allocator,
-        .perm_arena = decl_arena_allocator,
+        .arena = analysis_arena.allocator(),
         .code = zir,
         .owner_decl = decl,
         .owner_decl_index = decl_index,
@@ -4551,7 +4478,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
     defer sema.deinit();
 
     if (mod.declIsRoot(decl_index)) {
-        log.debug("semaDecl root {*} ({s})", .{ decl, decl.name });
         const main_struct_inst = Zir.main_struct_inst;
         const struct_index = decl.getOwnedStructIndex(mod).unwrap().?;
         const struct_obj = mod.structPtr(struct_index);
@@ -4563,7 +4489,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
         decl.generation = mod.generation;
         return false;
     }
-    log.debug("semaDecl {*} ({s})", .{ decl, decl.name });
 
     var wip_captures = try WipCaptureScope.init(gpa, decl.src_scope);
     defer wip_captures.deinit();
@@ -4619,7 +4544,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
         decl.ty = InternPool.Index.type_type.toType();
         decl.val = ty.toValue();
         decl.@"align" = 0;
-        decl.@"linksection" = null;
+        decl.@"linksection" = .none;
         decl.has_tv = true;
         decl.owns_tv = false;
         decl.analysis = .complete;
@@ -4646,7 +4571,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
             decl.clearValues(mod);
 
             decl.ty = decl_tv.ty;
-            decl.val = try decl_tv.val.copy(decl_arena_allocator);
+            decl.val = (try decl_tv.val.intern(decl_tv.ty, mod)).toValue();
             // linksection, align, and addrspace were already set by Sema
             decl.has_tv = true;
             decl.owns_tv = owns_tv;
@@ -4660,7 +4585,9 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
                     return sema.fail(&block_scope, export_src, "export of inline function", .{});
                 }
                 // The scope needs to have the decl in it.
-                const options: std.builtin.ExportOptions = .{ .name = mem.sliceTo(decl.name, 0) };
+                const options: std.builtin.ExportOptions = .{
+                    .name = mod.intern_pool.stringToSlice(decl.name),
+                };
                 try sema.analyzeExport(&block_scope, export_src, options, decl_index);
             }
             return type_changed or is_inline != prev_is_inline;
@@ -4693,14 +4620,13 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
             .func => {},
 
             else => {
-                log.debug("send global const to linker: {*} ({s})", .{ decl, decl.name });
                 queue_linker_work = true;
             },
         },
     }
 
     decl.ty = decl_tv.ty;
-    decl.val = try decl_tv.val.copy(decl_arena_allocator);
+    decl.val = (try decl_tv.val.intern(decl_tv.ty, mod)).toValue();
     decl.@"align" = blk: {
         const align_ref = decl.zirAlignRef(mod);
         if (align_ref == .none) break :blk 0;
@@ -4708,14 +4634,15 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
     };
     decl.@"linksection" = blk: {
         const linksection_ref = decl.zirLinksectionRef(mod);
-        if (linksection_ref == .none) break :blk null;
+        if (linksection_ref == .none) break :blk .none;
         const bytes = try sema.resolveConstString(&block_scope, section_src, linksection_ref, "linksection must be comptime-known");
         if (mem.indexOfScalar(u8, bytes, 0) != null) {
             return sema.fail(&block_scope, section_src, "linksection cannot contain null bytes", .{});
         } else if (bytes.len == 0) {
             return sema.fail(&block_scope, section_src, "linksection cannot be empty", .{});
         }
-        break :blk (try decl_arena_allocator.dupeZ(u8, bytes)).ptr;
+        const section = try mod.intern_pool.getOrPutString(gpa, bytes);
+        break :blk section.toOptional();
     };
     decl.@"addrspace" = blk: {
         const addrspace_ctx: Sema.AddressSpaceContext = switch (mod.intern_pool.indexToKey(decl_tv.val.toIntern())) {
@@ -4743,7 +4670,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
         (queue_linker_work and try sema.typeHasRuntimeBits(decl.ty));
 
     if (has_runtime_bits) {
-        log.debug("queue linker work for {*} ({s})", .{ decl, decl.name });
 
         // Needed for codegen_decl which will call updateDecl and then the
         // codegen backend wants full access to the Decl Type.
@@ -4759,7 +4685,9 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
     if (decl.is_exported) {
         const export_src: LazySrcLoc = .{ .token_offset = @boolToInt(decl.is_pub) };
         // The scope needs to have the decl in it.
-        const options: std.builtin.ExportOptions = .{ .name = mem.sliceTo(decl.name, 0) };
+        const options: std.builtin.ExportOptions = .{
+            .name = mod.intern_pool.stringToSlice(decl.name),
+        };
         try sema.analyzeExport(&block_scope, export_src, options, decl_index);
     }
 
@@ -4785,10 +4713,6 @@ pub fn declareDeclDependencyType(mod: *Module, depender_index: Decl.Index, depen
         }
     }
 
-    log.debug("{*} ({s}) depends on {*} ({s})", .{
-        depender, depender.name, dependee, dependee.name,
-    });
-
     if (dependee.deletion_flag) {
         dependee.deletion_flag = false;
         assert(mod.deletion_set.swapRemove(dependee_index));
@@ -5138,6 +5062,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
     const namespace = mod.namespacePtr(namespace_index);
     const gpa = mod.gpa;
     const zir = namespace.file_scope.zir;
+    const ip = &mod.intern_pool;
 
     // zig fmt: off
     const is_pub                       = (flags & 0b0001) != 0;
@@ -5157,31 +5082,31 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
     // Every Decl needs a name.
     var is_named_test = false;
     var kind: Decl.Kind = .named;
-    const decl_name: [:0]const u8 = switch (decl_name_index) {
+    const decl_name: InternPool.NullTerminatedString = switch (decl_name_index) {
         0 => name: {
             if (export_bit) {
                 const i = iter.usingnamespace_index;
                 iter.usingnamespace_index += 1;
                 kind = .@"usingnamespace";
-                break :name try std.fmt.allocPrintZ(gpa, "usingnamespace_{d}", .{i});
+                break :name try ip.getOrPutStringFmt(gpa, "usingnamespace_{d}", .{i});
             } else {
                 const i = iter.comptime_index;
                 iter.comptime_index += 1;
                 kind = .@"comptime";
-                break :name try std.fmt.allocPrintZ(gpa, "comptime_{d}", .{i});
+                break :name try ip.getOrPutStringFmt(gpa, "comptime_{d}", .{i});
             }
         },
         1 => name: {
             const i = iter.unnamed_test_index;
             iter.unnamed_test_index += 1;
             kind = .@"test";
-            break :name try std.fmt.allocPrintZ(gpa, "test_{d}", .{i});
+            break :name try ip.getOrPutStringFmt(gpa, "test_{d}", .{i});
         },
         2 => name: {
             is_named_test = true;
             const test_name = zir.nullTerminatedString(decl_doccomment_index);
             kind = .@"test";
-            break :name try std.fmt.allocPrintZ(gpa, "decltest.{s}", .{test_name});
+            break :name try ip.getOrPutStringFmt(gpa, "decltest.{s}", .{test_name});
         },
         else => name: {
             const raw_name = zir.nullTerminatedString(decl_name_index);
@@ -5189,14 +5114,12 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
                 is_named_test = true;
                 const test_name = zir.nullTerminatedString(decl_name_index + 1);
                 kind = .@"test";
-                break :name try std.fmt.allocPrintZ(gpa, "test.{s}", .{test_name});
+                break :name try ip.getOrPutStringFmt(gpa, "test.{s}", .{test_name});
             } else {
-                break :name try gpa.dupeZ(u8, raw_name);
+                break :name try ip.getOrPutString(gpa, raw_name);
             }
         },
     };
-    var must_free_decl_name = true;
-    defer if (must_free_decl_name) gpa.free(decl_name);
 
     const is_exported = export_bit and decl_name_index != 0;
     if (kind == .@"usingnamespace") try namespace.usingnamespace_set.ensureUnusedCapacity(gpa, 1);
@@ -5204,7 +5127,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
     // We create a Decl for it regardless of analysis status.
     const gop = try namespace.decls.getOrPutContextAdapted(
         gpa,
-        @as([]const u8, mem.sliceTo(decl_name, 0)),
+        decl_name,
         DeclAdapter{ .mod = mod },
         Namespace.DeclContext{ .module = mod },
     );
@@ -5214,11 +5137,9 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
         const new_decl = mod.declPtr(new_decl_index);
         new_decl.kind = kind;
         new_decl.name = decl_name;
-        must_free_decl_name = false;
         if (kind == .@"usingnamespace") {
             namespace.usingnamespace_set.putAssumeCapacity(new_decl_index, is_pub);
         }
-        log.debug("scan new {*} ({s}) into {*}", .{ new_decl, decl_name, namespace });
         new_decl.src_line = line;
         gop.key_ptr.* = new_decl_index;
         // Exported decls, comptime decls, usingnamespace decls, and
@@ -5239,7 +5160,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
                 if (!comp.bin_file.options.is_test) break :blk false;
                 if (decl_pkg != mod.main_pkg) break :blk false;
                 if (comp.test_filter) |test_filter| {
-                    if (mem.indexOf(u8, decl_name, test_filter) == null) {
+                    if (mem.indexOf(u8, ip.stringToSlice(decl_name), test_filter) == null) {
                         break :blk false;
                     }
                 }
@@ -5270,7 +5191,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
             gpa,
             src_loc,
             "duplicate test name: {s}",
-            .{decl_name},
+            .{ip.stringToSlice(decl_name)},
         );
         errdefer msg.destroy(gpa);
         try mod.failed_decls.putNoClobber(gpa, decl_index, msg);
@@ -5281,7 +5202,6 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) Allocator.Err
         };
         try mod.errNoteNonLazy(other_src_loc, msg, "other test here", .{});
     }
-    log.debug("scan existing {*} ({s}) of {*}", .{ decl, decl.name, namespace });
     // Update the AST node of the decl; even if its contents are unchanged, it may
     // have been re-ordered.
     decl.src_node = decl_node;
@@ -5315,7 +5235,6 @@ pub fn clearDecl(
     defer tracy.end();
 
     const decl = mod.declPtr(decl_index);
-    log.debug("clearing {*} ({s})", .{ decl, decl.name });
 
     const gpa = mod.gpa;
     try mod.deletion_set.ensureUnusedCapacity(gpa, decl.dependencies.count());
@@ -5330,9 +5249,6 @@ pub fn clearDecl(
         const dep = mod.declPtr(dep_index);
         dep.removeDependant(decl_index);
         if (dep.dependants.count() == 0 and !dep.deletion_flag) {
-            log.debug("insert {*} ({s}) dependant {*} ({s}) into deletion set", .{
-                decl, decl.name, dep, dep.name,
-            });
             // We don't recursively perform a deletion here, because during the update,
             // another reference to it may turn up.
             dep.deletion_flag = true;
@@ -5387,7 +5303,6 @@ pub fn clearDecl(
 /// This function is exclusively called for anonymous decls.
 pub fn deleteUnusedDecl(mod: *Module, decl_index: Decl.Index) void {
     const decl = mod.declPtr(decl_index);
-    log.debug("deleteUnusedDecl {d} ({s})", .{ decl_index, decl.name });
 
     assert(!mod.declIsRoot(decl_index));
     assert(mod.namespacePtr(decl.src_namespace).anon_decls.swapRemove(decl_index));
@@ -5415,7 +5330,6 @@ fn markDeclForDeletion(mod: *Module, decl_index: Decl.Index) !void {
 /// If other decls depend on this decl, they must be aborted first.
 pub fn abortAnonDecl(mod: *Module, decl_index: Decl.Index) void {
     const decl = mod.declPtr(decl_index);
-    log.debug("abortAnonDecl {*} ({s})", .{ decl, decl.name });
 
     assert(!mod.declIsRoot(decl_index));
     assert(mod.namespacePtr(decl.src_namespace).anon_decls.swapRemove(decl_index));
@@ -5468,21 +5382,20 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) Allocator.Error!void
             }
         }
         if (mod.comp.bin_file.cast(link.File.Elf)) |elf| {
-            elf.deleteDeclExport(decl_index, exp.options.name);
+            elf.deleteDeclExport(decl_index, exp.name);
         }
         if (mod.comp.bin_file.cast(link.File.MachO)) |macho| {
-            try macho.deleteDeclExport(decl_index, exp.options.name);
+            try macho.deleteDeclExport(decl_index, exp.name);
         }
         if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| {
             wasm.deleteDeclExport(decl_index);
         }
         if (mod.comp.bin_file.cast(link.File.Coff)) |coff| {
-            coff.deleteDeclExport(decl_index, exp.options.name);
+            coff.deleteDeclExport(decl_index, exp.name);
         }
         if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| {
             failed_kv.value.destroy(mod.gpa);
         }
-        mod.gpa.free(exp.options.name);
         mod.gpa.destroy(exp);
     }
     export_owners.deinit(mod.gpa);
@@ -5497,11 +5410,6 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE
     const decl_index = func.owner_decl;
     const decl = mod.declPtr(decl_index);
 
-    // Use the Decl's arena for captured values.
-    var decl_arena: std.heap.ArenaAllocator = undefined;
-    const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
-    defer decl.value_arena.?.release(&decl_arena);
-
     var comptime_mutable_decls = std.ArrayList(Decl.Index).init(gpa);
     defer comptime_mutable_decls.deinit();
 
@@ -5512,7 +5420,6 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE
         .mod = mod,
         .gpa = gpa,
         .arena = arena,
-        .perm_arena = decl_arena_allocator,
         .code = decl.getFileScope(mod).zir,
         .owner_decl = decl,
         .owner_decl_index = decl_index,
@@ -5616,7 +5523,6 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE
     }
 
     func.state = .in_progress;
-    log.debug("set {s} to in_progress", .{decl.name});
 
     const last_arg_index = inner_block.instructions.items.len;
 
@@ -5677,7 +5583,6 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE
     sema.air_extra.items[@enumToInt(Air.ExtraIndex.main_block)] = main_block_index;
 
     func.state = .success;
-    log.debug("set {s} to success", .{decl.name});
 
     // Finally we must resolve the return type and parameter types so that backends
     // have full access to type information.
@@ -5724,7 +5629,6 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE
 
 fn markOutdatedDecl(mod: *Module, decl_index: Decl.Index) !void {
     const decl = mod.declPtr(decl_index);
-    log.debug("mark outdated {*} ({s})", .{ decl, decl.name });
     try mod.comp.work_queue.writeItem(.{ .analyze_decl = decl_index });
     if (mod.failed_decls.fetchSwapRemove(decl_index)) |kv| {
         kv.value.destroy(mod.gpa);
@@ -5821,7 +5725,7 @@ pub fn allocateNewDecl(
         .ty = undefined,
         .val = undefined,
         .@"align" = undefined,
-        .@"linksection" = undefined,
+        .@"linksection" = .none,
         .@"addrspace" = .generic,
         .analysis = .unreferenced,
         .deletion_flag = false,
@@ -5839,25 +5743,20 @@ pub fn allocateNewDecl(
     return decl_and_index.decl_index;
 }
 
-/// Get error value for error tag `name`.
-pub fn getErrorValue(mod: *Module, name: []const u8) !std.StringHashMapUnmanaged(ErrorInt).KV {
+pub fn getErrorValue(
+    mod: *Module,
+    name: InternPool.NullTerminatedString,
+) Allocator.Error!ErrorInt {
     const gop = try mod.global_error_set.getOrPut(mod.gpa, name);
-    if (gop.found_existing) {
-        return std.StringHashMapUnmanaged(ErrorInt).KV{
-            .key = gop.key_ptr.*,
-            .value = gop.value_ptr.*,
-        };
-    }
+    return @intCast(ErrorInt, gop.index);
+}
 
-    errdefer assert(mod.global_error_set.remove(name));
-    try mod.error_name_list.ensureUnusedCapacity(mod.gpa, 1);
-    gop.key_ptr.* = try mod.gpa.dupe(u8, name);
-    gop.value_ptr.* = @intCast(ErrorInt, mod.error_name_list.items.len);
-    mod.error_name_list.appendAssumeCapacity(gop.key_ptr.*);
-    return std.StringHashMapUnmanaged(ErrorInt).KV{
-        .key = gop.key_ptr.*,
-        .value = gop.value_ptr.*,
-    };
+pub fn getErrorValueFromSlice(
+    mod: *Module,
+    name: []const u8,
+) Allocator.Error!ErrorInt {
+    const interned_name = try mod.intern_pool.getOrPutString(mod.gpa, name);
+    return getErrorValue(mod, interned_name);
 }
 
 pub fn createAnonymousDecl(mod: *Module, block: *Sema.Block, typed_value: TypedValue) !Decl.Index {
@@ -5874,24 +5773,23 @@ pub fn createAnonymousDeclFromDecl(
 ) !Decl.Index {
     const new_decl_index = try mod.allocateNewDecl(namespace, src_decl.src_node, src_scope);
     errdefer mod.destroyDecl(new_decl_index);
-    const name = try std.fmt.allocPrintZ(mod.gpa, "{s}__anon_{d}", .{
-        src_decl.name, @enumToInt(new_decl_index),
+    const ip = &mod.intern_pool;
+    const name = try ip.getOrPutStringFmt(mod.gpa, "{s}__anon_{d}", .{
+        ip.stringToSlice(src_decl.name), @enumToInt(new_decl_index),
     });
     try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, tv, name);
     return new_decl_index;
 }
 
-/// Takes ownership of `name` even if it returns an error.
 pub fn initNewAnonDecl(
     mod: *Module,
     new_decl_index: Decl.Index,
     src_line: u32,
     namespace: Namespace.Index,
     typed_value: TypedValue,
-    name: [:0]u8,
+    name: InternPool.NullTerminatedString,
 ) Allocator.Error!void {
     assert(typed_value.ty.toIntern() == mod.intern_pool.typeOf(typed_value.val.toIntern()));
-    errdefer mod.gpa.free(name);
 
     const new_decl = mod.declPtr(new_decl_index);
 
@@ -5900,7 +5798,7 @@ pub fn initNewAnonDecl(
     new_decl.ty = typed_value.ty;
     new_decl.val = typed_value.val;
     new_decl.@"align" = 0;
-    new_decl.@"linksection" = null;
+    new_decl.@"linksection" = .none;
     new_decl.has_tv = true;
     new_decl.analysis = .complete;
     new_decl.generation = mod.generation;
@@ -6330,12 +6228,11 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
         // deletion set at this time.
         for (file.deleted_decls.items) |decl_index| {
             const decl = mod.declPtr(decl_index);
-            log.debug("deleted from source: {*} ({s})", .{ decl, decl.name });
 
             // Remove from the namespace it resides in, preserving declaration order.
             assert(decl.zir_decl_index != 0);
             _ = mod.namespacePtr(decl.src_namespace).decls.orderedRemoveAdapted(
-                @as([]const u8, mem.sliceTo(decl.name, 0)),
+                decl.name,
                 DeclAdapter{ .mod = mod },
             );
 
@@ -6357,7 +6254,7 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
 pub fn processExports(mod: *Module) !void {
     const gpa = mod.gpa;
     // Map symbol names to `Export` for name collision detection.
-    var symbol_exports: std.StringArrayHashMapUnmanaged(*Export) = .{};
+    var symbol_exports: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export) = .{};
     defer symbol_exports.deinit(gpa);
 
     var it = mod.decl_exports.iterator();
@@ -6365,13 +6262,13 @@ pub fn processExports(mod: *Module) !void {
         const exported_decl = entry.key_ptr.*;
         const exports = entry.value_ptr.items;
         for (exports) |new_export| {
-            const gop = try symbol_exports.getOrPut(gpa, new_export.options.name);
+            const gop = try symbol_exports.getOrPut(gpa, new_export.name);
             if (gop.found_existing) {
                 new_export.status = .failed_retryable;
                 try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
                 const src_loc = new_export.getSrcLoc(mod);
                 const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {s}", .{
-                    new_export.options.name,
+                    mod.intern_pool.stringToSlice(new_export.name),
                 });
                 errdefer msg.destroy(gpa);
                 const other_export = gop.value_ptr.*;
@@ -6408,8 +6305,9 @@ pub fn populateTestFunctions(
     const builtin_file = (mod.importPkg(builtin_pkg) catch unreachable).file;
     const root_decl = mod.declPtr(builtin_file.root_decl.unwrap().?);
     const builtin_namespace = mod.namespacePtr(root_decl.src_namespace);
+    const test_functions_str = try mod.intern_pool.getOrPutString(gpa, "test_functions");
     const decl_index = builtin_namespace.decls.getKeyAdapted(
-        @as([]const u8, "test_functions"),
+        test_functions_str,
         DeclAdapter{ .mod = mod },
     ).?;
     {
@@ -6443,7 +6341,7 @@ pub fn populateTestFunctions(
 
         for (test_fn_vals, mod.test_functions.keys()) |*test_fn_val, test_decl_index| {
             const test_decl = mod.declPtr(test_decl_index);
-            const test_decl_name = mem.span(test_decl.name);
+            const test_decl_name = mod.intern_pool.stringToSlice(test_decl.name);
             const test_name_decl_index = n: {
                 const test_name_decl_ty = try mod.arrayType(.{
                     .len = test_decl_name.len,
@@ -7156,7 +7054,7 @@ pub fn opaqueSrcLoc(mod: *Module, opaque_type: InternPool.Key.OpaqueType) SrcLoc
     return mod.declPtr(opaque_type.decl).srcLoc(mod);
 }
 
-pub fn opaqueFullyQualifiedName(mod: *Module, opaque_type: InternPool.Key.OpaqueType) ![:0]u8 {
+pub fn opaqueFullyQualifiedName(mod: *Module, opaque_type: InternPool.Key.OpaqueType) !InternPool.NullTerminatedString {
     return mod.declPtr(opaque_type.decl).getFullyQualifiedName(mod);
 }
 
src/print_air.zig
@@ -685,8 +685,9 @@ const Writer = struct {
     fn writeDbgInline(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
         const ty_fn = w.air.instructions.items(.data)[inst].ty_fn;
         const func_index = ty_fn.func;
+        const ip = &w.module.intern_pool;
         const owner_decl = w.module.declPtr(w.module.funcPtr(func_index).owner_decl);
-        try s.print("{s}", .{owner_decl.name});
+        try s.print("{s}", .{ip.stringToSlice(owner_decl.name)});
     }
 
     fn writeDbgVar(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
src/Sema.zig
@@ -11,9 +11,6 @@ gpa: Allocator,
 /// Points to the temporary arena allocator of the Sema.
 /// This arena will be cleared when the sema is destroyed.
 arena: Allocator,
-/// Points to the arena allocator for the owner_decl.
-/// This arena will persist until the decl is invalidated.
-perm_arena: Allocator,
 code: Zir,
 air_instructions: std.MultiArrayList(Air.Inst) = .{},
 air_extra: std.ArrayListUnmanaged(u32) = .{},
@@ -740,7 +737,6 @@ pub const Block = struct {
             // TODO: migrate Decl alignment to use `InternPool.Alignment`
             new_decl.@"align" = @intCast(u32, alignment);
             errdefer sema.mod.abortAnonDecl(new_decl_index);
-            try new_decl.finalizeNewArena(&wad.new_decl_arena);
             wad.finished = true;
             try sema.mod.finalizeAnonDecl(new_decl_index);
             return new_decl_index;
@@ -1825,6 +1821,20 @@ pub fn resolveConstString(
     return val.toAllocatedBytes(wanted_type, sema.arena, sema.mod);
 }
 
+pub fn resolveConstStringIntern(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    zir_ref: Zir.Inst.Ref,
+    reason: []const u8,
+) !InternPool.NullTerminatedString {
+    const air_inst = try sema.resolveInst(zir_ref);
+    const wanted_type = Type.slice_const_u8;
+    const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src);
+    const val = try sema.resolveConstValue(block, src, coerced_inst, reason);
+    return val.toIpString(wanted_type, sema.mod);
+}
+
 pub fn resolveType(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !Type {
     const air_inst = try sema.resolveInst(zir_ref);
     assert(air_inst != .var_args_param_type);
@@ -1847,11 +1857,13 @@ fn analyzeAsType(
 
 pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     if (!mod.backendSupportsFeature(.error_return_trace)) return;
 
     assert(!block.is_comptime);
     var err_trace_block = block.makeSubBlock();
-    defer err_trace_block.instructions.deinit(sema.gpa);
+    defer err_trace_block.instructions.deinit(gpa);
 
     const src: LazySrcLoc = .unneeded;
 
@@ -1866,17 +1878,19 @@ pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize)
     const st_ptr = try err_trace_block.addTy(.alloc, try mod.singleMutPtrType(stack_trace_ty));
 
     // st.instruction_addresses = &addrs;
-    const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src, true);
+    const instruction_addresses_field_name = try ip.getOrPutString(gpa, "instruction_addresses");
+    const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, instruction_addresses_field_name, src, true);
     try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store);
 
     // st.index = 0;
-    const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src, true);
+    const index_field_name = try ip.getOrPutString(gpa, "index");
+    const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, index_field_name, src, true);
     try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, .zero_usize, src, .store);
 
     // @errorReturnTrace() = &st;
     _ = try err_trace_block.addUnOp(.set_err_return_trace, st_ptr);
 
-    try block.instructions.insertSlice(sema.gpa, last_arg_index, err_trace_block.instructions.items);
+    try block.instructions.insertSlice(gpa, last_arg_index, err_trace_block.instructions.items);
 }
 
 /// May return Value Tags: `variable`, `undef`.
@@ -2179,7 +2193,13 @@ fn failWithUseOfAsync(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError
     return sema.failWithOwnedErrorMsg(msg);
 }
 
-fn failWithInvalidFieldAccess(sema: *Sema, block: *Block, src: LazySrcLoc, object_ty: Type, field_name: []const u8) CompileError {
+fn failWithInvalidFieldAccess(
+    sema: *Sema,
+    block: *Block,
+    src: LazySrcLoc,
+    object_ty: Type,
+    field_name: InternPool.NullTerminatedString,
+) CompileError {
     const mod = sema.mod;
     const inner_ty = if (object_ty.isSinglePointer(mod)) object_ty.childType(mod) else object_ty;
 
@@ -2207,15 +2227,16 @@ fn failWithInvalidFieldAccess(sema: *Sema, block: *Block, src: LazySrcLoc, objec
     return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)});
 }
 
-fn typeSupportsFieldAccess(mod: *const Module, ty: Type, field_name: []const u8) bool {
+fn typeSupportsFieldAccess(mod: *const Module, ty: Type, field_name: InternPool.NullTerminatedString) bool {
+    const ip = &mod.intern_pool;
     switch (ty.zigTypeTag(mod)) {
-        .Array => return mem.eql(u8, field_name, "len"),
+        .Array => return ip.stringEqlSlice(field_name, "len"),
         .Pointer => {
             const ptr_info = ty.ptrInfo(mod);
             if (ptr_info.size == .Slice) {
-                return mem.eql(u8, field_name, "ptr") or mem.eql(u8, field_name, "len");
+                return ip.stringEqlSlice(field_name, "ptr") or ip.stringEqlSlice(field_name, "len");
             } else if (ptr_info.pointee_type.zigTypeTag(mod) == .Array) {
-                return mem.eql(u8, field_name, "len");
+                return ip.stringEqlSlice(field_name, "len");
             } else return false;
         },
         .Type, .Struct, .Union => return true,
@@ -2308,19 +2329,19 @@ pub fn fail(
 fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
     @setCold(true);
     const gpa = sema.gpa;
+    const mod = sema.mod;
 
-    if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) {
+    if (crash_report.is_enabled and mod.comp.debug_compile_errors) {
         if (err_msg.src_loc.lazy == .unneeded) return error.NeededSourceLocation;
         var wip_errors: std.zig.ErrorBundle.Wip = undefined;
         wip_errors.init(gpa) catch unreachable;
-        Compilation.addModuleErrorMsg(&wip_errors, err_msg.*) catch unreachable;
+        Compilation.addModuleErrorMsg(mod, &wip_errors, err_msg.*) catch unreachable;
         std.debug.print("compile error during Sema:\n", .{});
         var error_bundle = wip_errors.toOwnedBundle("") catch unreachable;
         error_bundle.renderToStdErr(.{ .ttyconf = .no_color });
         crash_report.compilerPanic("unexpected compile error occurred", null, null);
     }
 
-    const mod = sema.mod;
     ref: {
         errdefer err_msg.destroy(gpa);
         if (err_msg.src_loc.lazy == .unneeded) {
@@ -2330,9 +2351,9 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
         try mod.failed_files.ensureUnusedCapacity(gpa, 1);
 
         const max_references = blk: {
-            if (sema.mod.comp.reference_trace) |num| break :blk num;
+            if (mod.comp.reference_trace) |num| break :blk num;
             // Do not add multiple traces without explicit request.
-            if (sema.mod.failed_decls.count() != 0) break :ref;
+            if (mod.failed_decls.count() != 0) break :ref;
             break :blk default_reference_trace_len;
         };
 
@@ -2350,13 +2371,16 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
             if (gop.found_existing) break;
             if (cur_reference_trace < max_references) {
                 const decl = sema.mod.declPtr(ref.referencer);
-                try reference_stack.append(.{ .decl = decl.name, .src_loc = ref.src.toSrcLoc(decl, mod) });
+                try reference_stack.append(.{
+                    .decl = decl.name.toOptional(),
+                    .src_loc = ref.src.toSrcLoc(decl, mod),
+                });
             }
             referenced_by = ref.referencer;
         }
         if (sema.mod.comp.reference_trace == null and cur_reference_trace > 0) {
             try reference_stack.append(.{
-                .decl = null,
+                .decl = .none,
                 .src_loc = undefined,
                 .hidden = 0,
             });
@@ -2795,7 +2819,6 @@ fn zirStructDecl(
     new_namespace.ty = struct_ty.toType();
 
     try sema.analyzeStructDecl(new_decl, inst, struct_index);
-    try new_decl.finalizeNewArena(&new_decl_arena);
     const decl_val = sema.analyzeDeclVal(block, src, new_decl_index);
     try mod.finalizeAnonDecl(new_decl_index);
     return decl_val;
@@ -2812,6 +2835,7 @@ fn createAnonymousDeclTypeNamed(
 ) !Decl.Index {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const namespace = block.namespace;
     const src_scope = block.wip_capture_scope;
     const src_decl = mod.declPtr(block.src_decl);
@@ -2827,16 +2851,19 @@ fn createAnonymousDeclTypeNamed(
             // semantically analyzed.
             // This name is also used as the key in the parent namespace so it cannot be
             // renamed.
-            const name = try std.fmt.allocPrintZ(gpa, "{s}__{s}_{d}", .{
-                src_decl.name, anon_prefix, @enumToInt(new_decl_index),
-            });
-            errdefer gpa.free(name);
+
+            // This ensureUnusedCapacity protects against the src_decl slice from being
+            // reallocated during the call to `getOrPutStringFmt`.
+            try ip.string_bytes.ensureUnusedCapacity(gpa, ip.stringToSlice(src_decl.name).len +
+                anon_prefix.len + 20);
+            const name = ip.getOrPutStringFmt(gpa, "{s}__{s}_{d}", .{
+                ip.stringToSlice(src_decl.name), anon_prefix, @enumToInt(new_decl_index),
+            }) catch unreachable;
             try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name);
             return new_decl_index;
         },
         .parent => {
-            const name = try gpa.dupeZ(u8, mem.sliceTo(sema.mod.declPtr(block.src_decl).name, 0));
-            errdefer gpa.free(name);
+            const name = mod.declPtr(block.src_decl).name;
             try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name);
             return new_decl_index;
         },
@@ -2846,7 +2873,7 @@ fn createAnonymousDeclTypeNamed(
 
             var buf = std.ArrayList(u8).init(gpa);
             defer buf.deinit();
-            try buf.appendSlice(mem.sliceTo(sema.mod.declPtr(block.src_decl).name, 0));
+            try buf.appendSlice(ip.stringToSlice(mod.declPtr(block.src_decl).name));
             try buf.appendSlice("(");
 
             var arg_i: usize = 0;
@@ -2871,8 +2898,7 @@ fn createAnonymousDeclTypeNamed(
             };
 
             try buf.appendSlice(")");
-            const name = try buf.toOwnedSliceSentinel(0);
-            errdefer gpa.free(name);
+            const name = try ip.getOrPutString(gpa, buf.items);
             try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name);
             return new_decl_index;
         },
@@ -2885,10 +2911,17 @@ fn createAnonymousDeclTypeNamed(
                 .dbg_var_ptr, .dbg_var_val => {
                     if (zir_data[i].str_op.operand != ref) continue;
 
-                    const name = try std.fmt.allocPrintZ(gpa, "{s}.{s}", .{
-                        src_decl.name, zir_data[i].str_op.getStr(sema.code),
-                    });
-                    errdefer gpa.free(name);
+                    // This ensureUnusedCapacity protects against the src_decl
+                    // slice from being reallocated during the call to
+                    // `getOrPutStringFmt`.
+                    const zir_str = zir_data[i].str_op.getStr(sema.code);
+                    try ip.string_bytes.ensureUnusedCapacity(
+                        gpa,
+                        ip.stringToSlice(src_decl.name).len + zir_str.len + 10,
+                    );
+                    const name = ip.getOrPutStringFmt(gpa, "{s}.{s}", .{
+                        ip.stringToSlice(src_decl.name), zir_str,
+                    }) catch unreachable;
 
                     try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name);
                     return new_decl_index;
@@ -3249,7 +3282,6 @@ fn zirUnionDecl(
 
     _ = try mod.scanNamespace(new_namespace_index, extra_index, decls_len, new_decl);
 
-    try new_decl.finalizeNewArena(&new_decl_arena);
     const decl_val = sema.analyzeDeclVal(block, src, new_decl_index);
     try mod.finalizeAnonDecl(new_decl_index);
     return decl_val;
@@ -3315,7 +3347,6 @@ fn zirOpaqueDecl(
 
     extra_index = try mod.scanNamespace(new_namespace_index, extra_index, decls_len, new_decl);
 
-    try new_decl.finalizeNewArena(&new_decl_arena);
     const decl_val = sema.analyzeDeclVal(block, src, new_decl_index);
     try mod.finalizeAnonDecl(new_decl_index);
     return decl_val;
@@ -3344,8 +3375,8 @@ fn zirErrorSetDecl(
     while (extra_index < extra_index_end) : (extra_index += 2) { // +2 to skip over doc_string
         const str_index = sema.code.extra[extra_index];
         const name = sema.code.nullTerminatedString(str_index);
-        const kv = try mod.getErrorValue(name);
-        const name_ip = try mod.intern_pool.getOrPutString(gpa, kv.key);
+        const name_ip = try mod.intern_pool.getOrPutString(gpa, name);
+        _ = try mod.getErrorValue(name_ip);
         const result = names.getOrPutAssumeCapacity(name_ip);
         assert(!result.found_existing); // verified in AstGen
     }
@@ -3512,7 +3543,8 @@ fn indexablePtrLen(
     const is_pointer_to = object_ty.isSinglePointer(mod);
     const indexable_ty = if (is_pointer_to) object_ty.childType(mod) else object_ty;
     try checkIndexable(sema, block, src, indexable_ty);
-    return sema.fieldVal(block, src, object, "len", src);
+    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, "len");
+    return sema.fieldVal(block, src, object, field_name, src);
 }
 
 fn indexablePtrLenOrNone(
@@ -3525,7 +3557,8 @@ fn indexablePtrLenOrNone(
     const operand_ty = sema.typeOf(operand);
     try checkMemOperand(sema, block, src, operand_ty);
     if (operand_ty.ptrSize(mod) == .Many) return .none;
-    return sema.fieldVal(block, src, operand, "len", src);
+    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, "len");
+    return sema.fieldVal(block, src, operand, field_name, src);
 }
 
 fn zirAllocExtended(
@@ -4079,6 +4112,7 @@ fn zirFieldBasePtr(
 fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
     const args = sema.code.refSlice(extra.end, extra.data.operands_len);
@@ -4122,7 +4156,7 @@ fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
             }
             if (!object_ty.indexableHasLen(mod)) continue;
 
-            break :l try sema.fieldVal(block, arg_src, object, "len", arg_src);
+            break :l try sema.fieldVal(block, arg_src, object, try ip.getOrPutString(gpa, "len"), arg_src);
         };
         const arg_len = try sema.coerce(block, Type.usize, arg_len_uncoerced, arg_src);
         if (len == .none) {
@@ -4308,6 +4342,7 @@ fn validateUnionInit(
     union_ptr: Air.Inst.Ref,
 ) CompileError!void {
     const mod = sema.mod;
+    const gpa = sema.gpa;
 
     if (instrs.len != 1) {
         const msg = msg: {
@@ -4317,7 +4352,7 @@ fn validateUnionInit(
                 "cannot initialize multiple union fields at once; unions can only have one active field",
                 .{},
             );
-            errdefer msg.destroy(sema.gpa);
+            errdefer msg.destroy(gpa);
 
             for (instrs[1..]) |inst| {
                 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
@@ -4341,7 +4376,7 @@ fn validateUnionInit(
     const field_ptr_data = sema.code.instructions.items(.data)[field_ptr].pl_node;
     const field_src: LazySrcLoc = .{ .node_offset_initializer = field_ptr_data.src_node };
     const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
-    const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);
+    const field_name = try mod.intern_pool.getOrPutString(gpa, sema.code.nullTerminatedString(field_ptr_extra.field_name_start));
     // Validate the field access but ignore the index since we want the tag enum field index.
     _ = try sema.unionFieldIndex(block, union_ty, field_name, field_src);
     const air_tags = sema.air_instructions.items(.tag);
@@ -4444,6 +4479,7 @@ fn validateStructInit(
 ) CompileError!void {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     // Maps field index to field_ptr index of where it was already initialized.
     const found_fields = try gpa.alloc(Zir.Inst.Index, struct_ty.structFieldCount(mod));
@@ -4457,7 +4493,10 @@ fn validateStructInit(
         const field_src: LazySrcLoc = .{ .node_offset_initializer = field_ptr_data.src_node };
         const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
         struct_ptr_zir_ref = field_ptr_extra.lhs;
-        const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start);
+        const field_name = try ip.getOrPutString(
+            gpa,
+            sema.code.nullTerminatedString(field_ptr_extra.field_name_start),
+        );
         const field_index = if (struct_ty.isTuple(mod))
             try sema.tupleFieldIndex(block, struct_ty, field_name, field_src)
         else
@@ -4504,7 +4543,7 @@ fn validateStructInit(
                 }
                 const field_name = struct_ty.structFieldName(i, mod);
                 const template = "missing struct field: {s}";
-                const args = .{field_name};
+                const args = .{ip.stringToSlice(field_name)};
                 if (root_msg) |msg| {
                     try sema.errNote(block, init_src, msg, template, args);
                 } else {
@@ -4525,8 +4564,7 @@ fn validateStructInit(
 
         if (root_msg) |msg| {
             if (mod.typeToStruct(struct_ty)) |struct_obj| {
-                const fqn = try struct_obj.getFullyQualifiedName(mod);
-                defer gpa.free(fqn);
+                const fqn = ip.stringToSlice(try struct_obj.getFullyQualifiedName(mod));
                 try mod.errNoteNonLazy(
                     struct_obj.srcLoc(mod),
                     msg,
@@ -4649,7 +4687,7 @@ fn validateStructInit(
             }
             const field_name = struct_ty.structFieldName(i, mod);
             const template = "missing struct field: {s}";
-            const args = .{field_name};
+            const args = .{ip.stringToSlice(field_name)};
             if (root_msg) |msg| {
                 try sema.errNote(block, init_src, msg, template, args);
             } else {
@@ -4662,10 +4700,9 @@ fn validateStructInit(
 
     if (root_msg) |msg| {
         if (mod.typeToStruct(struct_ty)) |struct_obj| {
-            const fqn = try struct_obj.getFullyQualifiedName(sema.mod);
-            defer gpa.free(fqn);
+            const fqn = ip.stringToSlice(try struct_obj.getFullyQualifiedName(mod));
             try sema.mod.errNoteNonLazy(
-                struct_obj.srcLoc(sema.mod),
+                struct_obj.srcLoc(mod),
                 msg,
                 "struct '{s}' declared here",
                 .{fqn},
@@ -4949,7 +4986,7 @@ fn failWithBadMemberAccess(
     block: *Block,
     agg_ty: Type,
     field_src: LazySrcLoc,
-    field_name: []const u8,
+    field_name_nts: InternPool.NullTerminatedString,
 ) CompileError {
     const mod = sema.mod;
     const kw_name = switch (agg_ty.zigTypeTag(mod)) {
@@ -4959,6 +4996,7 @@ fn failWithBadMemberAccess(
         .Enum => "enum",
         else => unreachable,
     };
+    const field_name = mod.intern_pool.stringToSlice(field_name_nts);
     if (agg_ty.getOwnerDeclOrNull(mod)) |some| if (sema.mod.declIsRoot(some)) {
         return sema.fail(block, field_src, "root struct of file '{}' has no member named '{s}'", .{
             agg_ty.fmt(sema.mod), field_name,
@@ -4980,22 +5018,23 @@ fn failWithBadStructFieldAccess(
     block: *Block,
     struct_obj: *Module.Struct,
     field_src: LazySrcLoc,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
 ) CompileError {
+    const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
-    const fqn = try struct_obj.getFullyQualifiedName(sema.mod);
-    defer gpa.free(fqn);
+    const fqn = ip.stringToSlice(try struct_obj.getFullyQualifiedName(mod));
 
     const msg = msg: {
         const msg = try sema.errMsg(
             block,
             field_src,
             "no field named '{s}' in struct '{s}'",
-            .{ field_name, fqn },
+            .{ ip.stringToSlice(field_name), fqn },
         );
         errdefer msg.destroy(gpa);
-        try sema.mod.errNoteNonLazy(struct_obj.srcLoc(sema.mod), msg, "struct declared here", .{});
+        try mod.errNoteNonLazy(struct_obj.srcLoc(mod), msg, "struct declared here", .{});
         break :msg msg;
     };
     return sema.failWithOwnedErrorMsg(msg);
@@ -5006,22 +5045,23 @@ fn failWithBadUnionFieldAccess(
     block: *Block,
     union_obj: *Module.Union,
     field_src: LazySrcLoc,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
 ) CompileError {
+    const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
-    const fqn = try union_obj.getFullyQualifiedName(sema.mod);
-    defer gpa.free(fqn);
+    const fqn = ip.stringToSlice(try union_obj.getFullyQualifiedName(mod));
 
     const msg = msg: {
         const msg = try sema.errMsg(
             block,
             field_src,
             "no field named '{s}' in union '{s}'",
-            .{ field_name, fqn },
+            .{ ip.stringToSlice(field_name), fqn },
         );
         errdefer msg.destroy(gpa);
-        try sema.mod.errNoteNonLazy(union_obj.srcLoc(sema.mod), msg, "union declared here", .{});
+        try mod.errNoteNonLazy(union_obj.srcLoc(mod), msg, "union declared here", .{});
         break :msg msg;
     };
     return sema.failWithOwnedErrorMsg(msg);
@@ -5772,7 +5812,7 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
     const src = inst_data.src();
     const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
     const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
-    const decl_name = sema.code.nullTerminatedString(extra.decl_name);
+    const decl_name = try mod.intern_pool.getOrPutString(mod.gpa, sema.code.nullTerminatedString(extra.decl_name));
     const decl_index = if (extra.namespace != .none) index_blk: {
         const container_ty = try sema.resolveType(block, operand_src, extra.namespace);
         const container_namespace = container_ty.getNamespaceIndex(mod).unwrap().?;
@@ -5875,19 +5915,14 @@ pub fn analyzeExport(
     const new_export = try gpa.create(Export);
     errdefer gpa.destroy(new_export);
 
-    const symbol_name = try gpa.dupe(u8, borrowed_options.name);
-    errdefer gpa.free(symbol_name);
-
-    const section: ?[]const u8 = if (borrowed_options.section) |s| try gpa.dupe(u8, s) else null;
-    errdefer if (section) |s| gpa.free(s);
+    const symbol_name = try mod.intern_pool.getOrPutString(gpa, borrowed_options.name);
+    const section = try mod.intern_pool.getOrPutStringOpt(gpa, borrowed_options.section);
 
     new_export.* = .{
-        .options = .{
-            .name = symbol_name,
-            .linkage = borrowed_options.linkage,
-            .section = section,
-            .visibility = borrowed_options.visibility,
-        },
+        .name = symbol_name,
+        .linkage = borrowed_options.linkage,
+        .section = section,
+        .visibility = borrowed_options.visibility,
         .src = src,
         .owner_decl = sema.owner_decl_index,
         .src_decl = block.src_decl,
@@ -6121,23 +6156,25 @@ fn addDbgVar(
 }
 
 fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
     const src = inst_data.src();
-    const decl_name = inst_data.get(sema.code);
+    const decl_name = try mod.intern_pool.getOrPutString(sema.gpa, inst_data.get(sema.code));
     const decl_index = try sema.lookupIdentifier(block, src, decl_name);
     try sema.addReferencedBy(block, src, decl_index);
     return sema.analyzeDeclRef(decl_index);
 }
 
 fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
     const src = inst_data.src();
-    const decl_name = inst_data.get(sema.code);
+    const decl_name = try mod.intern_pool.getOrPutString(sema.gpa, inst_data.get(sema.code));
     const decl = try sema.lookupIdentifier(block, src, decl_name);
     return sema.analyzeDeclVal(block, src, decl);
 }
 
-fn lookupIdentifier(sema: *Sema, block: *Block, src: LazySrcLoc, name: []const u8) !Decl.Index {
+fn lookupIdentifier(sema: *Sema, block: *Block, src: LazySrcLoc, name: InternPool.NullTerminatedString) !Decl.Index {
     const mod = sema.mod;
     var namespace = block.namespace;
     while (true) {
@@ -6156,7 +6193,7 @@ fn lookupInNamespace(
     block: *Block,
     src: LazySrcLoc,
     namespace_index: Namespace.Index,
-    ident_name: []const u8,
+    ident_name: InternPool.NullTerminatedString,
     observe_usingnamespace: bool,
 ) CompileError!?Decl.Index {
     const mod = sema.mod;
@@ -6249,9 +6286,6 @@ fn lookupInNamespace(
         return decl_index;
     }
 
-    log.debug("{*} ({s}) depends on non-existence of '{s}' in {*} ({s})", .{
-        sema.owner_decl, sema.owner_decl.name, ident_name, namespace_decl, namespace_decl.name,
-    });
     // TODO This dependency is too strong. Really, it should only be a dependency
     // on the non-existence of `ident_name` in the namespace. We can lessen the number of
     // outdated declarations by making this dependency more sophisticated.
@@ -6276,10 +6310,12 @@ fn funcDeclSrc(sema: *Sema, func_inst: Air.Inst.Ref) !?*Decl {
 }
 
 pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref {
+    const mod = sema.mod;
+    const gpa = sema.gpa;
     const src = sema.src;
 
-    if (!sema.mod.backendSupportsFeature(.error_return_trace)) return .none;
-    if (!sema.mod.comp.bin_file.options.error_return_tracing) return .none;
+    if (!mod.backendSupportsFeature(.error_return_trace)) return .none;
+    if (!mod.comp.bin_file.options.error_return_tracing) return .none;
 
     if (block.is_comptime)
         return .none;
@@ -6292,7 +6328,8 @@ pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref
         error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
         else => |e| return e,
     };
-    const field_index = sema.structFieldIndex(block, stack_trace_ty, "index", src) catch |err| switch (err) {
+    const field_name = try mod.intern_pool.getOrPutString(gpa, "index");
+    const field_index = sema.structFieldIndex(block, stack_trace_ty, field_name, src) catch |err| switch (err) {
         error.NeededSourceLocation, error.GenericPoison, error.ComptimeReturn, error.ComptimeBreak => unreachable,
         else => |e| return e,
     };
@@ -6316,6 +6353,7 @@ fn popErrorReturnTrace(
     saved_error_trace_index: Air.Inst.Ref,
 ) CompileError!void {
     const mod = sema.mod;
+    const gpa = sema.gpa;
     var is_non_error: ?bool = null;
     var is_non_error_inst: Air.Inst.Ref = undefined;
     if (operand != .none) {
@@ -6332,13 +6370,14 @@ fn popErrorReturnTrace(
         const stack_trace_ty = try sema.resolveTypeFields(unresolved_stack_trace_ty);
         const ptr_stack_trace_ty = try mod.singleMutPtrType(stack_trace_ty);
         const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
-        const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, "index", src, stack_trace_ty, true);
+        const field_name = try mod.intern_pool.getOrPutString(gpa, "index");
+        const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, field_name, src, stack_trace_ty, true);
         try sema.storePtr2(block, src, field_ptr, src, saved_error_trace_index, src, .store);
     } else if (is_non_error == null) {
         // The result might be an error. If it is, we leave the error trace alone. If it isn't, we need
         // to pop any error trace that may have been propagated from our arguments.
 
-        try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).Struct.fields.len);
+        try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len);
         const cond_block_inst = try block.addInstAsIndex(.{
             .tag = .block,
             .data = .{
@@ -6350,28 +6389,29 @@ fn popErrorReturnTrace(
         });
 
         var then_block = block.makeSubBlock();
-        defer then_block.instructions.deinit(sema.gpa);
+        defer then_block.instructions.deinit(gpa);
 
         // If non-error, then pop the error return trace by restoring the index.
         const unresolved_stack_trace_ty = try sema.getBuiltinType("StackTrace");
         const stack_trace_ty = try sema.resolveTypeFields(unresolved_stack_trace_ty);
         const ptr_stack_trace_ty = try mod.singleMutPtrType(stack_trace_ty);
         const err_return_trace = try then_block.addTy(.err_return_trace, ptr_stack_trace_ty);
-        const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, "index", src, stack_trace_ty, true);
+        const field_name = try mod.intern_pool.getOrPutString(gpa, "index");
+        const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, field_name, src, stack_trace_ty, true);
         try sema.storePtr2(&then_block, src, field_ptr, src, saved_error_trace_index, src, .store);
         _ = try then_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
 
         // Otherwise, do nothing
         var else_block = block.makeSubBlock();
-        defer else_block.instructions.deinit(sema.gpa);
+        defer else_block.instructions.deinit(gpa);
         _ = try else_block.addBr(cond_block_inst, Air.Inst.Ref.void_value);
 
-        try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.CondBr).Struct.fields.len +
+        try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len +
             then_block.instructions.items.len + else_block.instructions.items.len +
             @typeInfo(Air.Block).Struct.fields.len + 1); // +1 for the sole .cond_br instruction in the .block
 
         const cond_br_inst = @intCast(Air.Inst.Index, sema.air_instructions.len);
-        try sema.air_instructions.append(sema.gpa, .{ .tag = .cond_br, .data = .{ .pl_op = .{
+        try sema.air_instructions.append(gpa, .{ .tag = .cond_br, .data = .{ .pl_op = .{
             .operand = is_non_error_inst,
             .payload = sema.addExtraAssumeCapacity(Air.CondBr{
                 .then_body_len = @intCast(u32, then_block.instructions.items.len),
@@ -6414,7 +6454,7 @@ fn zirCall(
         .direct => .{ .direct = try sema.resolveInst(extra.data.callee) },
         .field => blk: {
             const object_ptr = try sema.resolveInst(extra.data.obj_ptr);
-            const field_name = sema.code.nullTerminatedString(extra.data.field_name_start);
+            const field_name = try mod.intern_pool.getOrPutString(sema.gpa, sema.code.nullTerminatedString(extra.data.field_name_start));
             const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
             break :blk try sema.fieldCallBind(block, callee_src, object_ptr, field_name, field_name_src);
         },
@@ -6509,7 +6549,8 @@ fn zirCall(
         if (input_is_error or (pop_error_return_trace and modifier != .always_tail and return_ty.isError(mod))) {
             const unresolved_stack_trace_ty = try sema.getBuiltinType("StackTrace");
             const stack_trace_ty = try sema.resolveTypeFields(unresolved_stack_trace_ty);
-            const field_index = try sema.structFieldIndex(block, stack_trace_ty, "index", call_src);
+            const field_name = try mod.intern_pool.getOrPutString(sema.gpa, "index");
+            const field_index = try sema.structFieldIndex(block, stack_trace_ty, field_name, call_src);
 
             // Insert a save instruction before the arg resolution + call instructions we just generated
             const save_inst = try block.insertInst(block_index, .{
@@ -7436,9 +7477,10 @@ fn instantiateGenericCall(
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     const func_val = try sema.resolveConstValue(block, func_src, func, "generic function being called must be comptime-known");
-    const module_fn = mod.funcPtr(switch (mod.intern_pool.indexToKey(func_val.toIntern())) {
+    const module_fn = mod.funcPtr(switch (ip.indexToKey(func_val.toIntern())) {
         .func => |function| function.index,
         .ptr => |ptr| mod.declPtr(ptr.addr.decl).val.getFunctionIndex(mod).unwrap().?,
         else => unreachable,
@@ -7567,9 +7609,12 @@ fn instantiateGenericCall(
         const new_decl_index = try mod.allocateNewDecl(namespace_index, fn_owner_decl.src_node, src_decl.src_scope);
         const new_decl = mod.declPtr(new_decl_index);
         // TODO better names for generic function instantiations
-        const decl_name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{
-            fn_owner_decl.name, @enumToInt(new_decl_index),
-        });
+        // The ensureUnusedCapacity here protects against fn_owner_decl.name slice being
+        // reallocated during getOrPutStringFmt.
+        try ip.string_bytes.ensureUnusedCapacity(gpa, ip.stringToSlice(fn_owner_decl.name).len + 20);
+        const decl_name = ip.getOrPutStringFmt(gpa, "{s}__anon_{d}", .{
+            ip.stringToSlice(fn_owner_decl.name), @enumToInt(new_decl_index),
+        }) catch unreachable;
         new_decl.name = decl_name;
         new_decl.src_line = fn_owner_decl.src_line;
         new_decl.is_pub = fn_owner_decl.is_pub;
@@ -7590,12 +7635,8 @@ fn instantiateGenericCall(
         assert(new_decl.dependencies.keys().len == 0);
         try mod.declareDeclDependencyType(new_decl_index, module_fn.owner_decl, .function_body);
 
-        var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa);
-        const new_decl_arena_allocator = new_decl_arena.allocator();
-
         const new_func = sema.resolveGenericInstantiationType(
             block,
-            new_decl_arena_allocator,
             fn_zir,
             new_decl,
             new_decl_index,
@@ -7608,7 +7649,6 @@ fn instantiateGenericCall(
             bound_arg_src,
         ) catch |err| switch (err) {
             error.GenericPoison, error.ComptimeReturn => {
-                new_decl_arena.deinit();
                 // Resolving the new function type below will possibly declare more decl dependencies
                 // and so we remove them all here in case of error.
                 for (new_decl.dependencies.keys()) |dep_index| {
@@ -7623,10 +7663,6 @@ fn instantiateGenericCall(
             },
             else => {
                 assert(mod.monomorphed_funcs.removeContext(new_module_func_index, .{ .mod = mod }));
-                {
-                    errdefer new_decl_arena.deinit();
-                    try new_decl.finalizeNewArena(&new_decl_arena);
-                }
                 // TODO look up the compile error that happened here and attach a note to it
                 // pointing here, at the generic instantiation callsite.
                 if (sema.owner_func) |owner_func| {
@@ -7637,9 +7673,7 @@ fn instantiateGenericCall(
                 return err;
             },
         };
-        errdefer new_decl_arena.deinit();
 
-        try new_decl.finalizeNewArena(&new_decl_arena);
         break :callee new_func;
     } else gop.key_ptr.*;
     const callee = mod.funcPtr(callee_index);
@@ -7729,7 +7763,6 @@ fn instantiateGenericCall(
 fn resolveGenericInstantiationType(
     sema: *Sema,
     block: *Block,
-    new_decl_arena_allocator: Allocator,
     fn_zir: Zir,
     new_decl: *Decl,
     new_decl_index: Decl.Index,
@@ -7755,7 +7788,6 @@ fn resolveGenericInstantiationType(
         .mod = mod,
         .gpa = gpa,
         .arena = sema.arena,
-        .perm_arena = new_decl_arena_allocator,
         .code = fn_zir,
         .owner_decl = new_decl,
         .owner_decl_index = new_decl_index,
@@ -7764,7 +7796,8 @@ fn resolveGenericInstantiationType(
         .fn_ret_ty = Type.void,
         .owner_func = null,
         .owner_func_index = .none,
-        .comptime_args = try new_decl_arena_allocator.alloc(TypedValue, uncasted_args.len),
+        // TODO: fully migrate functions into InternPool
+        .comptime_args = try mod.tmp_hack_arena.allocator().alloc(TypedValue, uncasted_args.len),
         .comptime_args_fn_inst = module_fn.zir_body_inst,
         .preallocated_new_func = new_module_func.toOptional(),
         .is_generic_instantiation = true,
@@ -7931,10 +7964,6 @@ fn resolveGenericInstantiationType(
     new_decl.owns_tv = true;
     new_decl.analysis = .complete;
 
-    log.debug("generic function '{s}' instantiated with type {}", .{
-        new_decl.name, new_decl.ty.fmtDebug(),
-    });
-
     // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field
     // will be populated, ensuring it will have `analyzeBody` called with the ZIR
     // parameters mapped appropriately.
@@ -8134,13 +8163,13 @@ fn zirErrorValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
     _ = block;
     const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
-    const name = inst_data.get(sema.code);
+    const name = try mod.intern_pool.getOrPutString(sema.gpa, inst_data.get(sema.code));
+    _ = try mod.getErrorValue(name);
     // Create an error set type with only this error value, and return the value.
-    const kv = try sema.mod.getErrorValue(name);
-    const error_set_type = try mod.singleErrorSetType(kv.key);
+    const error_set_type = try mod.singleErrorSetTypeNts(name);
     return sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
         .ty = error_set_type.toIntern(),
-        .name = try mod.intern_pool.getOrPutString(sema.gpa, kv.key),
+        .name = name,
     } })).toValue());
 }
 
@@ -8162,7 +8191,7 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
         const err_name = mod.intern_pool.indexToKey(val.toIntern()).err.name;
         return sema.addConstant(Type.err_int, try mod.intValue(
             Type.err_int,
-            (try mod.getErrorValue(mod.intern_pool.stringToSlice(err_name))).value,
+            try mod.getErrorValue(err_name),
         ));
     }
 
@@ -8173,8 +8202,8 @@ fn zirErrorToInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
         switch (names.len) {
             0 => return sema.addConstant(Type.err_int, try mod.intValue(Type.err_int, 0)),
             1 => {
-                const name = mod.intern_pool.stringToSlice(names[0]);
-                return sema.addIntUnsigned(Type.err_int, mod.global_error_set.get(name).?);
+                const int = @intCast(Module.ErrorInt, mod.global_error_set.getIndex(names[0]).?);
+                return sema.addIntUnsigned(Type.err_int, int);
             },
             else => {},
         }
@@ -8197,11 +8226,11 @@ fn zirIntToError(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstDat
 
     if (try sema.resolveDefinedValue(block, operand_src, operand)) |value| {
         const int = try sema.usizeCast(block, operand_src, value.toUnsignedInt(mod));
-        if (int > sema.mod.global_error_set.count() or int == 0)
+        if (int > mod.global_error_set.count() or int == 0)
             return sema.fail(block, operand_src, "integer value '{d}' represents no error", .{int});
         return sema.addConstant(Type.anyerror, (try mod.intern(.{ .err = .{
             .ty = .anyerror_type,
-            .name = mod.intern_pool.getString(sema.mod.error_name_list.items[int]).unwrap().?,
+            .name = mod.global_error_set.keys()[int],
         } })).toValue());
     }
     try sema.requireRuntimeBlock(block, src, operand_src);
@@ -8917,7 +8946,7 @@ fn handleExternLibName(
 const FuncLinkSection = union(enum) {
     generic,
     default,
-    explicit: []const u8,
+    explicit: InternPool.NullTerminatedString,
 };
 
 fn funcCommon(
@@ -9186,9 +9215,9 @@ fn funcCommon(
     };
 
     sema.owner_decl.@"linksection" = switch (section) {
-        .generic => undefined,
-        .default => null,
-        .explicit => |section_name| try sema.perm_arena.dupeZ(u8, section_name),
+        .generic => .none,
+        .default => .none,
+        .explicit => |section_name| section_name.toOptional(),
     };
     sema.owner_decl.@"align" = alignment orelse 0;
     sema.owner_decl.@"addrspace" = address_space orelse .generic;
@@ -9572,11 +9601,12 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const tracy = trace(@src());
     defer tracy.end();
 
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
-    const field_name = sema.code.nullTerminatedString(extra.field_name_start);
+    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, sema.code.nullTerminatedString(extra.field_name_start));
     const object = try sema.resolveInst(extra.lhs);
     return sema.fieldVal(block, src, object, field_name, field_name_src);
 }
@@ -9585,11 +9615,12 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index, initializing: b
     const tracy = trace(@src());
     defer tracy.end();
 
+    const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
-    const field_name = sema.code.nullTerminatedString(extra.field_name_start);
+    const field_name = try mod.intern_pool.getOrPutString(sema.gpa, sema.code.nullTerminatedString(extra.field_name_start));
     const object_ptr = try sema.resolveInst(extra.lhs);
     return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, initializing);
 }
@@ -9603,7 +9634,7 @@ fn zirFieldValNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
     const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
     const object = try sema.resolveInst(extra.lhs);
-    const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.field_name, "field name must be comptime-known");
     return sema.fieldVal(block, src, object, field_name, field_name_src);
 }
 
@@ -9616,7 +9647,7 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr
     const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
     const object_ptr = try sema.resolveInst(extra.lhs);
-    const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name, "field name must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.field_name, "field name must be comptime-known");
     return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
 }
 
@@ -10434,6 +10465,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
 
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const src = inst_data.src();
     const src_node_offset = inst_data.src_node;
@@ -10605,7 +10637,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                             i,
                             msg,
                             "unhandled enumeration value: '{s}'",
-                            .{field_name},
+                            .{ip.stringToSlice(field_name)},
                         );
                     }
                     try mod.errNoteNonLazy(
@@ -10689,7 +10721,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
 
                 for (operand_ty.errorSetNames(mod)) |error_name_ip| {
-                    const error_name = mod.intern_pool.stringToSlice(error_name_ip);
+                    const error_name = ip.stringToSlice(error_name_ip);
                     if (!seen_errors.contains(error_name) and special_prong != .@"else") {
                         const msg = maybe_msg orelse blk: {
                             maybe_msg = try sema.errMsg(
@@ -10758,7 +10790,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
                 var names: Module.Fn.InferredErrorSet.NameMap = .{};
                 try names.ensureUnusedCapacity(sema.arena, error_names.len);
                 for (error_names) |error_name_ip| {
-                    const error_name = mod.intern_pool.stringToSlice(error_name_ip);
+                    const error_name = ip.stringToSlice(error_name_ip);
                     if (seen_errors.contains(error_name)) continue;
 
                     names.putAssumeCapacityNoClobber(error_name_ip, {});
@@ -12062,7 +12094,7 @@ fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
     const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
     const name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
     const unresolved_ty = try sema.resolveType(block, ty_src, extra.lhs);
-    const field_name = try sema.resolveConstString(block, name_src, extra.rhs, "field name must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, name_src, extra.rhs, "field name must be comptime-known");
     const ty = try sema.resolveTypeFields(unresolved_ty);
     const ip = &mod.intern_pool;
 
@@ -12070,19 +12102,17 @@ fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         switch (ip.indexToKey(ty.toIntern())) {
             .ptr_type => |ptr_type| switch (ptr_type.flags.size) {
                 .Slice => {
-                    if (mem.eql(u8, field_name, "ptr")) break :hf true;
-                    if (mem.eql(u8, field_name, "len")) break :hf true;
+                    if (ip.stringEqlSlice(field_name, "ptr")) break :hf true;
+                    if (ip.stringEqlSlice(field_name, "len")) break :hf true;
                     break :hf false;
                 },
                 else => {},
             },
             .anon_struct_type => |anon_struct| {
                 if (anon_struct.names.len != 0) {
-                    // If the string is not interned, then the field certainly is not present.
-                    const name_interned = ip.getString(field_name).unwrap() orelse break :hf false;
-                    break :hf mem.indexOfScalar(InternPool.NullTerminatedString, anon_struct.names, name_interned) != null;
+                    break :hf mem.indexOfScalar(InternPool.NullTerminatedString, anon_struct.names, field_name) != null;
                 } else {
-                    const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch break :hf false;
+                    const field_index = std.fmt.parseUnsigned(u32, ip.stringToSlice(field_name), 10) catch break :hf false;
                     break :hf field_index < ty.structFieldCount(mod);
                 }
             },
@@ -12097,11 +12127,9 @@ fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 break :hf union_obj.fields.contains(field_name);
             },
             .enum_type => |enum_type| {
-                // If the string is not interned, then the field certainly is not present.
-                const name_interned = ip.getString(field_name).unwrap() orelse break :hf false;
-                break :hf enum_type.nameIndex(ip, name_interned) != null;
+                break :hf enum_type.nameIndex(ip, field_name) != null;
             },
-            .array_type => break :hf mem.eql(u8, field_name, "len"),
+            .array_type => break :hf ip.stringEqlSlice(field_name, "len"),
             else => {},
         }
         return sema.fail(block, ty_src, "type '{}' does not support '@hasField'", .{
@@ -12123,7 +12151,7 @@ fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
     const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
     const container_type = try sema.resolveType(block, lhs_src, extra.lhs);
-    const decl_name = try sema.resolveConstString(block, rhs_src, extra.rhs, "decl name must be comptime-known");
+    const decl_name = try sema.resolveConstStringIntern(block, rhs_src, extra.rhs, "decl name must be comptime-known");
 
     try sema.checkNamespaceType(block, lhs_src, container_type);
 
@@ -12218,14 +12246,12 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
 fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
-    const err_name = inst_data.get(sema.code);
-
-    // Return the error code from the function.
-    const kv = try mod.getErrorValue(err_name);
-    const error_set_type = try mod.singleErrorSetType(kv.key);
+    const name = try mod.intern_pool.getOrPutString(sema.gpa, inst_data.get(sema.code));
+    _ = try mod.getErrorValue(name);
+    const error_set_type = try mod.singleErrorSetTypeNts(name);
     return sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
         .ty = error_set_type.toIntern(),
-        .name = mod.intern_pool.getString(kv.key).unwrap().?,
+        .name = name,
     } })).toValue());
 }
 
@@ -15730,12 +15756,7 @@ fn zirThis(
     return sema.analyzeDeclVal(block, src, this_decl_index);
 }
 
-fn zirClosureCapture(
-    sema: *Sema,
-    block: *Block,
-    inst: Zir.Inst.Index,
-) CompileError!void {
-    // TODO: Compile error when closed over values are modified
+fn zirClosureCapture(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
     const inst_data = sema.code.instructions.items(.data)[inst].un_tok;
     // Closures are not necessarily constant values. For example, the
     // code might do something like this:
@@ -15754,13 +15775,8 @@ fn zirClosureCapture(
     try block.wip_capture_scope.captures.putNoClobber(sema.gpa, inst, capture);
 }
 
-fn zirClosureGet(
-    sema: *Sema,
-    block: *Block,
-    inst: Zir.Inst.Index,
-) CompileError!Air.Inst.Ref {
+fn zirClosureGet(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    // TODO CLOSURE: Test this with inline functions
     const inst_data = sema.code.instructions.items(.data)[inst].inst_node;
     var scope: *CaptureScope = mod.declPtr(block.src_decl).src_scope.?;
     // Note: The target closure must be in this scope list.
@@ -15896,7 +15912,7 @@ fn zirBuiltinSrc(
     const func_name_val = blk: {
         var anon_decl = try block.startAnonDecl();
         defer anon_decl.deinit();
-        const name = mem.span(fn_owner_decl.name);
+        const name = mod.intern_pool.stringToSlice(fn_owner_decl.name);
         const new_decl_ty = try mod.arrayType(.{
             .len = name.len,
             .child = .u8_type,
@@ -15965,6 +15981,7 @@ fn zirBuiltinSrc(
 fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const inst_data = sema.code.instructions.items(.data)[inst].un_node;
     const src = inst_data.src();
     const ty = try sema.resolveType(block, src, inst_data.operand);
@@ -15995,7 +16012,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                "Fn",
+                try ip.getOrPutString(gpa, "Fn"),
             )).?;
             try mod.declareDeclDependency(sema.owner_decl_index, fn_info_decl_index);
             try sema.ensureDeclAnalyzed(fn_info_decl_index);
@@ -16006,7 +16023,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 fn_info_ty.getNamespaceIndex(mod).unwrap().?,
-                "Param",
+                try ip.getOrPutString(gpa, "Param"),
             )).?;
             try mod.declareDeclDependency(sema.owner_decl_index, param_info_decl_index);
             try sema.ensureDeclAnalyzed(param_info_decl_index);
@@ -16018,8 +16035,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 const info = mod.typeToFunc(ty).?;
                 const param_ty = info.param_types[i];
                 const is_generic = param_ty == .generic_poison_type;
-                const param_ty_val = try mod.intern_pool.get(gpa, .{ .opt = .{
-                    .ty = try mod.intern_pool.get(gpa, .{ .opt_type = .type_type }),
+                const param_ty_val = try ip.get(gpa, .{ .opt = .{
+                    .ty = try ip.get(gpa, .{ .opt_type = .type_type }),
                     .val = if (is_generic) .none else param_ty,
                 } });
 
@@ -16070,7 +16087,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const info = mod.typeToFunc(ty).?;
             const ret_ty_opt = try mod.intern(.{ .opt = .{
-                .ty = try mod.intern_pool.get(gpa, .{ .opt_type = .type_type }),
+                .ty = try ip.get(gpa, .{ .opt_type = .type_type }),
                 .val = if (info.return_type == .generic_poison_type) .none else info.return_type,
             } });
 
@@ -16104,7 +16121,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                "Int",
+                try ip.getOrPutString(gpa, "Int"),
             )).?;
             try mod.declareDeclDependency(sema.owner_decl_index, int_info_decl_index);
             try sema.ensureDeclAnalyzed(int_info_decl_index);
@@ -16133,7 +16150,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 block,
                 src,
                 type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                "Float",
+                try ip.getOrPutString(gpa, "Float"),
             )).?;
             try mod.declareDeclDependency(sema.owner_decl_index, float_info_decl_index);
             try sema.ensureDeclAnalyzed(float_info_decl_index);
@@ -16166,7 +16183,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     (try sema.getBuiltinType("Type")).getNamespaceIndex(mod).unwrap().?,
-                    "Pointer",
+                    try ip.getOrPutString(gpa, "Pointer"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, decl_index);
                 try sema.ensureDeclAnalyzed(decl_index);
@@ -16178,7 +16195,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     pointer_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Size",
+                    try ip.getOrPutString(gpa, "Size"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, decl_index);
                 try sema.ensureDeclAnalyzed(decl_index);
@@ -16219,7 +16236,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Array",
+                    try ip.getOrPutString(gpa, "Array"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, array_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(array_field_ty_decl_index);
@@ -16251,7 +16268,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Vector",
+                    try ip.getOrPutString(gpa, "Vector"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, vector_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(vector_field_ty_decl_index);
@@ -16281,7 +16298,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Optional",
+                    try ip.getOrPutString(gpa, "Optional"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, optional_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(optional_field_ty_decl_index);
@@ -16312,7 +16329,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Error",
+                    try ip.getOrPutString(gpa, "Error"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, set_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(set_field_ty_decl_index);
@@ -16332,7 +16349,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                 const names = ty.errorSetNames(mod);
                 const vals = try sema.arena.alloc(InternPool.Index, names.len);
                 for (vals, names) |*field_val, name_ip| {
-                    const name = mod.intern_pool.stringToSlice(name_ip);
+                    const name = ip.stringToSlice(name_ip);
                     const name_val = v: {
                         var anon_decl = try block.startAnonDecl();
                         defer anon_decl.deinit();
@@ -16415,7 +16432,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "ErrorUnion",
+                    try ip.getOrPutString(gpa, "ErrorUnion"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, error_union_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(error_union_field_ty_decl_index);
@@ -16440,7 +16457,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
         },
         .Enum => {
             // TODO: look into memoizing this result.
-            const enum_type = mod.intern_pool.indexToKey(ty.toIntern()).enum_type;
+            const enum_type = ip.indexToKey(ty.toIntern()).enum_type;
 
             const is_exhaustive = Value.makeBool(enum_type.tag_mode != .nonexhaustive);
 
@@ -16452,7 +16469,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "EnumField",
+                    try ip.getOrPutString(gpa, "EnumField"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, enum_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(enum_field_ty_decl_index);
@@ -16462,8 +16479,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             const enum_field_vals = try sema.arena.alloc(InternPool.Index, enum_type.names.len);
             for (enum_field_vals, 0..) |*field_val, i| {
-                const name_ip = mod.intern_pool.indexToKey(ty.toIntern()).enum_type.names[i];
-                const name = mod.intern_pool.stringToSlice(name_ip);
+                const name_ip = ip.indexToKey(ty.toIntern()).enum_type.names[i];
+                const name = ip.stringToSlice(name_ip);
                 const name_val = v: {
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
@@ -16532,7 +16549,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Enum",
+                    try ip.getOrPutString(gpa, "Enum"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, type_enum_ty_decl_index);
                 try sema.ensureDeclAnalyzed(type_enum_ty_decl_index);
@@ -16570,7 +16587,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Union",
+                    try ip.getOrPutString(gpa, "Union"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, type_union_ty_decl_index);
                 try sema.ensureDeclAnalyzed(type_union_ty_decl_index);
@@ -16583,7 +16600,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "UnionField",
+                    try ip.getOrPutString(gpa, "UnionField"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, union_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(union_field_ty_decl_index);
@@ -16601,7 +16618,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
 
             for (union_field_vals, 0..) |*field_val, i| {
                 const field = union_fields.values()[i];
-                const name = union_fields.keys()[i];
+                const name = ip.stringToSlice(union_fields.keys()[i]);
                 const name_val = v: {
                     var anon_decl = try block.startAnonDecl();
                     defer anon_decl.deinit();
@@ -16682,7 +16699,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     (try sema.getBuiltinType("Type")).getNamespaceIndex(mod).unwrap().?,
-                    "ContainerLayout",
+                    try ip.getOrPutString(gpa, "ContainerLayout"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, decl_index);
                 try sema.ensureDeclAnalyzed(decl_index);
@@ -16721,7 +16738,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Struct",
+                    try ip.getOrPutString(gpa, "Struct"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, type_struct_ty_decl_index);
                 try sema.ensureDeclAnalyzed(type_struct_ty_decl_index);
@@ -16734,7 +16751,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "StructField",
+                    try ip.getOrPutString(gpa, "StructField"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, struct_field_ty_decl_index);
                 try sema.ensureDeclAnalyzed(struct_field_ty_decl_index);
@@ -16749,11 +16766,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
             var struct_field_vals: []InternPool.Index = &.{};
             defer gpa.free(struct_field_vals);
             fv: {
-                const struct_type = switch (mod.intern_pool.indexToKey(struct_ty.toIntern())) {
+                const struct_type = switch (ip.indexToKey(struct_ty.toIntern())) {
                     .anon_struct_type => |tuple| {
                         struct_field_vals = try gpa.alloc(InternPool.Index, tuple.types.len);
                         for (struct_field_vals, 0..) |*struct_field_val, i| {
-                            const anon_struct_type = mod.intern_pool.indexToKey(struct_ty.toIntern()).anon_struct_type;
+                            const anon_struct_type = ip.indexToKey(struct_ty.toIntern()).anon_struct_type;
                             const field_ty = anon_struct_type.types[i];
                             const field_val = anon_struct_type.values[i];
                             const name_val = v: {
@@ -16761,7 +16778,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                                 defer anon_decl.deinit();
                                 const bytes = if (tuple.names.len != 0)
                                     // https://github.com/ziglang/zig/issues/15709
-                                    @as([]const u8, mod.intern_pool.stringToSlice(tuple.names[i]))
+                                    @as([]const u8, ip.stringToSlice(tuple.names[i]))
                                 else
                                     try std.fmt.allocPrint(sema.arena, "{d}", .{i});
                                 const new_decl_ty = try mod.arrayType(.{
@@ -16815,7 +16832,8 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     struct_field_vals,
                     struct_obj.fields.keys(),
                     struct_obj.fields.values(),
-                ) |*field_val, name, field| {
+                ) |*field_val, name_nts, field| {
+                    const name = ip.stringToSlice(name_nts);
                     const name_val = v: {
                         var anon_decl = try block.startAnonDecl();
                         defer anon_decl.deinit();
@@ -16838,10 +16856,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                         } });
                     };
 
-                    const opt_default_val = if (field.default_val.toIntern() == .unreachable_value)
+                    const opt_default_val = if (field.default_val == .none)
                         null
                     else
-                        field.default_val;
+                        field.default_val.toValue();
                     const default_val_ptr = try sema.optRefValue(block, field.ty, opt_default_val);
                     const alignment = field.alignment(mod, layout);
 
@@ -16908,7 +16926,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     (try sema.getBuiltinType("Type")).getNamespaceIndex(mod).unwrap().?,
-                    "ContainerLayout",
+                    try ip.getOrPutString(gpa, "ContainerLayout"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, decl_index);
                 try sema.ensureDeclAnalyzed(decl_index);
@@ -16945,7 +16963,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
                     block,
                     src,
                     type_info_ty.getNamespaceIndex(mod).unwrap().?,
-                    "Opaque",
+                    try ip.getOrPutString(gpa, "Opaque"),
                 )).?;
                 try mod.declareDeclDependency(sema.owner_decl_index, type_opaque_ty_decl_index);
                 try sema.ensureDeclAnalyzed(type_opaque_ty_decl_index);
@@ -16982,6 +17000,8 @@ fn typeInfoDecls(
     opt_namespace: Module.Namespace.OptionalIndex,
 ) CompileError!InternPool.Index {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+
     var decls_anon_decl = try block.startAnonDecl();
     defer decls_anon_decl.deinit();
 
@@ -16990,7 +17010,7 @@ fn typeInfoDecls(
             block,
             src,
             type_info_ty.getNamespaceIndex(mod).unwrap().?,
-            "Declaration",
+            try mod.intern_pool.getOrPutString(gpa, "Declaration"),
         )).?;
         try mod.declareDeclDependency(sema.owner_decl_index, declaration_ty_decl_index);
         try sema.ensureDeclAnalyzed(declaration_ty_decl_index);
@@ -16999,10 +17019,10 @@ fn typeInfoDecls(
     };
     try sema.queueFullTypeResolution(declaration_ty);
 
-    var decl_vals = std.ArrayList(InternPool.Index).init(sema.gpa);
+    var decl_vals = std.ArrayList(InternPool.Index).init(gpa);
     defer decl_vals.deinit();
 
-    var seen_namespaces = std.AutoHashMap(*Namespace, void).init(sema.gpa);
+    var seen_namespaces = std.AutoHashMap(*Namespace, void).init(gpa);
     defer seen_namespaces.deinit();
 
     if (opt_namespace.unwrap()) |namespace_index| {
@@ -17061,7 +17081,7 @@ fn typeInfoNamespaceDecls(
         const name_val = v: {
             var anon_decl = try block.startAnonDecl();
             defer anon_decl.deinit();
-            const name = mem.span(decl.name);
+            const name = mod.intern_pool.stringToSlice(decl.name);
             const new_decl_ty = try mod.arrayType(.{
                 .len = name.len,
                 .child = .u8_type,
@@ -17696,15 +17716,14 @@ fn zirRetErrValue(
 ) CompileError!Zir.Inst.Index {
     const mod = sema.mod;
     const inst_data = sema.code.instructions.items(.data)[inst].str_tok;
-    const err_name = inst_data.get(sema.code);
+    const err_name = try mod.intern_pool.getOrPutString(sema.gpa, inst_data.get(sema.code));
+    _ = try mod.getErrorValue(err_name);
     const src = inst_data.src();
-
     // Return the error code from the function.
-    const kv = try mod.getErrorValue(err_name);
-    const error_set_type = try mod.singleErrorSetType(err_name);
+    const error_set_type = try mod.singleErrorSetTypeNts(err_name);
     const result_inst = try sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
         .ty = error_set_type.toIntern(),
-        .name = try mod.intern_pool.getOrPutString(sema.gpa, kv.key),
+        .name = err_name,
     } })).toValue());
     return sema.analyzeRet(block, result_inst, src);
 }
@@ -18177,7 +18196,7 @@ fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
     const init_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
     const extra = sema.code.extraData(Zir.Inst.UnionInit, inst_data.payload_index).data;
     const union_ty = try sema.resolveType(block, ty_src, extra.union_type);
-    const field_name = try sema.resolveConstString(block, field_src, extra.field_name, "name of field being initialized must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, "name of field being initialized must be comptime-known");
     const init = try sema.resolveInst(extra.init);
     return sema.unionInit(block, init, init_src, union_ty, ty_src, field_name, field_src);
 }
@@ -18189,7 +18208,7 @@ fn unionInit(
     init_src: LazySrcLoc,
     union_ty: Type,
     union_ty_src: LazySrcLoc,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
@@ -18257,7 +18276,7 @@ fn zirStructInit(
             const field_type_data = zir_datas[item.data.field_type].pl_node;
             const field_src: LazySrcLoc = .{ .node_offset_initializer = field_type_data.src_node };
             const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
-            const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
+            const field_name = try mod.intern_pool.getOrPutString(gpa, sema.code.nullTerminatedString(field_type_extra.name_start));
             const field_index = if (resolved_ty.isTuple(mod))
                 try sema.tupleFieldIndex(block, resolved_ty, field_name, field_src)
             else
@@ -18298,7 +18317,7 @@ fn zirStructInit(
         const field_type_data = zir_datas[item.data.field_type].pl_node;
         const field_src: LazySrcLoc = .{ .node_offset_initializer = field_type_data.src_node };
         const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
-        const field_name = sema.code.nullTerminatedString(field_type_extra.name_start);
+        const field_name = try mod.intern_pool.getOrPutString(gpa, sema.code.nullTerminatedString(field_type_extra.name_start));
         const field_index = try sema.unionFieldIndex(block, resolved_ty, field_name, field_src);
         const tag_ty = resolved_ty.unionTagTypeHypothetical(mod);
         const enum_field_index = @intCast(u32, tag_ty.enumFieldIndex(field_name, mod).?);
@@ -18347,12 +18366,12 @@ fn finishStructInit(
     is_ref: bool,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     var root_msg: ?*Module.ErrorMsg = null;
     errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
 
-    switch (mod.intern_pool.indexToKey(struct_ty.toIntern())) {
+    switch (ip.indexToKey(struct_ty.toIntern())) {
         .anon_struct_type => |anon_struct| {
             for (anon_struct.types, anon_struct.values, 0..) |field_ty, default_val, i| {
                 if (field_inits[i] != .none) continue;
@@ -18366,9 +18385,9 @@ fn finishStructInit(
                             root_msg = try sema.errMsg(block, init_src, template, .{i});
                         }
                     } else {
-                        const field_name = mod.intern_pool.stringToSlice(anon_struct.names[i]);
+                        const field_name = anon_struct.names[i];
                         const template = "missing struct field: {s}";
-                        const args = .{field_name};
+                        const args = .{ip.stringToSlice(field_name)};
                         if (root_msg) |msg| {
                             try sema.errNote(block, init_src, msg, template, args);
                         } else {
@@ -18385,17 +18404,17 @@ fn finishStructInit(
             for (struct_obj.fields.values(), 0..) |field, i| {
                 if (field_inits[i] != .none) continue;
 
-                if (field.default_val.toIntern() == .unreachable_value) {
+                if (field.default_val == .none) {
                     const field_name = struct_obj.fields.keys()[i];
                     const template = "missing struct field: {s}";
-                    const args = .{field_name};
+                    const args = .{ip.stringToSlice(field_name)};
                     if (root_msg) |msg| {
                         try sema.errNote(block, init_src, msg, template, args);
                     } else {
                         root_msg = try sema.errMsg(block, init_src, template, args);
                     }
                 } else {
-                    field_inits[i] = try sema.addConstant(field.ty, field.default_val);
+                    field_inits[i] = try sema.addConstant(field.ty, field.default_val.toValue());
                 }
             }
         },
@@ -18404,10 +18423,9 @@ fn finishStructInit(
 
     if (root_msg) |msg| {
         if (mod.typeToStruct(struct_ty)) |struct_obj| {
-            const fqn = try struct_obj.getFullyQualifiedName(sema.mod);
-            defer gpa.free(fqn);
-            try sema.mod.errNoteNonLazy(
-                struct_obj.srcLoc(sema.mod),
+            const fqn = ip.stringToSlice(try struct_obj.getFullyQualifiedName(mod));
+            try mod.errNoteNonLazy(
+                struct_obj.srcLoc(mod),
                 msg,
                 "struct '{s}' declared here",
                 .{fqn},
@@ -18826,11 +18844,13 @@ fn zirFieldTypeRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro
     const ty_src = inst_data.src();
     const field_src = inst_data.src();
     const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type);
-    const field_name = try sema.resolveConstString(block, field_src, extra.field_name, "field name must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, "field name must be comptime-known");
     return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src);
 }
 
 fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+    const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
     const ty_src = inst_data.src();
@@ -18843,7 +18863,8 @@ fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         error.GenericPoison => return Air.Inst.Ref.generic_poison_type,
         else => |e| return e,
     };
-    const field_name = sema.code.nullTerminatedString(extra.name_start);
+    const zir_field_name = sema.code.nullTerminatedString(extra.name_start);
+    const field_name = try ip.getOrPutString(sema.gpa, zir_field_name);
     return sema.fieldType(block, aggregate_ty, field_name, field_name_src, ty_src);
 }
 
@@ -18851,7 +18872,7 @@ fn fieldType(
     sema: *Sema,
     block: *Block,
     aggregate_ty: Type,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_src: LazySrcLoc,
     ty_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
@@ -19050,13 +19071,14 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
     const operand = try sema.resolveInst(inst_data.operand);
     const operand_ty = sema.typeOf(operand);
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
 
     try sema.resolveTypeLayout(operand_ty);
     const enum_ty = switch (operand_ty.zigTypeTag(mod)) {
         .EnumLiteral => {
             const val = try sema.resolveConstValue(block, .unneeded, operand, "");
-            const tag_name = mod.intern_pool.indexToKey(val.toIntern()).enum_literal;
-            const bytes = mod.intern_pool.stringToSlice(tag_name);
+            const tag_name = ip.indexToKey(val.toIntern()).enum_literal;
+            const bytes = ip.stringToSlice(tag_name);
             return sema.addStrLit(block, bytes);
         },
         .Enum => operand_ty,
@@ -19089,7 +19111,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
             const enum_decl = mod.declPtr(enum_decl_index);
             const msg = msg: {
                 const msg = try sema.errMsg(block, src, "no field with value '{}' in enum '{s}'", .{
-                    val.fmtValue(enum_ty, sema.mod), enum_decl.name,
+                    val.fmtValue(enum_ty, sema.mod), ip.stringToSlice(enum_decl.name),
                 });
                 errdefer msg.destroy(sema.gpa);
                 try mod.errNoteNonLazy(enum_decl.srcLoc(mod), msg, "declared here", .{});
@@ -19098,7 +19120,7 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
             return sema.failWithOwnedErrorMsg(msg);
         };
         const field_name = enum_ty.enumFieldName(field_index, mod);
-        return sema.addStrLit(block, field_name);
+        return sema.addStrLit(block, ip.stringToSlice(field_name));
     }
     try sema.requireRuntimeBlock(block, src, operand_src);
     if (block.wantSafety() and sema.mod.backendSupportsFeature(.is_named_enum_value)) {
@@ -19119,6 +19141,7 @@ fn zirReify(
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const name_strategy = @intToEnum(Zir.Inst.NameStrategy, extended.small);
     const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
     const src = LazySrcLoc.nodeOffset(extra.node);
@@ -19127,11 +19150,10 @@ fn zirReify(
     const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node };
     const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src);
     const val = try sema.resolveConstValue(block, operand_src, type_info, "operand to @Type must be comptime-known");
-    const union_val = mod.intern_pool.indexToKey(val.toIntern()).un;
+    const union_val = ip.indexToKey(val.toIntern()).un;
     const target = mod.getTarget();
     if (try union_val.val.toValue().anyUndef(mod)) return sema.failWithUseOfUndef(block, src);
     const tag_index = type_info_ty.unionTagFieldIndex(union_val.tag.toValue(), mod).?;
-    const ip = &mod.intern_pool;
     switch (@intToEnum(std.builtin.TypeId, tag_index)) {
         .Type => return Air.Inst.Ref.type_type,
         .Void => return Air.Inst.Ref.void_type,
@@ -19145,8 +19167,14 @@ fn zirReify(
         .EnumLiteral => return Air.Inst.Ref.enum_literal_type,
         .Int => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const signedness_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("signedness").?);
-            const bits_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("bits").?);
+            const signedness_val = try union_val.val.toValue().fieldValue(
+                mod,
+                fields.getIndex(try ip.getOrPutString(gpa, "signedness")).?,
+            );
+            const bits_val = try union_val.val.toValue().fieldValue(
+                mod,
+                fields.getIndex(try ip.getOrPutString(gpa, "bits")).?,
+            );
 
             const signedness = mod.toEnum(std.builtin.Signedness, signedness_val);
             const bits = @intCast(u16, bits_val.toUnsignedInt(mod));
@@ -19155,8 +19183,12 @@ fn zirReify(
         },
         .Vector => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const len_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("len").?);
-            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("child").?);
+            const len_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "len"),
+            ).?);
+            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "child"),
+            ).?);
 
             const len = @intCast(u32, len_val.toUnsignedInt(mod));
             const child_ty = child_val.toType();
@@ -19171,7 +19203,9 @@ fn zirReify(
         },
         .Float => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const bits_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("bits").?);
+            const bits_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "bits"),
+            ).?);
 
             const bits = @intCast(u16, bits_val.toUnsignedInt(mod));
             const ty = switch (bits) {
@@ -19186,14 +19220,30 @@ fn zirReify(
         },
         .Pointer => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const size_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("size").?);
-            const is_const_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_const").?);
-            const is_volatile_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_volatile").?);
-            const alignment_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("alignment").?);
-            const address_space_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("address_space").?);
-            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("child").?);
-            const is_allowzero_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_allowzero").?);
-            const sentinel_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("sentinel").?);
+            const size_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "size"),
+            ).?);
+            const is_const_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_const"),
+            ).?);
+            const is_volatile_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_volatile"),
+            ).?);
+            const alignment_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "alignment"),
+            ).?);
+            const address_space_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "address_space"),
+            ).?);
+            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "child"),
+            ).?);
+            const is_allowzero_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_allowzero"),
+            ).?);
+            const sentinel_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "sentinel"),
+            ).?);
 
             if (!try sema.intFitsInType(alignment_val, Type.u32, null)) {
                 return sema.fail(block, src, "alignment must fit in 'u32'", .{});
@@ -19279,9 +19329,15 @@ fn zirReify(
         },
         .Array => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const len_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("len").?);
-            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("child").?);
-            const sentinel_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("sentinel").?);
+            const len_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "len"),
+            ).?);
+            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "child"),
+            ).?);
+            const sentinel_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "sentinel"),
+            ).?);
 
             const len = len_val.toUnsignedInt(mod);
             const child_ty = child_val.toType();
@@ -19298,7 +19354,9 @@ fn zirReify(
         },
         .Optional => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("child").?);
+            const child_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "child"),
+            ).?);
 
             const child_ty = child_val.toType();
 
@@ -19307,8 +19365,12 @@ fn zirReify(
         },
         .ErrorUnion => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const error_set_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("error_set").?);
-            const payload_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("payload").?);
+            const error_set_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "error_set"),
+            ).?);
+            const payload_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "payload"),
+            ).?);
 
             const error_set_ty = error_set_val.toType();
             const payload_ty = payload_val.toType();
@@ -19330,14 +19392,17 @@ fn zirReify(
             for (0..len) |i| {
                 const elem_val = try payload_val.elemValue(mod, i);
                 const elem_fields = ip.typeOf(elem_val.toIntern()).toType().structFields(mod);
-                const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex("name").?);
+                const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "name"),
+                ).?);
 
-                const name_str = try name_val.toAllocatedBytes(Type.slice_const_u8, sema.arena, mod);
-                const kv = try mod.getErrorValue(name_str);
-                const name_ip = try mod.intern_pool.getOrPutString(gpa, kv.key);
-                const gop = names.getOrPutAssumeCapacity(name_ip);
+                const name = try name_val.toIpString(Type.slice_const_u8, mod);
+                _ = try mod.getErrorValue(name);
+                const gop = names.getOrPutAssumeCapacity(name);
                 if (gop.found_existing) {
-                    return sema.fail(block, src, "duplicate error '{s}'", .{name_str});
+                    return sema.fail(block, src, "duplicate error '{s}'", .{
+                        ip.stringToSlice(name),
+                    });
                 }
             }
 
@@ -19346,11 +19411,21 @@ fn zirReify(
         },
         .Struct => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const layout_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("layout").?);
-            const backing_integer_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("backing_integer").?);
-            const fields_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("fields").?);
-            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("decls").?);
-            const is_tuple_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_tuple").?);
+            const layout_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "layout"),
+            ).?);
+            const backing_integer_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "backing_integer"),
+            ).?);
+            const fields_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "fields"),
+            ).?);
+            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "decls"),
+            ).?);
+            const is_tuple_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_tuple"),
+            ).?);
 
             const layout = mod.toEnum(std.builtin.Type.ContainerLayout, layout_val);
 
@@ -19367,10 +19442,18 @@ fn zirReify(
         },
         .Enum => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const tag_type_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("tag_type").?);
-            const fields_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("fields").?);
-            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("decls").?);
-            const is_exhaustive_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_exhaustive").?);
+            const tag_type_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "tag_type"),
+            ).?);
+            const fields_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "fields"),
+            ).?);
+            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "decls"),
+            ).?);
+            const is_exhaustive_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_exhaustive"),
+            ).?);
 
             // Decls
             if (decls_val.sliceLen(mod) > 0) {
@@ -19396,7 +19479,7 @@ fn zirReify(
 
             // Define our empty enum decl
             const fields_len = @intCast(u32, try sema.usizeCast(block, src, fields_val.sliceLen(mod)));
-            const incomplete_enum = try mod.intern_pool.getIncompleteEnum(gpa, .{
+            const incomplete_enum = try ip.getIncompleteEnum(gpa, .{
                 .decl = new_decl_index,
                 .namespace = .none,
                 .fields_len = fields_len,
@@ -19407,35 +19490,36 @@ fn zirReify(
                     .explicit,
                 .tag_ty = int_tag_ty.toIntern(),
             });
-            errdefer mod.intern_pool.remove(incomplete_enum.index);
+            errdefer ip.remove(incomplete_enum.index);
 
             new_decl.val = incomplete_enum.index.toValue();
 
             for (0..fields_len) |field_i| {
                 const elem_val = try fields_val.elemValue(mod, field_i);
                 const elem_fields = ip.typeOf(elem_val.toIntern()).toType().structFields(mod);
-                const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex("name").?);
-                const value_val = try elem_val.fieldValue(mod, elem_fields.getIndex("value").?);
+                const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "name"),
+                ).?);
+                const value_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "value"),
+                ).?);
 
-                const field_name = try name_val.toAllocatedBytes(
-                    Type.slice_const_u8,
-                    sema.arena,
-                    mod,
-                );
-                const field_name_ip = try mod.intern_pool.getOrPutString(gpa, field_name);
+                const field_name = try name_val.toIpString(Type.slice_const_u8, mod);
 
                 if (!try sema.intFitsInType(value_val, int_tag_ty, null)) {
                     // TODO: better source location
                     return sema.fail(block, src, "field '{s}' with enumeration value '{}' is too large for backing int type '{}'", .{
-                        field_name,
+                        ip.stringToSlice(field_name),
                         value_val.fmtValue(Type.comptime_int, mod),
                         int_tag_ty.fmt(mod),
                     });
                 }
 
-                if (try incomplete_enum.addFieldName(&mod.intern_pool, gpa, field_name_ip)) |other_index| {
+                if (try incomplete_enum.addFieldName(ip, gpa, field_name)) |other_index| {
                     const msg = msg: {
-                        const msg = try sema.errMsg(block, src, "duplicate enum field '{s}'", .{field_name});
+                        const msg = try sema.errMsg(block, src, "duplicate enum field '{s}'", .{
+                            ip.stringToSlice(field_name),
+                        });
                         errdefer msg.destroy(gpa);
                         _ = other_index; // TODO: this note is incorrect
                         try sema.errNote(block, src, msg, "other field here", .{});
@@ -19444,7 +19528,7 @@ fn zirReify(
                     return sema.failWithOwnedErrorMsg(msg);
                 }
 
-                if (try incomplete_enum.addFieldValue(&mod.intern_pool, gpa, (try mod.getCoerced(value_val, int_tag_ty)).toIntern())) |other| {
+                if (try incomplete_enum.addFieldValue(ip, gpa, (try mod.getCoerced(value_val, int_tag_ty)).toIntern())) |other| {
                     const msg = msg: {
                         const msg = try sema.errMsg(block, src, "enum tag value {} already taken", .{value_val.fmtValue(Type.comptime_int, mod)});
                         errdefer msg.destroy(gpa);
@@ -19462,7 +19546,9 @@ fn zirReify(
         },
         .Opaque => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("decls").?);
+            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "decls"),
+            ).?);
 
             // Decls
             if (decls_val.sliceLen(mod) > 0) {
@@ -19496,22 +19582,29 @@ fn zirReify(
                 .decl = new_decl_index,
                 .namespace = new_namespace_index,
             } });
-            errdefer mod.intern_pool.remove(opaque_ty);
+            errdefer ip.remove(opaque_ty);
 
             new_decl.val = opaque_ty.toValue();
             new_namespace.ty = opaque_ty.toType();
 
-            try new_decl.finalizeNewArena(&new_decl_arena);
             const decl_val = sema.analyzeDeclVal(block, src, new_decl_index);
             try mod.finalizeAnonDecl(new_decl_index);
             return decl_val;
         },
         .Union => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const layout_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("layout").?);
-            const tag_type_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("tag_type").?);
-            const fields_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("fields").?);
-            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("decls").?);
+            const layout_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "layout"),
+            ).?);
+            const tag_type_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "tag_type"),
+            ).?);
+            const fields_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "fields"),
+            ).?);
+            const decls_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "decls"),
+            ).?);
 
             // Decls
             if (decls_val.sliceLen(mod) > 0) {
@@ -19555,7 +19648,7 @@ fn zirReify(
             const union_obj = mod.unionPtr(union_index);
             errdefer mod.destroyUnion(union_index);
 
-            const union_ty = try mod.intern_pool.get(gpa, .{ .union_type = .{
+            const union_ty = try ip.get(gpa, .{ .union_type = .{
                 .index = union_index,
                 .runtime_tag = if (!tag_type_val.isNull(mod))
                     .tagged
@@ -19566,7 +19659,7 @@ fn zirReify(
                     .ReleaseFast, .ReleaseSmall => .none,
                 },
             } });
-            errdefer mod.intern_pool.remove(union_ty);
+            errdefer ip.remove(union_ty);
 
             new_decl.val = union_ty.toValue();
             new_namespace.ty = union_ty.toType();
@@ -19579,7 +19672,7 @@ fn zirReify(
             if (tag_type_val.optionalValue(mod)) |payload_val| {
                 union_obj.tag_ty = payload_val.toType();
 
-                const enum_type = switch (mod.intern_pool.indexToKey(union_obj.tag_ty.toIntern())) {
+                const enum_type = switch (ip.indexToKey(union_obj.tag_ty.toIntern())) {
                     .enum_type => |x| x,
                     else => return sema.fail(block, src, "Type.Union.tag_type must be an enum type", .{}),
                 };
@@ -19597,26 +19690,26 @@ fn zirReify(
             for (0..fields_len) |i| {
                 const elem_val = try fields_val.elemValue(mod, i);
                 const elem_fields = ip.typeOf(elem_val.toIntern()).toType().structFields(mod);
-                const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex("name").?);
-                const type_val = try elem_val.fieldValue(mod, elem_fields.getIndex("type").?);
-                const alignment_val = try elem_val.fieldValue(mod, elem_fields.getIndex("alignment").?);
-
-                const field_name = try name_val.toAllocatedBytes(
-                    Type.slice_const_u8,
-                    new_decl_arena_allocator,
-                    mod,
-                );
-
-                const field_name_ip = try mod.intern_pool.getOrPutString(gpa, field_name);
+                const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "name"),
+                ).?);
+                const type_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "type"),
+                ).?);
+                const alignment_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "alignment"),
+                ).?);
+
+                const field_name = try name_val.toIpString(Type.slice_const_u8, mod);
 
                 if (enum_field_names.len != 0) {
-                    enum_field_names[i] = field_name_ip;
+                    enum_field_names[i] = field_name;
                 }
 
                 if (explicit_enum_info) |tag_info| {
-                    const enum_index = tag_info.nameIndex(&mod.intern_pool, field_name_ip) orelse {
+                    const enum_index = tag_info.nameIndex(ip, field_name) orelse {
                         const msg = msg: {
-                            const msg = try sema.errMsg(block, src, "no field named '{s}' in enum '{}'", .{ field_name, union_obj.tag_ty.fmt(mod) });
+                            const msg = try sema.errMsg(block, src, "no field named '{s}' in enum '{}'", .{ ip.stringToSlice(field_name), union_obj.tag_ty.fmt(mod) });
                             errdefer msg.destroy(gpa);
                             try sema.addDeclaredHereNote(msg, union_obj.tag_ty);
                             break :msg msg;
@@ -19632,7 +19725,7 @@ fn zirReify(
                 const gop = union_obj.fields.getOrPutAssumeCapacity(field_name);
                 if (gop.found_existing) {
                     // TODO: better source location
-                    return sema.fail(block, src, "duplicate union field {s}", .{field_name});
+                    return sema.fail(block, src, "duplicate union field {s}", .{ip.stringToSlice(field_name)});
                 }
 
                 const field_ty = type_val.toType();
@@ -19688,7 +19781,7 @@ fn zirReify(
                         for (tag_info.names, 0..) |field_name, field_index| {
                             if (explicit_tags_seen[field_index]) continue;
                             try sema.addFieldErrNote(enum_ty, field_index, msg, "field '{s}' missing, declared here", .{
-                                mod.intern_pool.stringToSlice(field_name),
+                                ip.stringToSlice(field_name),
                             });
                         }
                         try sema.addDeclaredHereNote(msg, union_obj.tag_ty);
@@ -19700,19 +19793,30 @@ fn zirReify(
                 union_obj.tag_ty = try sema.generateUnionTagTypeSimple(block, enum_field_names, null);
             }
 
-            try new_decl.finalizeNewArena(&new_decl_arena);
             const decl_val = sema.analyzeDeclVal(block, src, new_decl_index);
             try mod.finalizeAnonDecl(new_decl_index);
             return decl_val;
         },
         .Fn => {
             const fields = ip.typeOf(union_val.val).toType().structFields(mod);
-            const calling_convention_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("calling_convention").?);
-            const alignment_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("alignment").?);
-            const is_generic_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_generic").?);
-            const is_var_args_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("is_var_args").?);
-            const return_type_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("return_type").?);
-            const params_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex("params").?);
+            const calling_convention_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "calling_convention"),
+            ).?);
+            const alignment_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "alignment"),
+            ).?);
+            const is_generic_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_generic"),
+            ).?);
+            const is_var_args_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "is_var_args"),
+            ).?);
+            const return_type_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "return_type"),
+            ).?);
+            const params_val = try union_val.val.toValue().fieldValue(mod, fields.getIndex(
+                try ip.getOrPutString(gpa, "params"),
+            ).?);
 
             const is_generic = is_generic_val.toBool();
             if (is_generic) {
@@ -19746,9 +19850,15 @@ fn zirReify(
             for (param_types, 0..) |*param_type, i| {
                 const elem_val = try params_val.elemValue(mod, i);
                 const elem_fields = ip.typeOf(elem_val.toIntern()).toType().structFields(mod);
-                const param_is_generic_val = try elem_val.fieldValue(mod, elem_fields.getIndex("is_generic").?);
-                const param_is_noalias_val = try elem_val.fieldValue(mod, elem_fields.getIndex("is_noalias").?);
-                const opt_param_type_val = try elem_val.fieldValue(mod, elem_fields.getIndex("type").?);
+                const param_is_generic_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "is_generic"),
+                ).?);
+                const param_is_noalias_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "is_noalias"),
+                ).?);
+                const opt_param_type_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+                    try ip.getOrPutString(gpa, "type"),
+                ).?);
 
                 if (param_is_generic_val.toBool()) {
                     return sema.fail(block, src, "Type.Fn.Param.is_generic must be false for @Type", .{});
@@ -19801,6 +19911,7 @@ fn reifyStruct(
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
     const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     var new_decl_arena = std.heap.ArenaAllocator.init(gpa);
     errdefer new_decl_arena.deinit();
@@ -19839,11 +19950,11 @@ fn reifyStruct(
     const struct_obj = mod.structPtr(struct_index);
     errdefer mod.destroyStruct(struct_index);
 
-    const struct_ty = try mod.intern_pool.get(gpa, .{ .struct_type = .{
+    const struct_ty = try ip.get(gpa, .{ .struct_type = .{
         .index = struct_index.toOptional(),
         .namespace = new_namespace_index.toOptional(),
     } });
-    errdefer mod.intern_pool.remove(struct_ty);
+    errdefer ip.remove(struct_ty);
 
     new_decl.val = struct_ty.toValue();
     new_namespace.ty = struct_ty.toType();
@@ -19854,12 +19965,22 @@ fn reifyStruct(
     var i: usize = 0;
     while (i < fields_len) : (i += 1) {
         const elem_val = try fields_val.elemValue(mod, i);
-        const elem_fields = mod.intern_pool.typeOf(elem_val.toIntern()).toType().structFields(mod);
-        const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex("name").?);
-        const type_val = try elem_val.fieldValue(mod, elem_fields.getIndex("type").?);
-        const default_value_val = try elem_val.fieldValue(mod, elem_fields.getIndex("default_value").?);
-        const is_comptime_val = try elem_val.fieldValue(mod, elem_fields.getIndex("is_comptime").?);
-        const alignment_val = try elem_val.fieldValue(mod, elem_fields.getIndex("alignment").?);
+        const elem_fields = ip.typeOf(elem_val.toIntern()).toType().structFields(mod);
+        const name_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+            try ip.getOrPutString(gpa, "name"),
+        ).?);
+        const type_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+            try ip.getOrPutString(gpa, "type"),
+        ).?);
+        const default_value_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+            try ip.getOrPutString(gpa, "default_value"),
+        ).?);
+        const is_comptime_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+            try ip.getOrPutString(gpa, "is_comptime"),
+        ).?);
+        const alignment_val = try elem_val.fieldValue(mod, elem_fields.getIndex(
+            try ip.getOrPutString(gpa, "alignment"),
+        ).?);
 
         if (!try sema.intFitsInType(alignment_val, Type.u32, null)) {
             return sema.fail(block, src, "alignment must fit in 'u32'", .{});
@@ -19874,19 +19995,15 @@ fn reifyStruct(
             return sema.fail(block, src, "extern struct fields cannot be marked comptime", .{});
         }
 
-        const field_name = try name_val.toAllocatedBytes(
-            Type.slice_const_u8,
-            new_decl_arena_allocator,
-            mod,
-        );
+        const field_name = try name_val.toIpString(Type.slice_const_u8, mod);
 
         if (is_tuple) {
-            const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch {
+            const field_index = std.fmt.parseUnsigned(u32, ip.stringToSlice(field_name), 10) catch {
                 return sema.fail(
                     block,
                     src,
                     "tuple cannot have non-numeric field '{s}'",
-                    .{field_name},
+                    .{ip.stringToSlice(field_name)},
                 );
             };
 
@@ -19902,16 +20019,16 @@ fn reifyStruct(
         const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
         if (gop.found_existing) {
             // TODO: better source location
-            return sema.fail(block, src, "duplicate struct field {s}", .{field_name});
+            return sema.fail(block, src, "duplicate struct field {s}", .{ip.stringToSlice(field_name)});
         }
 
         const field_ty = type_val.toType();
         const default_val = if (default_value_val.optionalValue(mod)) |opt_val|
-            try sema.pointerDeref(block, src, opt_val, try mod.singleConstPtrType(field_ty)) orelse
-                return sema.failWithNeededComptime(block, src, "struct field default value must be comptime-known")
+            (try sema.pointerDeref(block, src, opt_val, try mod.singleConstPtrType(field_ty)) orelse
+                return sema.failWithNeededComptime(block, src, "struct field default value must be comptime-known")).toIntern()
         else
-            Value.@"unreachable";
-        if (is_comptime_val.toBool() and default_val.toIntern() == .unreachable_value) {
+            .none;
+        if (is_comptime_val.toBool() and default_val == .none) {
             return sema.fail(block, src, "comptime field without default initialization value", .{});
         }
 
@@ -20000,7 +20117,6 @@ fn reifyStruct(
         struct_obj.status = .have_layout;
     }
 
-    try new_decl.finalizeNewArena(&new_decl_arena);
     const decl_val = sema.analyzeDeclVal(block, src, new_decl_index);
     try mod.finalizeAnonDecl(new_decl_index);
     return decl_val;
@@ -20871,7 +20987,7 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
 
     const ty = try sema.resolveType(block, lhs_src, extra.lhs);
-    const field_name = try sema.resolveConstString(block, rhs_src, extra.rhs, "name of field must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, rhs_src, extra.rhs, "name of field must be comptime-known");
 
     const mod = sema.mod;
     try sema.resolveTypeLayout(ty);
@@ -20889,7 +21005,7 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6
     }
 
     const field_index = if (ty.isTuple(mod)) blk: {
-        if (mem.eql(u8, field_name, "len")) {
+        if (mod.intern_pool.stringEqlSlice(field_name, "len")) {
             return sema.fail(block, src, "no offset available for 'len' field of tuple", .{});
         }
         break :blk try sema.tupleFieldIndex(block, ty, field_name, rhs_src);
@@ -21351,6 +21467,8 @@ fn resolveExportOptions(
     zir_ref: Zir.Inst.Ref,
 ) CompileError!std.builtin.ExportOptions {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const export_options_ty = try sema.getBuiltinType("ExportOptions");
     const air_ref = try sema.resolveInst(zir_ref);
     const options = try sema.coerce(block, export_options_ty, air_ref, src);
@@ -21360,16 +21478,16 @@ fn resolveExportOptions(
     const section_src = sema.maybeOptionsSrc(block, src, "section");
     const visibility_src = sema.maybeOptionsSrc(block, src, "visibility");
 
-    const name_operand = try sema.fieldVal(block, src, options, "name", name_src);
+    const name_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "name"), name_src);
     const name_val = try sema.resolveConstValue(block, name_src, name_operand, "name of exported value must be comptime-known");
     const name_ty = Type.slice_const_u8;
     const name = try name_val.toAllocatedBytes(name_ty, sema.arena, mod);
 
-    const linkage_operand = try sema.fieldVal(block, src, options, "linkage", linkage_src);
+    const linkage_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "linkage"), linkage_src);
     const linkage_val = try sema.resolveConstValue(block, linkage_src, linkage_operand, "linkage of exported value must be comptime-known");
     const linkage = mod.toEnum(std.builtin.GlobalLinkage, linkage_val);
 
-    const section_operand = try sema.fieldVal(block, src, options, "section", section_src);
+    const section_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "section"), section_src);
     const section_opt_val = try sema.resolveConstValue(block, section_src, section_operand, "linksection of exported value must be comptime-known");
     const section_ty = Type.slice_const_u8;
     const section = if (section_opt_val.optionalValue(mod)) |section_val|
@@ -21377,7 +21495,7 @@ fn resolveExportOptions(
     else
         null;
 
-    const visibility_operand = try sema.fieldVal(block, src, options, "visibility", visibility_src);
+    const visibility_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "visibility"), visibility_src);
     const visibility_val = try sema.resolveConstValue(block, visibility_src, visibility_operand, "visibility of exported value must be comptime-known");
     const visibility = mod.toEnum(std.builtin.SymbolVisibility, visibility_val);
 
@@ -22217,10 +22335,11 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
     const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node };
 
     const parent_ty = try sema.resolveType(block, ty_src, extra.parent_type);
-    const field_name = try sema.resolveConstString(block, name_src, extra.field_name, "field name must be comptime-known");
+    const field_name = try sema.resolveConstStringIntern(block, name_src, extra.field_name, "field name must be comptime-known");
     const field_ptr = try sema.resolveInst(extra.field_ptr);
     const field_ptr_ty = sema.typeOf(field_ptr);
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
 
     if (parent_ty.zigTypeTag(mod) != .Struct and parent_ty.zigTypeTag(mod) != .Union) {
         return sema.fail(block, ty_src, "expected struct or union type, found '{}'", .{parent_ty.fmt(sema.mod)});
@@ -22230,7 +22349,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
     const field_index = switch (parent_ty.zigTypeTag(mod)) {
         .Struct => blk: {
             if (parent_ty.isTuple(mod)) {
-                if (mem.eql(u8, field_name, "len")) {
+                if (ip.stringEqlSlice(field_name, "len")) {
                     return sema.fail(block, src, "cannot get @fieldParentPtr of 'len' field of tuple", .{});
                 }
                 break :blk try sema.tupleFieldIndex(block, parent_ty, field_name, name_src);
@@ -22276,7 +22395,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
     const result_ptr = try Type.ptr(sema.arena, sema.mod, ptr_ty_data);
 
     if (try sema.resolveDefinedValue(block, src, casted_field_ptr)) |field_ptr_val| {
-        const field = switch (mod.intern_pool.indexToKey(field_ptr_val.toIntern())) {
+        const field = switch (ip.indexToKey(field_ptr_val.toIntern())) {
             .ptr => |ptr| switch (ptr.addr) {
                 .field => |field| field,
                 else => null,
@@ -22291,7 +22410,7 @@ fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr
                     src,
                     "field '{s}' has index '{d}' but pointer value is index '{d}' of struct '{}'",
                     .{
-                        field_name,
+                        ip.stringToSlice(field_name),
                         field_index,
                         field.index,
                         parent_ty.fmt(sema.mod),
@@ -22807,6 +22926,8 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
 
 fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
     const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
     const src = inst_data.src();
@@ -22824,7 +22945,7 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void
     const dest_elem_ty = dest_ptr_ty.elemType2(mod);
 
     const runtime_src = if (try sema.resolveDefinedValue(block, dest_src, dest_ptr)) |ptr_val| rs: {
-        const len_air_ref = try sema.fieldVal(block, src, dest_ptr, "len", dest_src);
+        const len_air_ref = try sema.fieldVal(block, src, dest_ptr, try ip.getOrPutString(gpa, "len"), dest_src);
         const len_val = (try sema.resolveDefinedValue(block, dest_src, len_air_ref)) orelse
             break :rs dest_src;
         const len_u64 = (try len_val.getUnsignedIntAdvanced(mod, sema)).?;
@@ -23068,11 +23189,11 @@ fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A
         if (val.isGenericPoison()) {
             break :blk FuncLinkSection{ .generic = {} };
         }
-        break :blk FuncLinkSection{ .explicit = try val.toAllocatedBytes(ty, sema.arena, sema.mod) };
+        break :blk FuncLinkSection{ .explicit = try val.toIpString(ty, mod) };
     } else if (extra.data.bits.has_section_ref) blk: {
         const section_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]);
         extra_index += 1;
-        const section_name = sema.resolveConstString(block, section_src, section_ref, "linksection must be comptime-known") catch |err| switch (err) {
+        const section_name = sema.resolveConstStringIntern(block, section_src, section_ref, "linksection must be comptime-known") catch |err| switch (err) {
             error.GenericPoison => {
                 break :blk FuncLinkSection{ .generic = {} };
             },
@@ -23272,6 +23393,8 @@ fn resolvePrefetchOptions(
     zir_ref: Zir.Inst.Ref,
 ) CompileError!std.builtin.PrefetchOptions {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const options_ty = try sema.getBuiltinType("PrefetchOptions");
     const options = try sema.coerce(block, options_ty, try sema.resolveInst(zir_ref), src);
 
@@ -23279,13 +23402,13 @@ fn resolvePrefetchOptions(
     const locality_src = sema.maybeOptionsSrc(block, src, "locality");
     const cache_src = sema.maybeOptionsSrc(block, src, "cache");
 
-    const rw = try sema.fieldVal(block, src, options, "rw", rw_src);
+    const rw = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "rw"), rw_src);
     const rw_val = try sema.resolveConstValue(block, rw_src, rw, "prefetch read/write must be comptime-known");
 
-    const locality = try sema.fieldVal(block, src, options, "locality", locality_src);
+    const locality = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "locality"), locality_src);
     const locality_val = try sema.resolveConstValue(block, locality_src, locality, "prefetch locality must be comptime-known");
 
-    const cache = try sema.fieldVal(block, src, options, "cache", cache_src);
+    const cache = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "cache"), cache_src);
     const cache_val = try sema.resolveConstValue(block, cache_src, cache, "prefetch cache must be comptime-known");
 
     return std.builtin.PrefetchOptions{
@@ -23336,6 +23459,8 @@ fn resolveExternOptions(
     zir_ref: Zir.Inst.Ref,
 ) CompileError!std.builtin.ExternOptions {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
     const options_inst = try sema.resolveInst(zir_ref);
     const extern_options_ty = try sema.getBuiltinType("ExternOptions");
     const options = try sema.coerce(block, extern_options_ty, options_inst, src);
@@ -23345,18 +23470,18 @@ fn resolveExternOptions(
     const linkage_src = sema.maybeOptionsSrc(block, src, "linkage");
     const thread_local_src = sema.maybeOptionsSrc(block, src, "thread_local");
 
-    const name_ref = try sema.fieldVal(block, src, options, "name", name_src);
+    const name_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "name"), name_src);
     const name_val = try sema.resolveConstValue(block, name_src, name_ref, "name of the extern symbol must be comptime-known");
     const name = try name_val.toAllocatedBytes(Type.slice_const_u8, sema.arena, mod);
 
-    const library_name_inst = try sema.fieldVal(block, src, options, "library_name", library_src);
+    const library_name_inst = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "library_name"), library_src);
     const library_name_val = try sema.resolveConstValue(block, library_src, library_name_inst, "library in which extern symbol is must be comptime-known");
 
-    const linkage_ref = try sema.fieldVal(block, src, options, "linkage", linkage_src);
+    const linkage_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "linkage"), linkage_src);
     const linkage_val = try sema.resolveConstValue(block, linkage_src, linkage_ref, "linkage of the extern symbol must be comptime-known");
     const linkage = mod.toEnum(std.builtin.GlobalLinkage, linkage_val);
 
-    const is_thread_local = try sema.fieldVal(block, src, options, "is_thread_local", thread_local_src);
+    const is_thread_local = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, "is_thread_local"), thread_local_src);
     const is_thread_local_val = try sema.resolveConstValue(block, thread_local_src, is_thread_local, "threadlocality of the extern symbol must be comptime-known");
 
     const library_name = if (library_name_val.optionalValue(mod)) |payload| blk: {
@@ -23425,7 +23550,7 @@ fn zirBuiltinExtern(
     const new_decl_index = try mod.allocateNewDecl(sema.owner_decl.src_namespace, sema.owner_decl.src_node, null);
     errdefer mod.destroyDecl(new_decl_index);
     const new_decl = mod.declPtr(new_decl_index);
-    new_decl.name = try sema.gpa.dupeZ(u8, options.name);
+    new_decl.name = try mod.intern_pool.getOrPutString(sema.gpa, options.name);
 
     {
         const new_var = try mod.intern(.{ .variable = .{
@@ -23444,7 +23569,7 @@ fn zirBuiltinExtern(
         new_decl.ty = ty;
         new_decl.val = new_var.toValue();
         new_decl.@"align" = 0;
-        new_decl.@"linksection" = null;
+        new_decl.@"linksection" = .none;
         new_decl.has_tv = true;
         new_decl.analysis = .complete;
         new_decl.generation = mod.generation;
@@ -24265,12 +24390,13 @@ fn safetyPanic(
     panic_id: PanicId,
 ) CompileError!void {
     const mod = sema.mod;
+    const gpa = sema.gpa;
     const panic_messages_ty = try sema.getBuiltinType("panic_messages");
     const msg_decl_index = (try sema.namespaceLookup(
         block,
         sema.src,
         panic_messages_ty.getNamespaceIndex(mod).unwrap().?,
-        @tagName(panic_id),
+        try mod.intern_pool.getOrPutString(gpa, @tagName(panic_id)),
     )).?;
 
     const msg_inst = try sema.analyzeDeclVal(block, sema.src, msg_decl_index);
@@ -24302,14 +24428,13 @@ fn fieldVal(
     block: *Block,
     src: LazySrcLoc,
     object: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
 ) CompileError!Air.Inst.Ref {
     // When editing this function, note that there is corresponding logic to be edited
     // in `fieldPtr`. This function takes a value and returns a value.
 
     const mod = sema.mod;
-    const gpa = sema.gpa;
     const ip = &mod.intern_pool;
     const object_src = src; // TODO better source location
     const object_ty = sema.typeOf(object);
@@ -24326,12 +24451,12 @@ fn fieldVal(
 
     switch (inner_ty.zigTypeTag(mod)) {
         .Array => {
-            if (mem.eql(u8, field_name, "len")) {
+            if (ip.stringEqlSlice(field_name, "len")) {
                 return sema.addConstant(
                     Type.usize,
                     try mod.intValue(Type.usize, inner_ty.arrayLen(mod)),
                 );
-            } else if (mem.eql(u8, field_name, "ptr") and is_pointer_to) {
+            } else if (ip.stringEqlSlice(field_name, "ptr") and is_pointer_to) {
                 const ptr_info = object_ty.ptrInfo(mod);
                 const result_ty = try Type.ptr(sema.arena, mod, .{
                     .pointee_type = ptr_info.pointee_type.childType(mod),
@@ -24352,20 +24477,20 @@ fn fieldVal(
                     block,
                     field_name_src,
                     "no member named '{s}' in '{}'",
-                    .{ field_name, object_ty.fmt(mod) },
+                    .{ ip.stringToSlice(field_name), object_ty.fmt(mod) },
                 );
             }
         },
         .Pointer => {
             const ptr_info = inner_ty.ptrInfo(mod);
             if (ptr_info.size == .Slice) {
-                if (mem.eql(u8, field_name, "ptr")) {
+                if (ip.stringEqlSlice(field_name, "ptr")) {
                     const slice = if (is_pointer_to)
                         try sema.analyzeLoad(block, src, object, object_src)
                     else
                         object;
                     return sema.analyzeSlicePtr(block, object_src, slice, inner_ty);
-                } else if (mem.eql(u8, field_name, "len")) {
+                } else if (ip.stringEqlSlice(field_name, "len")) {
                     const slice = if (is_pointer_to)
                         try sema.analyzeLoad(block, src, object, object_src)
                     else
@@ -24376,7 +24501,7 @@ fn fieldVal(
                         block,
                         field_name_src,
                         "no member named '{s}' in '{}'",
-                        .{ field_name, object_ty.fmt(mod) },
+                        .{ ip.stringToSlice(field_name), object_ty.fmt(mod) },
                     );
                 }
             }
@@ -24392,13 +24517,12 @@ fn fieldVal(
 
             switch (try child_type.zigTypeTagOrPoison(mod)) {
                 .ErrorSet => {
-                    const name = try ip.getOrPutString(gpa, field_name);
                     switch (ip.indexToKey(child_type.toIntern())) {
                         .error_set_type => |error_set_type| blk: {
-                            if (error_set_type.nameIndex(ip, name) != null) break :blk;
+                            if (error_set_type.nameIndex(ip, field_name) != null) break :blk;
                             const msg = msg: {
                                 const msg = try sema.errMsg(block, src, "no error named '{s}' in '{}'", .{
-                                    field_name, child_type.fmt(mod),
+                                    ip.stringToSlice(field_name), child_type.fmt(mod),
                                 });
                                 errdefer msg.destroy(sema.gpa);
                                 try sema.addDeclaredHereNote(msg, child_type);
@@ -24419,10 +24543,10 @@ fn fieldVal(
                     const error_set_type = if (!child_type.isAnyError(mod))
                         child_type
                     else
-                        try mod.singleErrorSetTypeNts(name);
+                        try mod.singleErrorSetTypeNts(field_name);
                     return sema.addConstant(error_set_type, (try mod.intern(.{ .err = .{
                         .ty = error_set_type.toIntern(),
-                        .name = name,
+                        .name = field_name,
                     } })).toValue());
                 },
                 .Union => {
@@ -24499,7 +24623,7 @@ fn fieldPtr(
     block: *Block,
     src: LazySrcLoc,
     object_ptr: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
     initializing: bool,
 ) CompileError!Air.Inst.Ref {
@@ -24507,7 +24631,6 @@ fn fieldPtr(
     // in `fieldVal`. This function takes a pointer and returns a pointer.
 
     const mod = sema.mod;
-    const gpa = sema.gpa;
     const ip = &mod.intern_pool;
     const object_ptr_src = src; // TODO better source location
     const object_ptr_ty = sema.typeOf(object_ptr);
@@ -24528,7 +24651,7 @@ fn fieldPtr(
 
     switch (inner_ty.zigTypeTag(mod)) {
         .Array => {
-            if (mem.eql(u8, field_name, "len")) {
+            if (ip.stringEqlSlice(field_name, "len")) {
                 var anon_decl = try block.startAnonDecl();
                 defer anon_decl.deinit();
                 return sema.analyzeDeclRef(try anon_decl.finish(
@@ -24541,7 +24664,7 @@ fn fieldPtr(
                     block,
                     field_name_src,
                     "no member named '{s}' in '{}'",
-                    .{ field_name, object_ty.fmt(mod) },
+                    .{ ip.stringToSlice(field_name), object_ty.fmt(mod) },
                 );
             }
         },
@@ -24553,7 +24676,7 @@ fn fieldPtr(
 
             const attr_ptr_ty = if (is_pointer_to) object_ty else object_ptr_ty;
 
-            if (mem.eql(u8, field_name, "ptr")) {
+            if (ip.stringEqlSlice(field_name, "ptr")) {
                 const slice_ptr_ty = inner_ty.slicePtrFieldType(mod);
 
                 const result_ty = try Type.ptr(sema.arena, mod, .{
@@ -24575,7 +24698,7 @@ fn fieldPtr(
                 try sema.requireRuntimeBlock(block, src, null);
 
                 return block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr);
-            } else if (mem.eql(u8, field_name, "len")) {
+            } else if (ip.stringEqlSlice(field_name, "len")) {
                 const result_ty = try Type.ptr(sema.arena, mod, .{
                     .pointee_type = Type.usize,
                     .mutable = attr_ptr_ty.ptrIsMutable(mod),
@@ -24600,7 +24723,7 @@ fn fieldPtr(
                     block,
                     field_name_src,
                     "no member named '{s}' in '{}'",
-                    .{ field_name, object_ty.fmt(mod) },
+                    .{ ip.stringToSlice(field_name), object_ty.fmt(mod) },
                 );
             }
         },
@@ -24617,14 +24740,13 @@ fn fieldPtr(
 
             switch (child_type.zigTypeTag(mod)) {
                 .ErrorSet => {
-                    const name = try ip.getOrPutString(gpa, field_name);
                     switch (ip.indexToKey(child_type.toIntern())) {
                         .error_set_type => |error_set_type| blk: {
-                            if (error_set_type.nameIndex(ip, name) != null) {
+                            if (error_set_type.nameIndex(ip, field_name) != null) {
                                 break :blk;
                             }
                             return sema.fail(block, src, "no error named '{s}' in '{}'", .{
-                                field_name, child_type.fmt(mod),
+                                ip.stringToSlice(field_name), child_type.fmt(mod),
                             });
                         },
                         .inferred_error_set_type => {
@@ -24642,12 +24764,12 @@ fn fieldPtr(
                     const error_set_type = if (!child_type.isAnyError(mod))
                         child_type
                     else
-                        try mod.singleErrorSetTypeNts(name);
+                        try mod.singleErrorSetTypeNts(field_name);
                     return sema.analyzeDeclRef(try anon_decl.finish(
                         error_set_type,
                         (try mod.intern(.{ .err = .{
                             .ty = error_set_type.toIntern(),
-                            .name = name,
+                            .name = field_name,
                         } })).toValue(),
                         0, // default alignment
                     ));
@@ -24736,13 +24858,14 @@ fn fieldCallBind(
     block: *Block,
     src: LazySrcLoc,
     raw_ptr: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
 ) CompileError!ResolvedFieldCallee {
     // When editing this function, note that there is corresponding logic to be edited
     // in `fieldVal`. This function takes a pointer and returns a pointer.
 
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const raw_ptr_src = src; // TODO better source location
     const raw_ptr_ty = sema.typeOf(raw_ptr);
     const inner_ty = if (raw_ptr_ty.zigTypeTag(mod) == .Pointer and (raw_ptr_ty.ptrSize(mod) == .One or raw_ptr_ty.ptrSize(mod) == .C))
@@ -24771,18 +24894,18 @@ fn fieldCallBind(
 
                     return sema.finishFieldCallBind(block, src, ptr_ty, field.ty, field_index, object_ptr);
                 } else if (struct_ty.isTuple(mod)) {
-                    if (mem.eql(u8, field_name, "len")) {
+                    if (ip.stringEqlSlice(field_name, "len")) {
                         return .{ .direct = try sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount(mod)) };
                     }
-                    if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| {
+                    if (std.fmt.parseUnsigned(u32, ip.stringToSlice(field_name), 10)) |field_index| {
                         if (field_index >= struct_ty.structFieldCount(mod)) break :find_field;
                         return sema.finishFieldCallBind(block, src, ptr_ty, struct_ty.structFieldType(field_index, mod), field_index, object_ptr);
                     } else |_| {}
                 } else {
                     const max = struct_ty.structFieldCount(mod);
-                    var i: u32 = 0;
-                    while (i < max) : (i += 1) {
-                        if (mem.eql(u8, struct_ty.structFieldName(i, mod), field_name)) {
+                    for (0..max) |i_usize| {
+                        const i = @intCast(u32, i_usize);
+                        if (field_name == struct_ty.structFieldName(i, mod)) {
                             return sema.finishFieldCallBind(block, src, ptr_ty, struct_ty.structFieldType(i, mod), i, object_ptr);
                         }
                     }
@@ -24876,12 +24999,12 @@ fn fieldCallBind(
     };
 
     const msg = msg: {
-        const msg = try sema.errMsg(block, src, "no field or member function named '{s}' in '{}'", .{ field_name, concrete_ty.fmt(mod) });
+        const msg = try sema.errMsg(block, src, "no field or member function named '{s}' in '{}'", .{ ip.stringToSlice(field_name), concrete_ty.fmt(mod) });
         errdefer msg.destroy(sema.gpa);
         try sema.addDeclaredHereNote(msg, concrete_ty);
         if (found_decl) |decl_idx| {
             const decl = mod.declPtr(decl_idx);
-            try mod.errNoteNonLazy(decl.srcLoc(mod), msg, "'{s}' is not a member function", .{field_name});
+            try mod.errNoteNonLazy(decl.srcLoc(mod), msg, "'{s}' is not a member function", .{ip.stringToSlice(field_name)});
         }
         break :msg msg;
     };
@@ -24933,7 +25056,7 @@ fn namespaceLookup(
     block: *Block,
     src: LazySrcLoc,
     namespace: Namespace.Index,
-    decl_name: []const u8,
+    decl_name: InternPool.NullTerminatedString,
 ) CompileError!?Decl.Index {
     const mod = sema.mod;
     const gpa = sema.gpa;
@@ -24942,7 +25065,7 @@ fn namespaceLookup(
         if (!decl.is_pub and decl.getFileScope(mod) != block.getFileScope(mod)) {
             const msg = msg: {
                 const msg = try sema.errMsg(block, src, "'{s}' is not marked 'pub'", .{
-                    decl_name,
+                    mod.intern_pool.stringToSlice(decl_name),
                 });
                 errdefer msg.destroy(gpa);
                 try mod.errNoteNonLazy(decl.srcLoc(mod), msg, "declared here", .{});
@@ -24960,7 +25083,7 @@ fn namespaceLookupRef(
     block: *Block,
     src: LazySrcLoc,
     namespace: Namespace.Index,
-    decl_name: []const u8,
+    decl_name: InternPool.NullTerminatedString,
 ) CompileError!?Air.Inst.Ref {
     const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null;
     try sema.addReferencedBy(block, src, decl);
@@ -24972,7 +25095,7 @@ fn namespaceLookupVal(
     block: *Block,
     src: LazySrcLoc,
     namespace: Namespace.Index,
-    decl_name: []const u8,
+    decl_name: InternPool.NullTerminatedString,
 ) CompileError!?Air.Inst.Ref {
     const decl = (try sema.namespaceLookup(block, src, namespace, decl_name)) orelse return null;
     return try sema.analyzeDeclVal(block, src, decl);
@@ -24983,7 +25106,7 @@ fn structFieldPtr(
     block: *Block,
     src: LazySrcLoc,
     struct_ptr: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
     unresolved_struct_ty: Type,
     initializing: bool,
@@ -24995,7 +25118,7 @@ fn structFieldPtr(
     try sema.resolveStructLayout(struct_ty);
 
     if (struct_ty.isTuple(mod)) {
-        if (mem.eql(u8, field_name, "len")) {
+        if (mod.intern_pool.stringEqlSlice(field_name, "len")) {
             const len_inst = try sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount(mod));
             return sema.analyzeRef(block, src, len_inst);
         }
@@ -25101,7 +25224,7 @@ fn structFieldPtrByIndex(
     if (field.is_comptime) {
         const val = try mod.intern(.{ .ptr = .{
             .ty = ptr_field_ty.toIntern(),
-            .addr = .{ .comptime_field = try field.default_val.intern(field.ty, mod) },
+            .addr = .{ .comptime_field = field.default_val },
         } });
         return sema.addConstant(ptr_field_ty, val.toValue());
     }
@@ -25126,7 +25249,7 @@ fn structFieldVal(
     block: *Block,
     src: LazySrcLoc,
     struct_byval: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
     unresolved_struct_ty: Type,
 ) CompileError!Air.Inst.Ref {
@@ -25145,7 +25268,7 @@ fn structFieldVal(
             const field = struct_obj.fields.values()[field_index];
 
             if (field.is_comptime) {
-                return sema.addConstant(field.ty, field.default_val);
+                return sema.addConstant(field.ty, field.default_val.toValue());
             }
 
             if (try sema.resolveMaybeUndefVal(struct_byval)) |struct_val| {
@@ -25176,12 +25299,12 @@ fn tupleFieldVal(
     block: *Block,
     src: LazySrcLoc,
     tuple_byval: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
     tuple_ty: Type,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
-    if (mem.eql(u8, field_name, "len")) {
+    if (mod.intern_pool.stringEqlSlice(field_name, "len")) {
         return sema.addIntUnsigned(Type.usize, tuple_ty.structFieldCount(mod));
     }
     const field_index = try sema.tupleFieldIndex(block, tuple_ty, field_name, field_name_src);
@@ -25193,11 +25316,12 @@ fn tupleFieldIndex(
     sema: *Sema,
     block: *Block,
     tuple_ty: Type,
-    field_name: []const u8,
+    field_name_ip: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
 ) CompileError!u32 {
     const mod = sema.mod;
-    assert(!mem.eql(u8, field_name, "len"));
+    const field_name = mod.intern_pool.stringToSlice(field_name_ip);
+    assert(!std.mem.eql(u8, field_name, "len"));
     if (std.fmt.parseUnsigned(u32, field_name, 10)) |field_index| {
         if (field_index < tuple_ty.structFieldCount(mod)) return field_index;
         return sema.fail(block, field_name_src, "index '{s}' out of bounds of tuple '{}'", .{
@@ -25253,13 +25377,14 @@ fn unionFieldPtr(
     block: *Block,
     src: LazySrcLoc,
     union_ptr: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
     unresolved_union_ty: Type,
     initializing: bool,
 ) CompileError!Air.Inst.Ref {
     const arena = sema.arena;
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
 
     assert(unresolved_union_ty.zigTypeTag(mod) == .Union);
 
@@ -25281,7 +25406,9 @@ fn unionFieldPtr(
             const msg = try sema.errMsg(block, src, "cannot initialize 'noreturn' field of union", .{});
             errdefer msg.destroy(sema.gpa);
 
-            try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' declared here", .{field_name});
+            try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' declared here", .{
+                ip.stringToSlice(field_name),
+            });
             try sema.addDeclaredHereNote(msg, union_ty);
             break :msg msg;
         };
@@ -25296,14 +25423,17 @@ fn unionFieldPtr(
                 if (union_val.isUndef(mod)) {
                     return sema.failWithUseOfUndef(block, src);
                 }
-                const un = mod.intern_pool.indexToKey(union_val.toIntern()).un;
+                const un = ip.indexToKey(union_val.toIntern()).un;
                 const field_tag = try mod.enumValueFieldIndex(union_obj.tag_ty, enum_field_index);
                 const tag_matches = un.tag == field_tag.toIntern();
                 if (!tag_matches) {
                     const msg = msg: {
                         const active_index = union_obj.tag_ty.enumTagFieldIndex(un.tag.toValue(), mod).?;
                         const active_field_name = union_obj.tag_ty.enumFieldName(active_index, mod);
-                        const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{ field_name, active_field_name });
+                        const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{
+                            ip.stringToSlice(field_name),
+                            ip.stringToSlice(active_field_name),
+                        });
                         errdefer msg.destroy(sema.gpa);
                         try sema.addDeclaredHereNote(msg, union_ty);
                         break :msg msg;
@@ -25345,11 +25475,12 @@ fn unionFieldVal(
     block: *Block,
     src: LazySrcLoc,
     union_byval: Air.Inst.Ref,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_name_src: LazySrcLoc,
     unresolved_union_ty: Type,
 ) CompileError!Air.Inst.Ref {
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
     assert(unresolved_union_ty.zigTypeTag(mod) == .Union);
 
     const union_ty = try sema.resolveTypeFields(unresolved_union_ty);
@@ -25361,7 +25492,7 @@ fn unionFieldVal(
     if (try sema.resolveMaybeUndefVal(union_byval)) |union_val| {
         if (union_val.isUndef(mod)) return sema.addConstUndef(field.ty);
 
-        const un = mod.intern_pool.indexToKey(union_val.toIntern()).un;
+        const un = ip.indexToKey(union_val.toIntern()).un;
         const field_tag = try mod.enumValueFieldIndex(union_obj.tag_ty, enum_field_index);
         const tag_matches = un.tag == field_tag.toIntern();
         switch (union_obj.layout) {
@@ -25372,7 +25503,9 @@ fn unionFieldVal(
                     const msg = msg: {
                         const active_index = union_obj.tag_ty.enumTagFieldIndex(un.tag.toValue(), mod).?;
                         const active_field_name = union_obj.tag_ty.enumFieldName(active_index, mod);
-                        const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{ field_name, active_field_name });
+                        const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{
+                            ip.stringToSlice(field_name), ip.stringToSlice(active_field_name),
+                        });
                         errdefer msg.destroy(sema.gpa);
                         try sema.addDeclaredHereNote(msg, union_ty);
                         break :msg msg;
@@ -26470,14 +26603,13 @@ fn coerceExtra(
                 // enum literal to enum
                 const val = try sema.resolveConstValue(block, .unneeded, inst, "");
                 const string = mod.intern_pool.indexToKey(val.toIntern()).enum_literal;
-                const bytes = mod.intern_pool.stringToSlice(string);
-                const field_index = dest_ty.enumFieldIndex(bytes, mod) orelse {
+                const field_index = dest_ty.enumFieldIndex(string, mod) orelse {
                     const msg = msg: {
                         const msg = try sema.errMsg(
                             block,
                             inst_src,
                             "no field named '{s}' in enum '{}'",
-                            .{ bytes, dest_ty.fmt(mod) },
+                            .{ mod.intern_pool.stringToSlice(string), dest_ty.fmt(mod) },
                         );
                         errdefer msg.destroy(sema.gpa);
                         try sema.addDeclaredHereNote(msg, dest_ty);
@@ -27876,10 +28008,7 @@ fn storePtrVal(
                 error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
             };
 
-            const arena = mut_kit.beginArena(mod);
-            defer mut_kit.finishArena(mod);
-
-            reinterpret.val_ptr.* = (try (try Value.readFromMemory(mut_kit.ty, mod, buffer, arena)).intern(mut_kit.ty, mod)).toValue();
+            reinterpret.val_ptr.* = (try (try Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena)).intern(mut_kit.ty, mod)).toValue();
         },
         .bad_decl_ty, .bad_ptr_ty => {
             // TODO show the decl declaration site in a note and explain whether the decl
@@ -27913,18 +28042,6 @@ const ComptimePtrMutationKit = struct {
         bad_ptr_ty,
     },
     ty: Type,
-    decl_arena: std.heap.ArenaAllocator = undefined,
-
-    fn beginArena(self: *ComptimePtrMutationKit, mod: *Module) Allocator {
-        const decl = mod.declPtr(self.mut_decl.decl);
-        return decl.value_arena.?.acquire(mod.gpa, &self.decl_arena);
-    }
-
-    fn finishArena(self: *ComptimePtrMutationKit, mod: *Module) void {
-        const decl = mod.declPtr(self.mut_decl.decl);
-        decl.value_arena.?.release(&self.decl_arena);
-        self.decl_arena = undefined;
-    }
 };
 
 fn beginComptimePtrMutation(
@@ -27966,10 +28083,8 @@ fn beginComptimePtrMutation(
                         // An error union has been initialized to undefined at comptime and now we
                         // are for the first time setting the payload. We must change the
                         // representation of the error union from `undef` to `opt_payload`.
-                        const arena = parent.beginArena(sema.mod);
-                        defer parent.finishArena(sema.mod);
 
-                        const payload = try arena.create(Value.Payload.SubValue);
+                        const payload = try sema.arena.create(Value.Payload.SubValue);
                         payload.* = .{
                             .base = .{ .tag = .eu_payload },
                             .data = (try mod.intern(.{ .undef = payload_ty.toIntern() })).toValue(),
@@ -28019,10 +28134,8 @@ fn beginComptimePtrMutation(
                             // An optional has been initialized to undefined at comptime and now we
                             // are for the first time setting the payload. We must change the
                             // representation of the optional from `undef` to `opt_payload`.
-                            const arena = parent.beginArena(sema.mod);
-                            defer parent.finishArena(sema.mod);
 
-                            const payload = try arena.create(Value.Payload.SubValue);
+                            const payload = try sema.arena.create(Value.Payload.SubValue);
                             payload.* = .{
                                 .base = .{ .tag = .opt_payload },
                                 .data = payload_val.toValue(),
@@ -28088,8 +28201,7 @@ fn beginComptimePtrMutation(
                                     // If we wanted to avoid this, there would need to be special detection
                                     // elsewhere to identify when writing a value to an array element that is stored
                                     // using the `bytes` tag, and handle it without making a call to this function.
-                                    const arena = parent.beginArena(sema.mod);
-                                    defer parent.finishArena(sema.mod);
+                                    const arena = sema.arena;
 
                                     const bytes = val_ptr.castTag(.bytes).?.data;
                                     const dest_len = parent.ty.arrayLenIncludingSentinel(mod);
@@ -28121,8 +28233,7 @@ fn beginComptimePtrMutation(
                                     // need to be special detection elsewhere to identify when writing a value to an
                                     // array element that is stored using the `repeated` tag, and handle it
                                     // without making a call to this function.
-                                    const arena = parent.beginArena(sema.mod);
-                                    defer parent.finishArena(sema.mod);
+                                    const arena = sema.arena;
 
                                     const repeated_val = try val_ptr.castTag(.repeated).?.data.copy(arena);
                                     const array_len_including_sentinel =
@@ -28163,8 +28274,7 @@ fn beginComptimePtrMutation(
                                     // An array has been initialized to undefined at comptime and now we
                                     // are for the first time setting an element. We must change the representation
                                     // of the array from `undef` to `array`.
-                                    const arena = parent.beginArena(sema.mod);
-                                    defer parent.finishArena(sema.mod);
+                                    const arena = sema.arena;
 
                                     const array_len_including_sentinel =
                                         try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel(mod));
@@ -28261,8 +28371,7 @@ fn beginComptimePtrMutation(
                             parent.mut_decl,
                         ),
                         .repeated => {
-                            const arena = parent.beginArena(sema.mod);
-                            defer parent.finishArena(sema.mod);
+                            const arena = sema.arena;
 
                             const elems = try arena.alloc(Value, parent.ty.structFieldCount(mod));
                             @memset(elems, val_ptr.castTag(.repeated).?.data);
@@ -28325,8 +28434,7 @@ fn beginComptimePtrMutation(
                             // A struct or union has been initialized to undefined at comptime and now we
                             // are for the first time setting a field. We must change the representation
                             // of the struct/union from `undef` to `struct`/`union`.
-                            const arena = parent.beginArena(sema.mod);
-                            defer parent.finishArena(sema.mod);
+                            const arena = sema.arena;
 
                             switch (parent.ty.zigTypeTag(mod)) {
                                 .Struct => {
@@ -28436,11 +28544,7 @@ fn beginComptimePtrMutationInner(
     const target = mod.getTarget();
     const coerce_ok = (try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_ty, true, target, src, src)) == .ok;
 
-    const decl = mod.declPtr(mut_decl.decl);
-    var decl_arena: std.heap.ArenaAllocator = undefined;
-    const allocator = decl.value_arena.?.acquire(sema.gpa, &decl_arena);
-    defer decl.value_arena.?.release(&decl_arena);
-    decl_val.* = try decl_val.unintern(allocator, mod);
+    decl_val.* = try decl_val.unintern(sema.arena, mod);
 
     if (coerce_ok) {
         return ComptimePtrMutationKit{
@@ -28928,6 +29032,7 @@ fn coerceEnumToUnion(
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const inst_ty = sema.typeOf(inst);
 
     const tag_ty = union_ty.unionTagType(mod) orelse {
@@ -28966,7 +29071,9 @@ fn coerceEnumToUnion(
                 errdefer msg.destroy(sema.gpa);
 
                 const field_name = union_obj.fields.keys()[field_index];
-                try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' declared here", .{field_name});
+                try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' declared here", .{
+                    ip.stringToSlice(field_name),
+                });
                 try sema.addDeclaredHereNote(msg, union_ty);
                 break :msg msg;
             };
@@ -28976,11 +29083,14 @@ fn coerceEnumToUnion(
             const msg = msg: {
                 const field_name = union_obj.fields.keys()[field_index];
                 const msg = try sema.errMsg(block, inst_src, "coercion from enum '{}' to union '{}' must initialize '{}' field '{s}'", .{
-                    inst_ty.fmt(sema.mod), union_ty.fmt(sema.mod), field_ty.fmt(sema.mod), field_name,
+                    inst_ty.fmt(sema.mod),  union_ty.fmt(sema.mod),
+                    field_ty.fmt(sema.mod), ip.stringToSlice(field_name),
                 });
                 errdefer msg.destroy(sema.gpa);
 
-                try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' declared here", .{field_name});
+                try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' declared here", .{
+                    ip.stringToSlice(field_name),
+                });
                 try sema.addDeclaredHereNote(msg, union_ty);
                 break :msg msg;
             };
@@ -29049,7 +29159,10 @@ fn coerceEnumToUnion(
             const field_name = field.key_ptr.*;
             const field_ty = field.value_ptr.ty;
             if (!(try sema.typeHasRuntimeBits(field_ty))) continue;
-            try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' has type '{}'", .{ field_name, field_ty.fmt(sema.mod) });
+            try sema.addFieldErrNote(union_ty, field_index, msg, "field '{s}' has type '{}'", .{
+                ip.stringToSlice(field_name),
+                field_ty.fmt(sema.mod),
+            });
         }
         try sema.addDeclaredHereNote(msg, union_ty);
         break :msg msg;
@@ -29068,11 +29181,11 @@ fn coerceAnonStructToUnion(
     const mod = sema.mod;
     const inst_ty = sema.typeOf(inst);
     const field_info: union(enum) {
-        name: []const u8,
+        name: InternPool.NullTerminatedString,
         count: usize,
     } = switch (mod.intern_pool.indexToKey(inst_ty.toIntern())) {
         .anon_struct_type => |anon_struct_type| if (anon_struct_type.names.len == 1)
-            .{ .name = mod.intern_pool.stringToSlice(anon_struct_type.names[0]) }
+            .{ .name = anon_struct_type.names[0] }
         else
             .{ .count = anon_struct_type.names.len },
         .struct_type => |struct_type| name: {
@@ -29335,6 +29448,7 @@ fn coerceTupleToStruct(
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const struct_ty = try sema.resolveTypeFields(dest_ty);
 
     if (struct_ty.isTupleOrAnonStruct(mod)) {
@@ -29348,7 +29462,7 @@ fn coerceTupleToStruct(
 
     const inst_ty = sema.typeOf(inst);
     var runtime_src: ?LazySrcLoc = null;
-    const field_count = switch (mod.intern_pool.indexToKey(inst_ty.toIntern())) {
+    const field_count = switch (ip.indexToKey(inst_ty.toIntern())) {
         .anon_struct_type => |anon_struct_type| anon_struct_type.types.len,
         .struct_type => |struct_type| if (mod.structPtrUnwrap(struct_type.index)) |struct_obj|
             struct_obj.fields.count()
@@ -29360,11 +29474,11 @@ fn coerceTupleToStruct(
         const field_i = @intCast(u32, field_index_usize);
         const field_src = inst_src; // TODO better source location
         // https://github.com/ziglang/zig/issues/15709
-        const field_name: []const u8 = switch (mod.intern_pool.indexToKey(inst_ty.toIntern())) {
+        const field_name: InternPool.NullTerminatedString = switch (ip.indexToKey(inst_ty.toIntern())) {
             .anon_struct_type => |anon_struct_type| if (anon_struct_type.names.len > 0)
-                mod.intern_pool.stringToSlice(anon_struct_type.names[field_i])
+                anon_struct_type.names[field_i]
             else
-                try std.fmt.allocPrint(sema.arena, "{d}", .{field_i}),
+                try ip.getOrPutStringFmt(sema.gpa, "{d}", .{field_i}),
             .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.keys()[field_i],
             else => unreachable,
         };
@@ -29378,7 +29492,7 @@ fn coerceTupleToStruct(
                 return sema.failWithNeededComptime(block, field_src, "value stored in comptime field must be comptime-known");
             };
 
-            if (!init_val.eql(field.default_val, field.ty, sema.mod)) {
+            if (!init_val.eql(field.default_val.toValue(), field.ty, sema.mod)) {
                 return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, field_i);
             }
         }
@@ -29401,9 +29515,9 @@ fn coerceTupleToStruct(
         const field_name = fields.keys()[i];
         const field = fields.values()[i];
         const field_src = inst_src; // TODO better source location
-        if (field.default_val.toIntern() == .unreachable_value) {
+        if (field.default_val == .none) {
             const template = "missing struct field: {s}";
-            const args = .{field_name};
+            const args = .{ip.stringToSlice(field_name)};
             if (root_msg) |msg| {
                 try sema.errNote(block, field_src, msg, template, args);
             } else {
@@ -29412,9 +29526,9 @@ fn coerceTupleToStruct(
             continue;
         }
         if (runtime_src == null) {
-            field_vals[i] = field.default_val.toIntern();
+            field_vals[i] = field.default_val;
         } else {
-            field_ref.* = try sema.addConstant(field.ty, field.default_val);
+            field_ref.* = try sema.addConstant(field.ty, field.default_val.toValue());
         }
     }
 
@@ -29433,7 +29547,7 @@ fn coerceTupleToStruct(
         .ty = struct_ty.toIntern(),
         .storage = .{ .elems = field_vals },
     } });
-    errdefer mod.intern_pool.remove(struct_val);
+    errdefer ip.remove(struct_val);
 
     return sema.addConstant(struct_ty, struct_val.toValue());
 }
@@ -29446,7 +29560,8 @@ fn coerceTupleToTuple(
     inst_src: LazySrcLoc,
 ) !Air.Inst.Ref {
     const mod = sema.mod;
-    const dest_field_count = switch (mod.intern_pool.indexToKey(tuple_ty.toIntern())) {
+    const ip = &mod.intern_pool;
+    const dest_field_count = switch (ip.indexToKey(tuple_ty.toIntern())) {
         .anon_struct_type => |anon_struct_type| anon_struct_type.types.len,
         .struct_type => |struct_type| if (mod.structPtrUnwrap(struct_type.index)) |struct_obj|
             struct_obj.fields.count()
@@ -29459,7 +29574,7 @@ fn coerceTupleToTuple(
     @memset(field_refs, .none);
 
     const inst_ty = sema.typeOf(inst);
-    const src_field_count = switch (mod.intern_pool.indexToKey(inst_ty.toIntern())) {
+    const src_field_count = switch (ip.indexToKey(inst_ty.toIntern())) {
         .anon_struct_type => |anon_struct_type| anon_struct_type.types.len,
         .struct_type => |struct_type| if (mod.structPtrUnwrap(struct_type.index)) |struct_obj|
             struct_obj.fields.count()
@@ -29474,30 +29589,26 @@ fn coerceTupleToTuple(
         const field_i = @intCast(u32, field_index_usize);
         const field_src = inst_src; // TODO better source location
         // https://github.com/ziglang/zig/issues/15709
-        const field_name: []const u8 = switch (mod.intern_pool.indexToKey(inst_ty.toIntern())) {
+        const field_name: InternPool.NullTerminatedString = switch (ip.indexToKey(inst_ty.toIntern())) {
             .anon_struct_type => |anon_struct_type| if (anon_struct_type.names.len > 0)
-                mod.intern_pool.stringToSlice(anon_struct_type.names[field_i])
+                anon_struct_type.names[field_i]
             else
-                try std.fmt.allocPrint(sema.arena, "{d}", .{field_i}),
+                try ip.getOrPutStringFmt(sema.gpa, "{d}", .{field_i}),
             .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.keys()[field_i],
             else => unreachable,
         };
 
-        if (mem.eql(u8, field_name, "len")) {
+        if (ip.stringEqlSlice(field_name, "len"))
             return sema.fail(block, field_src, "cannot assign to 'len' field of tuple", .{});
-        }
 
-        const field_ty = switch (mod.intern_pool.indexToKey(tuple_ty.toIntern())) {
+        const field_ty = switch (ip.indexToKey(tuple_ty.toIntern())) {
             .anon_struct_type => |anon_struct_type| anon_struct_type.types[field_index_usize].toType(),
             .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.values()[field_index_usize].ty,
             else => unreachable,
         };
-        const default_val = switch (mod.intern_pool.indexToKey(tuple_ty.toIntern())) {
+        const default_val = switch (ip.indexToKey(tuple_ty.toIntern())) {
             .anon_struct_type => |anon_struct_type| anon_struct_type.values[field_index_usize],
-            .struct_type => |struct_type| switch (mod.structPtrUnwrap(struct_type.index).?.fields.values()[field_index_usize].default_val.toIntern()) {
-                .unreachable_value => .none,
-                else => |default_val| default_val,
-            },
+            .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.values()[field_index_usize].default_val,
             else => unreachable,
         };
 
@@ -29531,12 +29642,9 @@ fn coerceTupleToTuple(
     for (field_refs, 0..) |*field_ref, i| {
         if (field_ref.* != .none) continue;
 
-        const default_val = switch (mod.intern_pool.indexToKey(tuple_ty.toIntern())) {
+        const default_val = switch (ip.indexToKey(tuple_ty.toIntern())) {
             .anon_struct_type => |anon_struct_type| anon_struct_type.values[i],
-            .struct_type => |struct_type| switch (mod.structPtrUnwrap(struct_type.index).?.fields.values()[i].default_val.toIntern()) {
-                .unreachable_value => .none,
-                else => |default_val| default_val,
-            },
+            .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.values()[i].default_val,
             else => unreachable,
         };
 
@@ -29552,7 +29660,7 @@ fn coerceTupleToTuple(
                 continue;
             }
             const template = "missing struct field: {s}";
-            const args = .{tuple_ty.structFieldName(i, mod)};
+            const args = .{ip.stringToSlice(tuple_ty.structFieldName(i, mod))};
             if (root_msg) |msg| {
                 try sema.errNote(block, field_src, msg, template, args);
             } else {
@@ -29563,7 +29671,7 @@ fn coerceTupleToTuple(
         if (runtime_src == null) {
             field_vals[i] = default_val;
         } else {
-            const field_ty = switch (mod.intern_pool.indexToKey(tuple_ty.toIntern())) {
+            const field_ty = switch (ip.indexToKey(tuple_ty.toIntern())) {
                 .anon_struct_type => |anon_struct_type| anon_struct_type.types[i].toType(),
                 .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.values()[i].ty,
                 else => unreachable,
@@ -31803,15 +31911,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
         }
 
         if (struct_obj.layout == .Auto and mod.backendSupportsFeature(.field_reordering)) {
-            const optimized_order = if (struct_obj.owner_decl == sema.owner_decl_index)
-                try sema.perm_arena.alloc(u32, struct_obj.fields.count())
-            else blk: {
-                const decl = mod.declPtr(struct_obj.owner_decl);
-                var decl_arena: std.heap.ArenaAllocator = undefined;
-                const decl_arena_allocator = decl.value_arena.?.acquire(sema.gpa, &decl_arena);
-                defer decl.value_arena.?.release(&decl_arena);
-                break :blk try decl_arena_allocator.alloc(u32, struct_obj.fields.count());
-            };
+            const optimized_order = try mod.tmp_hack_arena.allocator().alloc(u32, struct_obj.fields.count());
 
             for (struct_obj.fields.values(), 0..) |field, i| {
                 optimized_order[i] = if (try sema.typeHasRuntimeBits(field.ty))
@@ -31852,9 +31952,6 @@ fn semaBackingIntType(mod: *Module, struct_obj: *Module.Struct) CompileError!voi
 
     const decl_index = struct_obj.owner_decl;
     const decl = mod.declPtr(decl_index);
-    var decl_arena: std.heap.ArenaAllocator = undefined;
-    const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
-    defer decl.value_arena.?.release(&decl_arena);
 
     const zir = mod.namespacePtr(struct_obj.namespace).file_scope.zir;
     const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
@@ -31880,7 +31977,6 @@ fn semaBackingIntType(mod: *Module, struct_obj: *Module.Struct) CompileError!voi
             .mod = mod,
             .gpa = gpa,
             .arena = analysis_arena.allocator(),
-            .perm_arena = decl_arena_allocator,
             .code = zir,
             .owner_decl = decl,
             .owner_decl_index = decl_index,
@@ -31936,7 +32032,6 @@ fn semaBackingIntType(mod: *Module, struct_obj: *Module.Struct) CompileError!voi
                 .mod = mod,
                 .gpa = gpa,
                 .arena = undefined,
-                .perm_arena = decl_arena_allocator,
                 .code = zir,
                 .owner_decl = decl,
                 .owner_decl_index = decl_index,
@@ -32581,6 +32676,7 @@ fn resolveInferredErrorSetTy(
 
 fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void {
     const gpa = mod.gpa;
+    const ip = &mod.intern_pool;
     const decl_index = struct_obj.owner_decl;
     const zir = mod.namespacePtr(struct_obj.namespace).file_scope.zir;
     const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended;
@@ -32628,9 +32724,6 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
     }
 
     const decl = mod.declPtr(decl_index);
-    var decl_arena: std.heap.ArenaAllocator = undefined;
-    const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
-    defer decl.value_arena.?.release(&decl_arena);
 
     var analysis_arena = std.heap.ArenaAllocator.init(gpa);
     defer analysis_arena.deinit();
@@ -32642,7 +32735,6 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
         .mod = mod,
         .gpa = gpa,
         .arena = analysis_arena.allocator(),
-        .perm_arena = decl_arena_allocator,
         .code = zir,
         .owner_decl = decl,
         .owner_decl_index = decl_index,
@@ -32674,7 +32766,7 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
     }
 
     struct_obj.fields = .{};
-    try struct_obj.fields.ensureTotalCapacity(decl_arena_allocator, fields_len);
+    try struct_obj.fields.ensureTotalCapacity(mod.tmp_hack_arena.allocator(), fields_len);
 
     const Field = struct {
         type_body_len: u32 = 0,
@@ -32725,16 +32817,15 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
             extra_index += 1;
 
             // This string needs to outlive the ZIR code.
-            const field_name = if (field_name_zir) |some|
-                try decl_arena_allocator.dupe(u8, some)
-            else
-                try std.fmt.allocPrint(decl_arena_allocator, "{d}", .{field_i});
+            const field_name = try ip.getOrPutString(gpa, if (field_name_zir) |s| s else try std.fmt.allocPrint(sema.arena, "{d}", .{
+                field_i,
+            }));
 
             const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name);
             if (gop.found_existing) {
                 const msg = msg: {
                     const field_src = mod.fieldSrcLoc(struct_obj.owner_decl, .{ .index = field_i }).lazy;
-                    const msg = try sema.errMsg(&block_scope, field_src, "duplicate struct field: '{s}'", .{field_name});
+                    const msg = try sema.errMsg(&block_scope, field_src, "duplicate struct field: '{s}'", .{ip.stringToSlice(field_name)});
                     errdefer msg.destroy(gpa);
 
                     const prev_field_index = struct_obj.fields.getIndex(field_name).?;
@@ -32748,7 +32839,7 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
             gop.value_ptr.* = .{
                 .ty = Type.noreturn,
                 .abi_align = 0,
-                .default_val = Value.@"unreachable",
+                .default_val = .none,
                 .is_comptime = is_comptime,
                 .offset = undefined,
             };
@@ -32917,7 +33008,7 @@ fn semaStructFields(mod: *Module, struct_obj: *Module.Struct) CompileError!void
                     }).lazy;
                     return sema.failWithNeededComptime(&block_scope, init_src, "struct field default value must be comptime-known");
                 };
-                field.default_val = try default_val.copy(decl_arena_allocator);
+                field.default_val = try default_val.intern(field.ty, mod);
             }
         }
     }
@@ -32935,6 +33026,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
     defer tracy.end();
 
     const gpa = mod.gpa;
+    const ip = &mod.intern_pool;
     const decl_index = union_obj.owner_decl;
     const zir = mod.namespacePtr(union_obj.namespace).file_scope.zir;
     const extended = zir.instructions.items(.data)[union_obj.zir_index].extended;
@@ -32978,9 +33070,6 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
     extra_index += body.len;
 
     const decl = mod.declPtr(decl_index);
-    var decl_arena: std.heap.ArenaAllocator = undefined;
-    const decl_arena_allocator = decl.value_arena.?.acquire(gpa, &decl_arena);
-    defer decl.value_arena.?.release(&decl_arena);
 
     var analysis_arena = std.heap.ArenaAllocator.init(gpa);
     defer analysis_arena.deinit();
@@ -32992,7 +33081,6 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         .mod = mod,
         .gpa = gpa,
         .arena = analysis_arena.allocator(),
-        .perm_arena = decl_arena_allocator,
         .code = zir,
         .owner_decl = decl,
         .owner_decl_index = decl_index,
@@ -33033,7 +33121,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         try ct_decl.intern(mod);
     }
 
-    try union_obj.fields.ensureTotalCapacity(decl_arena_allocator, fields_len);
+    try union_obj.fields.ensureTotalCapacity(mod.tmp_hack_arena.allocator(), fields_len);
 
     var int_tag_ty: Type = undefined;
     var enum_field_names: []InternPool.NullTerminatedString = &.{};
@@ -33070,7 +33158,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         } else {
             // The provided type is the enum tag type.
             union_obj.tag_ty = provided_ty;
-            const enum_type = switch (mod.intern_pool.indexToKey(union_obj.tag_ty.toIntern())) {
+            const enum_type = switch (ip.indexToKey(union_obj.tag_ty.toIntern())) {
                 .enum_type => |x| x,
                 else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{}'", .{union_obj.tag_ty.fmt(mod)}),
             };
@@ -33174,10 +33262,9 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         }
 
         // This string needs to outlive the ZIR code.
-        const field_name = try decl_arena_allocator.dupe(u8, field_name_zir);
-        const field_name_ip = try mod.intern_pool.getOrPutString(gpa, field_name);
+        const field_name = try ip.getOrPutString(gpa, field_name_zir);
         if (enum_field_names.len != 0) {
-            enum_field_names[field_i] = field_name_ip;
+            enum_field_names[field_i] = field_name;
         }
 
         const field_ty: Type = if (!has_type)
@@ -33205,7 +33292,9 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         if (gop.found_existing) {
             const msg = msg: {
                 const field_src = mod.fieldSrcLoc(union_obj.owner_decl, .{ .index = field_i }).lazy;
-                const msg = try sema.errMsg(&block_scope, field_src, "duplicate union field: '{s}'", .{field_name});
+                const msg = try sema.errMsg(&block_scope, field_src, "duplicate union field: '{s}'", .{
+                    ip.stringToSlice(field_name),
+                });
                 errdefer msg.destroy(gpa);
 
                 const prev_field_index = union_obj.fields.getIndex(field_name).?;
@@ -33218,14 +33307,14 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
         }
 
         if (explicit_enum_info) |tag_info| {
-            const enum_index = tag_info.nameIndex(&mod.intern_pool, field_name_ip) orelse {
+            const enum_index = tag_info.nameIndex(ip, field_name) orelse {
                 const msg = msg: {
                     const ty_src = mod.fieldSrcLoc(union_obj.owner_decl, .{
                         .index = field_i,
                         .range = .type,
                     }).lazy;
                     const msg = try sema.errMsg(&block_scope, ty_src, "no field named '{s}' in enum '{}'", .{
-                        field_name, union_obj.tag_ty.fmt(mod),
+                        ip.stringToSlice(field_name), union_obj.tag_ty.fmt(mod),
                     });
                     errdefer msg.destroy(sema.gpa);
                     try sema.addDeclaredHereNote(msg, union_obj.tag_ty);
@@ -33317,7 +33406,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void {
                 for (tag_info.names, 0..) |field_name, field_index| {
                     if (explicit_tags_seen[field_index]) continue;
                     try sema.addFieldErrNote(enum_ty, field_index, msg, "field '{s}' missing, declared here", .{
-                        mod.intern_pool.stringToSlice(field_name),
+                        ip.stringToSlice(field_name),
                     });
                 }
                 try sema.addDeclaredHereNote(msg, union_obj.tag_ty);
@@ -33345,14 +33434,22 @@ fn generateUnionTagTypeNumbered(
     union_obj: *Module.Union,
 ) !Type {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     const src_decl = mod.declPtr(block.src_decl);
     const new_decl_index = try mod.allocateNewDecl(block.namespace, src_decl.src_node, block.wip_capture_scope);
     errdefer mod.destroyDecl(new_decl_index);
     const name = name: {
-        const fqn = try union_obj.getFullyQualifiedName(mod);
-        defer sema.gpa.free(fqn);
-        break :name try std.fmt.allocPrintZ(sema.gpa, "@typeInfo({s}).Union.tag_type.?", .{fqn});
+        const prefix = "@typeInfo(";
+        const fqn = ip.stringToSlice(try union_obj.getFullyQualifiedName(mod));
+        const suffix = ").Union.tag_type.?";
+        const start = ip.string_bytes.items.len;
+        try ip.string_bytes.ensureUnusedCapacity(gpa, prefix.len + suffix.len + fqn.len);
+        ip.string_bytes.appendSliceAssumeCapacity(prefix);
+        ip.string_bytes.appendSliceAssumeCapacity(fqn);
+        ip.string_bytes.appendSliceAssumeCapacity(suffix);
+        break :name try ip.getOrPutTrailingString(gpa, ip.string_bytes.items.len - start);
     };
     try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, block.namespace, .{
         .ty = Type.type,
@@ -33390,6 +33487,8 @@ fn generateUnionTagTypeSimple(
     maybe_union_obj: ?*Module.Union,
 ) !Type {
     const mod = sema.mod;
+    const gpa = sema.gpa;
+    const ip = &mod.intern_pool;
 
     const new_decl_index = new_decl_index: {
         const union_obj = maybe_union_obj orelse {
@@ -33402,9 +33501,15 @@ fn generateUnionTagTypeSimple(
         const new_decl_index = try mod.allocateNewDecl(block.namespace, src_decl.src_node, block.wip_capture_scope);
         errdefer mod.destroyDecl(new_decl_index);
         const name = name: {
-            const fqn = try union_obj.getFullyQualifiedName(mod);
-            defer sema.gpa.free(fqn);
-            break :name try std.fmt.allocPrintZ(sema.gpa, "@typeInfo({s}).Union.tag_type.?", .{fqn});
+            const prefix = "@typeInfo(";
+            const fqn = ip.stringToSlice(try union_obj.getFullyQualifiedName(mod));
+            const suffix = ").Union.tag_type.?";
+            const start = ip.string_bytes.items.len;
+            try ip.string_bytes.ensureUnusedCapacity(gpa, prefix.len + suffix.len + fqn.len);
+            ip.string_bytes.appendSliceAssumeCapacity(prefix);
+            ip.string_bytes.appendSliceAssumeCapacity(fqn);
+            ip.string_bytes.appendSliceAssumeCapacity(suffix);
+            break :name try ip.getOrPutTrailingString(gpa, ip.string_bytes.items.len - start);
         };
         try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, block.namespace, .{
             .ty = Type.type,
@@ -33436,7 +33541,9 @@ fn generateUnionTagTypeSimple(
 }
 
 fn getBuiltin(sema: *Sema, name: []const u8) CompileError!Air.Inst.Ref {
-    var wip_captures = try WipCaptureScope.init(sema.gpa, sema.owner_decl.src_scope);
+    const gpa = sema.gpa;
+
+    var wip_captures = try WipCaptureScope.init(gpa, sema.owner_decl.src_scope);
     defer wip_captures.deinit();
 
     var block: Block = .{
@@ -33450,19 +33557,20 @@ fn getBuiltin(sema: *Sema, name: []const u8) CompileError!Air.Inst.Ref {
         .is_comptime = true,
     };
     defer {
-        block.instructions.deinit(sema.gpa);
-        block.params.deinit(sema.gpa);
+        block.instructions.deinit(gpa);
+        block.params.deinit(gpa);
     }
     const src = LazySrcLoc.nodeOffset(0);
 
     const mod = sema.mod;
+    const ip = &mod.intern_pool;
     const std_pkg = mod.main_pkg.table.get("std").?;
     const std_file = (mod.importPkg(std_pkg) catch unreachable).file;
     const opt_builtin_inst = (try sema.namespaceLookupRef(
         &block,
         src,
         mod.declPtr(std_file.root_decl.unwrap().?).src_namespace,
-        "builtin",
+        try ip.getOrPutString(gpa, "builtin"),
     )) orelse @panic("lib/std.zig is corrupt and missing 'builtin'");
     const builtin_inst = try sema.analyzeLoad(&block, src, opt_builtin_inst, src);
     const builtin_ty = sema.analyzeAsType(&block, src, builtin_inst) catch |err| switch (err) {
@@ -33473,7 +33581,7 @@ fn getBuiltin(sema: *Sema, name: []const u8) CompileError!Air.Inst.Ref {
         &block,
         src,
         builtin_ty.getNamespaceIndex(mod).unwrap().?,
-        name,
+        try ip.getOrPutString(gpa, name),
     )) orelse std.debug.panic("lib/std/builtin.zig is corrupt and missing '{s}'", .{name});
     return sema.analyzeDeclVal(&block, src, opt_ty_decl);
 }
@@ -33608,7 +33716,7 @@ pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
                     const field_vals = try sema.arena.alloc(InternPool.Index, s.fields.count());
                     for (field_vals, s.fields.values(), 0..) |*field_val, field, i| {
                         if (field.is_comptime) {
-                            field_val.* = try field.default_val.intern(field.ty, mod);
+                            field_val.* = field.default_val;
                             continue;
                         }
                         if (field.ty.eql(resolved_ty, sema.mod)) {
@@ -34287,7 +34395,7 @@ fn unionFieldIndex(
     sema: *Sema,
     block: *Block,
     unresolved_union_ty: Type,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_src: LazySrcLoc,
 ) !u32 {
     const mod = sema.mod;
@@ -34302,7 +34410,7 @@ fn structFieldIndex(
     sema: *Sema,
     block: *Block,
     unresolved_struct_ty: Type,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_src: LazySrcLoc,
 ) !u32 {
     const mod = sema.mod;
@@ -34321,19 +34429,17 @@ fn anonStructFieldIndex(
     sema: *Sema,
     block: *Block,
     struct_ty: Type,
-    field_name: []const u8,
+    field_name: InternPool.NullTerminatedString,
     field_src: LazySrcLoc,
 ) !u32 {
     const mod = sema.mod;
     switch (mod.intern_pool.indexToKey(struct_ty.toIntern())) {
         .anon_struct_type => |anon_struct_type| for (anon_struct_type.names, 0..) |name, i| {
-            if (mem.eql(u8, mod.intern_pool.stringToSlice(name), field_name)) {
-                return @intCast(u32, i);
-            }
+            if (name == field_name) return @intCast(u32, i);
         },
         .struct_type => |struct_type| if (mod.structPtrUnwrap(struct_type.index)) |struct_obj| {
             for (struct_obj.fields.keys(), 0..) |name, i| {
-                if (mem.eql(u8, name, field_name)) {
+                if (name == field_name) {
                     return @intCast(u32, i);
                 }
             }
@@ -34341,7 +34447,7 @@ fn anonStructFieldIndex(
         else => unreachable,
     }
     return sema.fail(block, field_src, "no field named '{s}' in anonymous struct '{}'", .{
-        field_name, struct_ty.fmt(sema.mod),
+        mod.intern_pool.stringToSlice(field_name), struct_ty.fmt(sema.mod),
     });
 }
 
src/type.zig
@@ -2546,7 +2546,7 @@ pub const Type = struct {
                         defer mod.gpa.free(field_vals);
                         for (field_vals, s.fields.values()) |*field_val, field| {
                             if (field.is_comptime) {
-                                field_val.* = try field.default_val.intern(field.ty, mod);
+                                field_val.* = field.default_val;
                                 continue;
                             }
                             if (try field.ty.onePossibleValue(mod)) |field_opv| {
@@ -2977,18 +2977,14 @@ pub const Type = struct {
         return mod.intern_pool.indexToKey(ty.toIntern()).enum_type.names.len;
     }
 
-    pub fn enumFieldName(ty: Type, field_index: usize, mod: *Module) [:0]const u8 {
-        const ip = &mod.intern_pool;
-        const field_name = ip.indexToKey(ty.toIntern()).enum_type.names[field_index];
-        return ip.stringToSlice(field_name);
+    pub fn enumFieldName(ty: Type, field_index: usize, mod: *Module) InternPool.NullTerminatedString {
+        return mod.intern_pool.indexToKey(ty.toIntern()).enum_type.names[field_index];
     }
 
-    pub fn enumFieldIndex(ty: Type, field_name: []const u8, mod: *Module) ?u32 {
+    pub fn enumFieldIndex(ty: Type, field_name: InternPool.NullTerminatedString, mod: *Module) ?u32 {
         const ip = &mod.intern_pool;
         const enum_type = ip.indexToKey(ty.toIntern()).enum_type;
-        // If the string is not interned, then the field certainly is not present.
-        const field_name_interned = ip.getString(field_name).unwrap() orelse return null;
-        return enum_type.nameIndex(ip, field_name_interned);
+        return enum_type.nameIndex(ip, field_name);
     }
 
     /// Asserts `ty` is an enum. `enum_tag` can either be `enum_field_index` or
@@ -3017,19 +3013,16 @@ pub const Type = struct {
         }
     }
 
-    pub fn structFieldName(ty: Type, field_index: usize, mod: *Module) []const u8 {
-        switch (mod.intern_pool.indexToKey(ty.toIntern())) {
+    pub fn structFieldName(ty: Type, field_index: usize, mod: *Module) InternPool.NullTerminatedString {
+        return switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .struct_type => |struct_type| {
                 const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
                 assert(struct_obj.haveFieldTypes());
                 return struct_obj.fields.keys()[field_index];
             },
-            .anon_struct_type => |anon_struct| {
-                const name = anon_struct.names[field_index];
-                return mod.intern_pool.stringToSlice(name);
-            },
+            .anon_struct_type => |anon_struct| anon_struct.names[field_index],
             else => unreachable,
-        }
+        };
     }
 
     pub fn structFieldCount(ty: Type, mod: *Module) usize {
@@ -3082,7 +3075,10 @@ pub const Type = struct {
         switch (mod.intern_pool.indexToKey(ty.toIntern())) {
             .struct_type => |struct_type| {
                 const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
-                return struct_obj.fields.values()[index].default_val;
+                const val = struct_obj.fields.values()[index].default_val;
+                // TODO: avoid using `unreachable` to indicate this.
+                if (val == .none) return Value.@"unreachable";
+                return val.toValue();
             },
             .anon_struct_type => |anon_struct| {
                 const val = anon_struct.values[index];
@@ -3100,7 +3096,7 @@ pub const Type = struct {
                 const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
                 const field = struct_obj.fields.values()[index];
                 if (field.is_comptime) {
-                    return field.default_val;
+                    return field.default_val.toValue();
                 } else {
                     return field.ty.onePossibleValue(mod);
                 }
src/TypedValue.zig
@@ -201,10 +201,10 @@ pub fn print(
             },
             .variable => return writer.writeAll("(variable)"),
             .extern_func => |extern_func| return writer.print("(extern function '{s}')", .{
-                mod.declPtr(extern_func.decl).name,
+                mod.intern_pool.stringToSlice(mod.declPtr(extern_func.decl).name),
             }),
-            .func => |func| return writer.print("(function '{s}')", .{
-                mod.declPtr(mod.funcPtr(func.index).owner_decl).name,
+            .func => |func| return writer.print("(function '{d}')", .{
+                mod.intern_pool.stringToSlice(mod.declPtr(mod.funcPtr(func.index).owner_decl).name),
             }),
             .int => |int| switch (int.storage) {
                 inline .u64, .i64, .big_int => |x| return writer.print("{}", .{x}),
@@ -296,19 +296,20 @@ fn printAggregate(
     }
     if (ty.zigTypeTag(mod) == .Struct) {
         try writer.writeAll(".{");
-        const max_len = std.math.min(ty.structFieldCount(mod), max_aggregate_items);
+        const max_len = @min(ty.structFieldCount(mod), max_aggregate_items);
 
-        var i: u32 = 0;
-        while (i < max_len) : (i += 1) {
+        for (0..max_len) |i| {
             if (i != 0) try writer.writeAll(", ");
-            if (switch (mod.intern_pool.indexToKey(ty.toIntern())) {
-                .struct_type => |struct_type| mod.structPtrUnwrap(struct_type.index).?.fields.keys()[i],
-                .anon_struct_type => |anon_struct_type| if (anon_struct_type.isTuple())
-                    null
-                else
-                    mod.intern_pool.stringToSlice(anon_struct_type.names[i]),
+
+            const field_name = switch (mod.intern_pool.indexToKey(ty.toIntern())) {
+                .struct_type => |x| mod.structPtrUnwrap(x.index).?.fields.keys()[i].toOptional(),
+                .anon_struct_type => |x| if (x.isTuple()) .none else x.names[i].toOptional(),
                 else => unreachable,
-            }) |field_name| try writer.print(".{s} = ", .{field_name});
+            };
+
+            if (field_name.unwrap()) |name_ip| try writer.print(".{s} = ", .{
+                mod.intern_pool.stringToSlice(name_ip),
+            });
             try print(.{
                 .ty = ty.structFieldType(i, mod),
                 .val = try val.fieldValue(mod, i),
src/value.zig
@@ -24,9 +24,6 @@ pub const Value = struct {
     /// This union takes advantage of the fact that the first page of memory
     /// is unmapped, giving us 4096 possible enum tags that have no payload.
     legacy: extern union {
-        /// If the tag value is less than Tag.no_payload_count, then no pointer
-        /// dereference is needed.
-        tag_if_small_enough: Tag,
         ptr_otherwise: *Payload,
     },
 
@@ -64,8 +61,6 @@ pub const Value = struct {
         /// An instance of a union.
         @"union",
 
-        pub const no_payload_count = 0;
-
         pub fn Type(comptime t: Tag) type {
             return switch (t) {
                 .eu_payload,
@@ -96,16 +91,7 @@ pub const Value = struct {
         }
     };
 
-    pub fn initTag(small_tag: Tag) Value {
-        assert(@enumToInt(small_tag) < Tag.no_payload_count);
-        return Value{
-            .ip_index = .none,
-            .legacy = .{ .tag_if_small_enough = small_tag },
-        };
-    }
-
     pub fn initPayload(payload: *Payload) Value {
-        assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
         return Value{
             .ip_index = .none,
             .legacy = .{ .ptr_otherwise = payload },
@@ -114,11 +100,7 @@ pub const Value = struct {
 
     pub fn tag(self: Value) Tag {
         assert(self.ip_index == .none);
-        if (@enumToInt(self.legacy.tag_if_small_enough) < Tag.no_payload_count) {
-            return self.legacy.tag_if_small_enough;
-        } else {
-            return self.legacy.ptr_otherwise.tag;
-        }
+        return self.legacy.ptr_otherwise.tag;
     }
 
     /// Prefer `castTag` to this.
@@ -129,12 +111,7 @@ pub const Value = struct {
         if (@hasField(T, "base_tag")) {
             return self.castTag(T.base_tag);
         }
-        if (@enumToInt(self.legacy.tag_if_small_enough) < Tag.no_payload_count) {
-            return null;
-        }
         inline for (@typeInfo(Tag).Enum.fields) |field| {
-            if (field.value < Tag.no_payload_count)
-                continue;
             const t = @intToEnum(Tag, field.value);
             if (self.legacy.ptr_otherwise.tag == t) {
                 if (T == t.Type()) {
@@ -149,9 +126,6 @@ pub const Value = struct {
     pub fn castTag(self: Value, comptime t: Tag) ?*t.Type() {
         if (self.ip_index != .none) return null;
 
-        if (@enumToInt(self.legacy.tag_if_small_enough) < Tag.no_payload_count)
-            return null;
-
         if (self.legacy.ptr_otherwise.tag == t)
             return @fieldParentPtr(t.Type(), "base", self.legacy.ptr_otherwise);
 
@@ -164,12 +138,7 @@ pub const Value = struct {
         if (self.ip_index != .none) {
             return Value{ .ip_index = self.ip_index, .legacy = undefined };
         }
-        if (@enumToInt(self.legacy.tag_if_small_enough) < Tag.no_payload_count) {
-            return Value{
-                .ip_index = .none,
-                .legacy = .{ .tag_if_small_enough = self.legacy.tag_if_small_enough },
-            };
-        } else switch (self.legacy.ptr_otherwise.tag) {
+        switch (self.legacy.ptr_otherwise.tag) {
             .bytes => {
                 const bytes = self.castTag(.bytes).?.data;
                 const new_payload = try arena.create(Payload.Bytes);
@@ -312,6 +281,30 @@ pub const Value = struct {
         } };
     }
 
+    /// Asserts that the value is representable as an array of bytes.
+    /// Returns the value as a null-terminated string stored in the InternPool.
+    pub fn toIpString(val: Value, ty: Type, mod: *Module) !InternPool.NullTerminatedString {
+        const ip = &mod.intern_pool;
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
+            .enum_literal => |enum_literal| enum_literal,
+            .ptr => |ptr| switch (ptr.len) {
+                .none => unreachable,
+                else => try arrayToIpString(val, ptr.len.toValue().toUnsignedInt(mod), mod),
+            },
+            .aggregate => |aggregate| switch (aggregate.storage) {
+                .bytes => |bytes| try ip.getOrPutString(mod.gpa, bytes),
+                .elems => try arrayToIpString(val, ty.arrayLen(mod), mod),
+                .repeated_elem => |elem| {
+                    const byte = @intCast(u8, elem.toValue().toUnsignedInt(mod));
+                    const len = @intCast(usize, ty.arrayLen(mod));
+                    try ip.string_bytes.appendNTimes(mod.gpa, byte, len);
+                    return ip.getOrPutTrailingString(mod.gpa, len);
+                },
+            },
+            else => unreachable,
+        };
+    }
+
     /// Asserts that the value is representable as an array of bytes.
     /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
     pub fn toAllocatedBytes(val: Value, ty: Type, allocator: Allocator, mod: *Module) ![]u8 {
@@ -319,11 +312,11 @@ pub const Value = struct {
             .enum_literal => |enum_literal| allocator.dupe(u8, mod.intern_pool.stringToSlice(enum_literal)),
             .ptr => |ptr| switch (ptr.len) {
                 .none => unreachable,
-                else => arrayToAllocatedBytes(val, ptr.len.toValue().toUnsignedInt(mod), allocator, mod),
+                else => try arrayToAllocatedBytes(val, ptr.len.toValue().toUnsignedInt(mod), allocator, mod),
             },
             .aggregate => |aggregate| switch (aggregate.storage) {
                 .bytes => |bytes| try allocator.dupe(u8, bytes),
-                .elems => arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
+                .elems => try arrayToAllocatedBytes(val, ty.arrayLen(mod), allocator, mod),
                 .repeated_elem => |elem| {
                     const byte = @intCast(u8, elem.toValue().toUnsignedInt(mod));
                     const result = try allocator.alloc(u8, @intCast(usize, ty.arrayLen(mod)));
@@ -344,6 +337,23 @@ pub const Value = struct {
         return result;
     }
 
+    fn arrayToIpString(val: Value, len_u64: u64, mod: *Module) !InternPool.NullTerminatedString {
+        const gpa = mod.gpa;
+        const ip = &mod.intern_pool;
+        const len = @intCast(usize, len_u64);
+        try ip.string_bytes.ensureUnusedCapacity(gpa, len);
+        for (0..len) |i| {
+            // I don't think elemValue has the possibility to affect ip.string_bytes. Let's
+            // assert just to be sure.
+            const prev = ip.string_bytes.items.len;
+            const elem_val = try val.elemValue(mod, i);
+            assert(ip.string_bytes.items.len == prev);
+            const byte = @intCast(u8, elem_val.toUnsignedInt(mod));
+            ip.string_bytes.appendAssumeCapacity(byte);
+        }
+        return ip.getOrPutTrailingString(gpa, len);
+    }
+
     pub fn intern(val: Value, ty: Type, mod: *Module) Allocator.Error!InternPool.Index {
         if (val.ip_index != .none) return (try mod.getCoerced(val, ty)).toIntern();
         switch (val.tag()) {
@@ -498,7 +508,7 @@ pub const Value = struct {
             // Assume it is already an integer and return it directly.
             .simple_type, .int_type => val,
             .enum_literal => |enum_literal| {
-                const field_index = ty.enumFieldIndex(ip.stringToSlice(enum_literal), mod).?;
+                const field_index = ty.enumFieldIndex(enum_literal, mod).?;
                 return switch (ip.indexToKey(ty.toIntern())) {
                     // Assume it is already an integer and return it directly.
                     .simple_type, .int_type => val,
@@ -776,7 +786,7 @@ pub const Value = struct {
                     .error_union => |error_union| error_union.val.err_name,
                     else => unreachable,
                 };
-                const int = mod.global_error_set.get(mod.intern_pool.stringToSlice(name)).?;
+                const int = @intCast(Module.ErrorInt, mod.global_error_set.getIndex(name).?);
                 std.mem.writeInt(Int, buffer[0..@sizeOf(Int)], @intCast(Int, int), endian);
             },
             .Union => switch (ty.containerLayout(mod)) {
@@ -1028,10 +1038,10 @@ pub const Value = struct {
                 // TODO revisit this when we have the concept of the error tag type
                 const Int = u16;
                 const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], endian);
-                const name = mod.error_name_list.items[@intCast(usize, int)];
+                const name = mod.global_error_set.keys()[@intCast(usize, int)];
                 return (try mod.intern(.{ .err = .{
                     .ty = ty.toIntern(),
-                    .name = mod.intern_pool.getString(name).unwrap().?,
+                    .name = name,
                 } })).toValue();
             },
             .Pointer => {
@@ -2155,15 +2165,29 @@ pub const Value = struct {
     /// unreachable. For error unions, prefer `errorUnionIsPayload` to find out whether
     /// something is an error or not because it works without having to figure out the
     /// string.
-    pub fn getError(self: Value, mod: *const Module) ?[]const u8 {
-        return mod.intern_pool.stringToSliceUnwrap(switch (mod.intern_pool.indexToKey(self.toIntern())) {
-            .err => |err| err.name.toOptional(),
+    pub fn getError(val: Value, mod: *const Module) ?[]const u8 {
+        return switch (getErrorName(val, mod)) {
+            .empty => null,
+            else => |s| mod.intern_pool.stringToSlice(s),
+        };
+    }
+
+    pub fn getErrorName(val: Value, mod: *const Module) InternPool.NullTerminatedString {
+        return switch (mod.intern_pool.indexToKey(val.toIntern())) {
+            .err => |err| err.name,
             .error_union => |error_union| switch (error_union.val) {
-                .err_name => |err_name| err_name.toOptional(),
-                .payload => .none,
+                .err_name => |err_name| err_name,
+                .payload => .empty,
             },
             else => unreachable,
-        });
+        };
+    }
+
+    pub fn getErrorInt(val: Value, mod: *const Module) Module.ErrorInt {
+        return switch (getErrorName(val, mod)) {
+            .empty => 0,
+            else => |s| @intCast(Module.ErrorInt, mod.global_error_set.getIndex(s).?),
+        };
     }
 
     /// Assumes the type is an error union. Returns true if and only if the value is
@@ -4225,7 +4249,7 @@ pub const Value = struct {
         var fields: [tags.len]std.builtin.Type.StructField = undefined;
         for (&fields, tags) |*field, t| field.* = .{
             .name = t.name,
-            .type = *if (t.value < Tag.no_payload_count) void else @field(Tag, t.name).Type(),
+            .type = *@field(Tag, t.name).Type(),
             .default_value = null,
             .is_comptime = false,
             .alignment = 0,