Commit 4404c4d200

Jakub Konka <kubkon@jakubkonka.com>
2023-01-31 17:54:12
link: make Elf atoms fully owned by the linker
1 parent d42a931
src/arch/aarch64/CodeGen.zig
@@ -4308,8 +4308,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
             const fn_owner_decl = mod.declPtr(func.owner_decl);
 
             if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-                try fn_owner_decl.link.elf.ensureInitialized(elf_file);
-                const got_addr = @intCast(u32, fn_owner_decl.link.elf.getOffsetTableAddress(elf_file));
+                const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
+                const atom = elf_file.getAtom(atom_index);
+                const got_addr = @intCast(u32, atom.getOffsetTableAddress(elf_file));
                 try self.genSetReg(Type.initTag(.usize), .x30, .{ .memory = got_addr });
             } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
                 const atom = try macho_file.getOrCreateAtomForDecl(func.owner_decl);
@@ -6138,8 +6139,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
     mod.markDeclAlive(decl);
 
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        try decl.link.elf.ensureInitialized(elf_file);
-        return MCValue{ .memory = decl.link.elf.getOffsetTableAddress(elf_file) };
+        const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index);
+        const atom = elf_file.getAtom(atom_index);
+        return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) };
     } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
         const atom = try macho_file.getOrCreateAtomForDecl(decl_index);
         const sym_index = macho_file.getAtom(atom).getSymbolIndex().?;
@@ -6168,8 +6170,7 @@ fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue {
         return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)});
     };
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        const vaddr = elf_file.local_symbols.items[local_sym_index].st_value;
-        return MCValue{ .memory = vaddr };
+        return MCValue{ .memory = elf_file.getSymbol(local_sym_index).st_value };
     } else if (self.bin_file.cast(link.File.MachO)) |_| {
         return MCValue{ .linker_load = .{
             .type = .direct,
src/arch/arm/CodeGen.zig
@@ -4256,12 +4256,11 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
     if (self.air.value(callee)) |func_value| {
         if (func_value.castTag(.function)) |func_payload| {
             const func = func_payload.data;
-            const mod = self.bin_file.options.module.?;
-            const fn_owner_decl = mod.declPtr(func.owner_decl);
 
             if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-                try fn_owner_decl.link.elf.ensureInitialized(elf_file);
-                const got_addr = @intCast(u32, fn_owner_decl.link.elf.getOffsetTableAddress(elf_file));
+                const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
+                const atom = elf_file.getAtom(atom_index);
+                const got_addr = @intCast(u32, atom.getOffsetTableAddress(elf_file));
                 try self.genSetReg(Type.initTag(.usize), .lr, .{ .memory = got_addr });
             } else if (self.bin_file.cast(link.File.MachO)) |_| {
                 unreachable; // unsupported architecture for MachO
@@ -6084,8 +6083,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
     mod.markDeclAlive(decl);
 
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        try decl.link.elf.ensureInitialized(elf_file);
-        return MCValue{ .memory = decl.link.elf.getOffsetTableAddress(elf_file) };
+        const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index);
+        const atom = elf_file.getAtom(atom_index);
+        return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) };
     } else if (self.bin_file.cast(link.File.MachO)) |_| {
         unreachable; // unsupported architecture for MachO
     } else if (self.bin_file.cast(link.File.Coff)) |_| {
@@ -6106,8 +6106,7 @@ fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue {
         return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)});
     };
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        const vaddr = elf_file.local_symbols.items[local_sym_index].st_value;
-        return MCValue{ .memory = vaddr };
+        return MCValue{ .memory = elf_file.getSymbol(local_sym_index).st_value };
     } else if (self.bin_file.cast(link.File.MachO)) |_| {
         unreachable;
     } else if (self.bin_file.cast(link.File.Coff)) |_| {
src/arch/riscv64/CodeGen.zig
@@ -1721,12 +1721,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
         if (self.air.value(callee)) |func_value| {
             if (func_value.castTag(.function)) |func_payload| {
                 const func = func_payload.data;
-
-                const mod = self.bin_file.options.module.?;
-                const fn_owner_decl = mod.declPtr(func.owner_decl);
-                try fn_owner_decl.link.elf.ensureInitialized(elf_file);
-                const got_addr = @intCast(u32, fn_owner_decl.link.elf.getOffsetTableAddress(elf_file));
-
+                const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
+                const atom = elf_file.getAtom(atom_index);
+                const got_addr = @intCast(u32, atom.getOffsetTableAddress(elf_file));
                 try self.genSetReg(Type.initTag(.usize), .ra, .{ .memory = got_addr });
                 _ = try self.addInst(.{
                     .tag = .jalr,
@@ -2553,8 +2550,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
     const decl = mod.declPtr(decl_index);
     mod.markDeclAlive(decl);
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        try decl.link.elf.ensureInitialized(elf_file);
-        return MCValue{ .memory = decl.link.elf.getOffsetTableAddress(elf_file) };
+        const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index);
+        const atom = elf_file.getAtom(atom_index);
+        return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) };
     } else if (self.bin_file.cast(link.File.MachO)) |_| {
         unreachable;
     } else if (self.bin_file.cast(link.File.Coff)) |_| {
src/arch/sparc64/CodeGen.zig
@@ -1216,11 +1216,10 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
         if (self.bin_file.tag == link.File.Elf.base_tag) {
             if (func_value.castTag(.function)) |func_payload| {
                 const func = func_payload.data;
-                const mod = self.bin_file.options.module.?;
-                const fn_owner_decl = mod.declPtr(func.owner_decl);
                 const got_addr = if (self.bin_file.cast(link.File.Elf)) |elf_file| blk: {
-                    try fn_owner_decl.link.elf.ensureInitialized(elf_file);
-                    break :blk @intCast(u32, fn_owner_decl.link.elf.getOffsetTableAddress(elf_file));
+                    const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
+                    const atom = elf_file.getAtom(atom_index);
+                    break :blk @intCast(u32, atom.getOffsetTableAddress(elf_file));
                 } else unreachable;
 
                 try self.genSetReg(Type.initTag(.usize), .o7, .{ .memory = got_addr });
@@ -4205,8 +4204,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
 
     mod.markDeclAlive(decl);
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        try decl.link.elf.ensureInitialized(elf_file);
-        return MCValue{ .memory = decl.link.elf.getOffsetTableAddress(elf_file) };
+        const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index);
+        const atom = elf_file.getAtom(atom_index);
+        return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) };
     } else {
         return self.fail("TODO codegen non-ELF const Decl pointer", .{});
     }
src/arch/x86_64/CodeGen.zig
@@ -4000,8 +4000,9 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier
             const fn_owner_decl = mod.declPtr(func.owner_decl);
 
             if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-                try fn_owner_decl.link.elf.ensureInitialized(elf_file);
-                const got_addr = @intCast(u32, fn_owner_decl.link.elf.getOffsetTableAddress(elf_file));
+                const atom_index = try elf_file.getOrCreateAtomForDecl(func.owner_decl);
+                const atom = elf_file.getAtom(atom_index);
+                const got_addr = @intCast(u32, atom.getOffsetTableAddress(elf_file));
                 _ = try self.addInst(.{
                     .tag = .call,
                     .ops = Mir.Inst.Ops.encode(.{ .flags = 0b01 }),
@@ -6721,8 +6722,9 @@ fn lowerDeclRef(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index) Inne
     module.markDeclAlive(decl);
 
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        try decl.link.elf.ensureInitialized(elf_file);
-        return MCValue{ .memory = decl.link.elf.getOffsetTableAddress(elf_file) };
+        const atom_index = try elf_file.getOrCreateAtomForDecl(decl_index);
+        const atom = elf_file.getAtom(atom_index);
+        return MCValue{ .memory = atom.getOffsetTableAddress(elf_file) };
     } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
         const atom_index = try macho_file.getOrCreateAtomForDecl(decl_index);
         const sym_index = macho_file.getAtom(atom_index).getSymbolIndex().?;
@@ -6751,8 +6753,7 @@ fn lowerUnnamedConst(self: *Self, tv: TypedValue) InnerError!MCValue {
         return self.fail("lowering unnamed constant failed: {s}", .{@errorName(err)});
     };
     if (self.bin_file.cast(link.File.Elf)) |elf_file| {
-        const vaddr = elf_file.local_symbols.items[local_sym_index].st_value;
-        return MCValue{ .memory = vaddr };
+        return MCValue{ .memory = elf_file.getSymbol(local_sym_index).st_value };
     } else if (self.bin_file.cast(link.File.MachO)) |_| {
         return MCValue{ .linker_load = .{
             .type = .direct,
src/link/Elf/Atom.zig
@@ -20,44 +20,35 @@ offset_table_index: u32,
 
 /// Points to the previous and next neighbors, based on the `text_offset`.
 /// This can be used to find, for example, the capacity of this `TextBlock`.
-prev: ?*Atom,
-next: ?*Atom,
+prev_index: ?Atom.Index,
+next_index: ?Atom.Index,
 
 dbg_info_atom: Dwarf.Atom,
 
-pub const empty = Atom{
-    .local_sym_index = 0,
-    .offset_table_index = undefined,
-    .prev = null,
-    .next = null,
-    .dbg_info_atom = undefined,
-};
+pub const Index = u32;
 
-pub fn ensureInitialized(self: *Atom, elf_file: *Elf) !void {
-    if (self.getSymbolIndex() != null) return; // Already initialized
-    self.local_sym_index = try elf_file.allocateLocalSymbol();
-    self.offset_table_index = try elf_file.allocateGotOffset();
-    try elf_file.atom_by_index_table.putNoClobber(elf_file.base.allocator, self.local_sym_index, self);
-}
+pub const Reloc = struct {
+    target: u32,
+    offset: u64,
+    addend: u32,
+    prev_vaddr: u64,
+};
 
 pub fn getSymbolIndex(self: Atom) ?u32 {
     if (self.local_sym_index == 0) return null;
     return self.local_sym_index;
 }
 
-pub fn getSymbol(self: Atom, elf_file: *Elf) elf.Elf64_Sym {
-    const sym_index = self.getSymbolIndex().?;
-    return elf_file.local_symbols.items[sym_index];
+pub fn getSymbol(self: Atom, elf_file: *const Elf) elf.Elf64_Sym {
+    return elf_file.getSymbol(self.getSymbolIndex().?);
 }
 
 pub fn getSymbolPtr(self: Atom, elf_file: *Elf) *elf.Elf64_Sym {
-    const sym_index = self.getSymbolIndex().?;
-    return &elf_file.local_symbols.items[sym_index];
+    return elf_file.getSymbolPtr(self.getSymbolIndex().?);
 }
 
-pub fn getName(self: Atom, elf_file: *Elf) []const u8 {
-    const sym = self.getSymbol();
-    return elf_file.getString(sym.st_name);
+pub fn getName(self: Atom, elf_file: *const Elf) []const u8 {
+    return elf_file.getSymbolName(self.getSymbolIndex().?);
 }
 
 pub fn getOffsetTableAddress(self: Atom, elf_file: *Elf) u64 {
@@ -72,9 +63,10 @@ pub fn getOffsetTableAddress(self: Atom, elf_file: *Elf) u64 {
 /// Returns how much room there is to grow in virtual address space.
 /// File offset relocation happens transparently, so it is not included in
 /// this calculation.
-pub fn capacity(self: Atom, elf_file: *Elf) u64 {
+pub fn capacity(self: Atom, elf_file: *const Elf) u64 {
     const self_sym = self.getSymbol(elf_file);
-    if (self.next) |next| {
+    if (self.next_index) |next_index| {
+        const next = elf_file.getAtom(next_index);
         const next_sym = next.getSymbol(elf_file);
         return next_sym.st_value - self_sym.st_value;
     } else {
@@ -83,9 +75,10 @@ pub fn capacity(self: Atom, elf_file: *Elf) u64 {
     }
 }
 
-pub fn freeListEligible(self: Atom, elf_file: *Elf) bool {
+pub fn freeListEligible(self: Atom, elf_file: *const Elf) bool {
     // No need to keep a free list node for the last block.
-    const next = self.next orelse return false;
+    const next_index = self.next_index orelse return false;
+    const next = elf_file.getAtom(next_index);
     const self_sym = self.getSymbol(elf_file);
     const next_sym = next.getSymbol(elf_file);
     const cap = next_sym.st_value - self_sym.st_value;
@@ -94,3 +87,17 @@ pub fn freeListEligible(self: Atom, elf_file: *Elf) bool {
     const surplus = cap - ideal_cap;
     return surplus >= Elf.min_text_capacity;
 }
+
+pub fn addRelocation(elf_file: *Elf, atom_index: Index, reloc: Reloc) !void {
+    const gpa = elf_file.base.allocator;
+    const gop = try elf_file.relocs.getOrPut(gpa, atom_index);
+    if (!gop.found_existing) {
+        gop.value_ptr.* = .{};
+    }
+    try gop.value_ptr.append(gpa, reloc);
+}
+
+pub fn freeRelocations(elf_file: *Elf, atom_index: Index) void {
+    var removed_relocs = elf_file.relocs.fetchRemove(atom_index);
+    if (removed_relocs) |*relocs| relocs.value.deinit(elf_file.base.allocator);
+}
src/link/Dwarf.zig
@@ -1099,7 +1099,7 @@ pub fn commitDeclState(
                         switch (self.bin_file.tag) {
                             .elf => {
                                 const elf_file = self.bin_file.cast(File.Elf).?;
-                                const debug_line_sect = &elf_file.sections.items[elf_file.debug_line_section_index.?];
+                                const debug_line_sect = &elf_file.sections.items(.shdr)[elf_file.debug_line_section_index.?];
                                 const file_pos = debug_line_sect.sh_offset + src_fn.off;
                                 try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, src_fn.len);
                             },
@@ -1152,7 +1152,7 @@ pub fn commitDeclState(
                     const elf_file = self.bin_file.cast(File.Elf).?;
                     const shdr_index = elf_file.debug_line_section_index.?;
                     try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true);
-                    const debug_line_sect = elf_file.sections.items[shdr_index];
+                    const debug_line_sect = elf_file.sections.items(.shdr)[shdr_index];
                     const file_pos = debug_line_sect.sh_offset + src_fn.off;
                     try pwriteDbgLineNops(
                         elf_file.base.file.?,
@@ -1332,7 +1332,7 @@ fn updateDeclDebugInfoAllocation(self: *Dwarf, atom: *Atom, len: u32) !void {
                 switch (self.bin_file.tag) {
                     .elf => {
                         const elf_file = self.bin_file.cast(File.Elf).?;
-                        const debug_info_sect = &elf_file.sections.items[elf_file.debug_info_section_index.?];
+                        const debug_info_sect = &elf_file.sections.items(.shdr)[elf_file.debug_info_section_index.?];
                         const file_pos = debug_info_sect.sh_offset + atom.off;
                         try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, &[0]u8{}, atom.len, false);
                     },
@@ -1399,7 +1399,7 @@ fn writeDeclDebugInfo(self: *Dwarf, atom: *Atom, dbg_info_buf: []const u8) !void
             const elf_file = self.bin_file.cast(File.Elf).?;
             const shdr_index = elf_file.debug_info_section_index.?;
             try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true);
-            const debug_info_sect = elf_file.sections.items[shdr_index];
+            const debug_info_sect = elf_file.sections.items(.shdr)[shdr_index];
             const file_pos = debug_info_sect.sh_offset + atom.off;
             try pwriteDbgInfoNops(
                 elf_file.base.file.?,
@@ -1475,7 +1475,7 @@ pub fn updateDeclLineNumber(self: *Dwarf, decl: *const Module.Decl) !void {
     switch (self.bin_file.tag) {
         .elf => {
             const elf_file = self.bin_file.cast(File.Elf).?;
-            const shdr = elf_file.sections.items[elf_file.debug_line_section_index.?];
+            const shdr = elf_file.sections.items(.shdr)[elf_file.debug_line_section_index.?];
             const file_pos = shdr.sh_offset + decl.fn_link.elf.off + self.getRelocDbgLineOff();
             try elf_file.base.file.?.pwriteAll(&data, file_pos);
         },
@@ -1690,7 +1690,7 @@ pub fn writeDbgAbbrev(self: *Dwarf) !void {
             const elf_file = self.bin_file.cast(File.Elf).?;
             const shdr_index = elf_file.debug_abbrev_section_index.?;
             try elf_file.growNonAllocSection(shdr_index, needed_size, 1, false);
-            const debug_abbrev_sect = elf_file.sections.items[shdr_index];
+            const debug_abbrev_sect = elf_file.sections.items(.shdr)[shdr_index];
             const file_pos = debug_abbrev_sect.sh_offset + abbrev_offset;
             try elf_file.base.file.?.pwriteAll(&abbrev_buf, file_pos);
         },
@@ -1805,7 +1805,7 @@ pub fn writeDbgInfoHeader(self: *Dwarf, module: *Module, low_pc: u64, high_pc: u
     switch (self.bin_file.tag) {
         .elf => {
             const elf_file = self.bin_file.cast(File.Elf).?;
-            const debug_info_sect = elf_file.sections.items[elf_file.debug_info_section_index.?];
+            const debug_info_sect = elf_file.sections.items(.shdr)[elf_file.debug_info_section_index.?];
             const file_pos = debug_info_sect.sh_offset;
             try pwriteDbgInfoNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt, false);
         },
@@ -2124,7 +2124,7 @@ pub fn writeDbgAranges(self: *Dwarf, addr: u64, size: u64) !void {
             const elf_file = self.bin_file.cast(File.Elf).?;
             const shdr_index = elf_file.debug_aranges_section_index.?;
             try elf_file.growNonAllocSection(shdr_index, needed_size, 16, false);
-            const debug_aranges_sect = elf_file.sections.items[shdr_index];
+            const debug_aranges_sect = elf_file.sections.items(.shdr)[shdr_index];
             const file_pos = debug_aranges_sect.sh_offset;
             try elf_file.base.file.?.pwriteAll(di_buf.items, file_pos);
         },
@@ -2285,9 +2285,9 @@ pub fn writeDbgLineHeader(self: *Dwarf) !void {
             .elf => {
                 const elf_file = self.bin_file.cast(File.Elf).?;
                 const shdr_index = elf_file.debug_line_section_index.?;
-                const needed_size = elf_file.sections.items[shdr_index].sh_size + delta;
+                const needed_size = elf_file.sections.items(.shdr)[shdr_index].sh_size + delta;
                 try elf_file.growNonAllocSection(shdr_index, needed_size, 1, true);
-                const file_pos = elf_file.sections.items[shdr_index].sh_offset + src_fn.off;
+                const file_pos = elf_file.sections.items(.shdr)[shdr_index].sh_offset + src_fn.off;
 
                 const amt = try elf_file.base.file.?.preadAll(buffer, file_pos);
                 if (amt != buffer.len) return error.InputOutput;
@@ -2346,7 +2346,7 @@ pub fn writeDbgLineHeader(self: *Dwarf) !void {
     switch (self.bin_file.tag) {
         .elf => {
             const elf_file = self.bin_file.cast(File.Elf).?;
-            const debug_line_sect = elf_file.sections.items[elf_file.debug_line_section_index.?];
+            const debug_line_sect = elf_file.sections.items(.shdr)[elf_file.debug_line_section_index.?];
             const file_pos = debug_line_sect.sh_offset;
             try pwriteDbgLineNops(elf_file.base.file.?, file_pos, 0, di_buf.items, jmp_amt);
         },
@@ -2487,7 +2487,7 @@ pub fn flushModule(self: *Dwarf, module: *Module) !void {
             switch (self.bin_file.tag) {
                 .elf => {
                     const elf_file = self.bin_file.cast(File.Elf).?;
-                    const debug_info_sect = &elf_file.sections.items[elf_file.debug_info_section_index.?];
+                    const debug_info_sect = &elf_file.sections.items(.shdr)[elf_file.debug_info_section_index.?];
                     break :blk debug_info_sect.sh_offset;
                 },
                 .macho => {
@@ -2638,7 +2638,7 @@ fn addDbgInfoErrorSet(
 fn getDbgInfoAtom(tag: File.Tag, mod: *Module, decl_index: Module.Decl.Index) *Atom {
     const decl = mod.declPtr(decl_index);
     return switch (tag) {
-        .elf => &decl.link.elf.dbg_info_atom,
+        .elf => unreachable,
         .macho => unreachable,
         .wasm => &decl.link.wasm.dbg_info_atom,
         else => unreachable,
src/link/Elf.zig
@@ -1,43 +1,89 @@
 const Elf = @This();
 
 const std = @import("std");
+const build_options = @import("build_options");
 const builtin = @import("builtin");
-const math = std.math;
-const mem = std.mem;
 const assert = std.debug.assert;
-const Allocator = std.mem.Allocator;
-const fs = std.fs;
 const elf = std.elf;
+const fs = std.fs;
 const log = std.log.scoped(.link);
+const math = std.math;
+const mem = std.mem;
 
-const Atom = @import("Elf/Atom.zig");
-const Module = @import("../Module.zig");
-const Compilation = @import("../Compilation.zig");
-const Dwarf = @import("Dwarf.zig");
 const codegen = @import("../codegen.zig");
-const lldMain = @import("../main.zig").lldMain;
-const trace = @import("../tracy.zig").trace;
-const Package = @import("../Package.zig");
-const Value = @import("../value.zig").Value;
-const Type = @import("../type.zig").Type;
-const TypedValue = @import("../TypedValue.zig");
-const link = @import("../link.zig");
-const File = link.File;
-const build_options = @import("build_options");
-const target_util = @import("../target.zig");
 const glibc = @import("../glibc.zig");
+const link = @import("../link.zig");
+const lldMain = @import("../main.zig").lldMain;
 const musl = @import("../musl.zig");
-const Cache = @import("../Cache.zig");
+const target_util = @import("../target.zig");
+const trace = @import("../tracy.zig").trace;
+
 const Air = @import("../Air.zig");
+const Allocator = std.mem.Allocator;
+pub const Atom = @import("Elf/Atom.zig");
+const Cache = @import("../Cache.zig");
+const Compilation = @import("../Compilation.zig");
+const Dwarf = @import("Dwarf.zig");
+const File = link.File;
 const Liveness = @import("../Liveness.zig");
 const LlvmObject = @import("../codegen/llvm.zig").Object;
-
-pub const TextBlock = Atom;
+const Module = @import("../Module.zig");
+const Package = @import("../Package.zig");
+const StringTable = @import("strtab.zig").StringTable;
+const Type = @import("../type.zig").Type;
+const TypedValue = @import("../TypedValue.zig");
+const Value = @import("../value.zig").Value;
 
 const default_entry_addr = 0x8000000;
 
 pub const base_tag: File.Tag = .elf;
 
+const Section = struct {
+    shdr: elf.Elf64_Shdr,
+    phdr_index: u16,
+
+    /// Index of the last allocated atom in this section.
+    last_atom_index: ?Atom.Index = null,
+
+    /// A list of atoms that have surplus capacity. This list can have false
+    /// positives, as functions grow and shrink over time, only sometimes being added
+    /// or removed from the freelist.
+    ///
+    /// An atom has surplus capacity when its overcapacity value is greater than
+    /// padToIdeal(minimum_atom_size). That is, when it has so
+    /// much extra capacity, that we could fit a small new symbol in it, itself with
+    /// ideal_capacity or more.
+    ///
+    /// Ideal capacity is defined by size + (size / ideal_factor)
+    ///
+    /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
+    /// overcapacity can be negative. A simple way to have negative overcapacity is to
+    /// allocate a fresh text block, which will have ideal capacity, and then grow it
+    /// by 1 byte. It will then have -1 overcapacity.
+    free_list: std.ArrayListUnmanaged(Atom.Index) = .{},
+};
+
+const DeclMetadata = struct {
+    atom: Atom.Index,
+    shdr: u16,
+    /// A list of all exports aliases of this Decl.
+    exports: std.ArrayListUnmanaged(u32) = .{},
+
+    fn getExport(m: DeclMetadata, elf_file: *const Elf, name: []const u8) ?u32 {
+        for (m.exports.items) |exp| {
+            if (mem.eql(u8, name, elf_file.getSymbolName(exp))) return exp;
+        }
+        return null;
+    }
+
+    fn getExportPtr(m: *DeclMetadata, elf_file: *Elf, name: []const u8) ?*u32 {
+        for (m.exports.items) |*exp| {
+            if (mem.eql(u8, name, elf_file.getSymbolName(exp.*))) return exp;
+        }
+        return null;
+    }
+};
+
 base: File,
 dwarf: ?Dwarf = null,
 
@@ -48,12 +94,12 @@ llvm_object: ?*LlvmObject = null,
 
 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
 /// Same order as in the file.
-sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){},
+sections: std.MultiArrayList(Section) = .{},
 shdr_table_offset: ?u64 = null,
 
 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
 /// Same order as in the file.
-program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){},
+program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = .{},
 phdr_table_offset: ?u64 = null,
 /// The index into the program headers of a PT_LOAD program header with Read and Execute flags
 phdr_load_re_index: ?u16 = null,
@@ -65,12 +111,10 @@ phdr_load_ro_index: ?u16 = null,
 /// The index into the program headers of a PT_LOAD program header with Write flag
 phdr_load_rw_index: ?u16 = null,
 
-phdr_shdr_table: std.AutoHashMapUnmanaged(u16, u16) = .{},
-
 entry_addr: ?u64 = null,
 page_size: u32,
 
-shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
+shstrtab: StringTable(.strtab) = .{},
 shstrtab_index: ?u16 = null,
 
 symtab_section_index: ?u16 = null,
@@ -113,39 +157,14 @@ debug_line_header_dirty: bool = false,
 
 error_flags: File.ErrorFlags = File.ErrorFlags{},
 
-/// Pointer to the last allocated atom
-atoms: std.AutoHashMapUnmanaged(u16, *TextBlock) = .{},
-
-/// A list of text blocks that have surplus capacity. This list can have false
-/// positives, as functions grow and shrink over time, only sometimes being added
-/// or removed from the freelist.
-///
-/// A text block has surplus capacity when its overcapacity value is greater than
-/// padToIdeal(minimum_text_block_size). That is, when it has so
-/// much extra capacity, that we could fit a small new symbol in it, itself with
-/// ideal_capacity or more.
-///
-/// Ideal capacity is defined by size + (size / ideal_factor)
-///
-/// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
-/// overcapacity can be negative. A simple way to have negative overcapacity is to
-/// allocate a fresh text block, which will have ideal capacity, and then grow it
-/// by 1 byte. It will then have -1 overcapacity.
-atom_free_lists: std.AutoHashMapUnmanaged(u16, std.ArrayListUnmanaged(*TextBlock)) = .{},
-
-/// Table of Decls that are currently alive.
-/// We store them here so that we can properly dispose of any allocated
-/// memory within the atom in the incremental linker.
-/// TODO consolidate this.
-decls: std.AutoHashMapUnmanaged(Module.Decl.Index, ?u16) = .{},
+/// Table of tracked Decls.
+decls: std.AutoHashMapUnmanaged(Module.Decl.Index, DeclMetadata) = .{},
 
 /// List of atoms that are owned directly by the linker.
-/// Currently these are only atoms that are the result of linking
-/// object files. Atoms which take part in incremental linking are
-/// at present owned by Module.Decl.
-/// TODO consolidate this.
-managed_atoms: std.ArrayListUnmanaged(*TextBlock) = .{},
-atom_by_index_table: std.AutoHashMapUnmanaged(u32, *TextBlock) = .{},
+atoms: std.ArrayListUnmanaged(Atom) = .{},
+
+/// Table of atoms indexed by the symbol index.
+atom_by_index_table: std.AutoHashMapUnmanaged(u32, Atom.Index) = .{},
 
 /// Table of unnamed constants associated with a parent `Decl`.
 /// We store them here so that we can free the constants whenever the `Decl`
@@ -173,15 +192,8 @@ unnamed_const_atoms: UnnamedConstTable = .{},
 /// this will be a table indexed by index into the list of Atoms.
 relocs: RelocTable = .{},
 
-const Reloc = struct {
-    target: u32,
-    offset: u64,
-    addend: u32,
-    prev_vaddr: u64,
-};
-
-const RelocTable = std.AutoHashMapUnmanaged(*TextBlock, std.ArrayListUnmanaged(Reloc));
-const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(*TextBlock));
+const RelocTable = std.AutoHashMapUnmanaged(Atom.Index, std.ArrayListUnmanaged(Atom.Reloc));
+const UnnamedConstTable = std.AutoHashMapUnmanaged(Module.Decl.Index, std.ArrayListUnmanaged(Atom.Index));
 
 /// When allocating, the ideal_capacity is calculated by
 /// actual_capacity + (actual_capacity / ideal_factor)
@@ -190,15 +202,11 @@ const ideal_factor = 3;
 /// In order for a slice of bytes to be considered eligible to keep metadata pointing at
 /// it as a possible place to put new symbols, it must have enough room for this many bytes
 /// (plus extra for reserved capacity).
-const minimum_text_block_size = 64;
-pub const min_text_capacity = padToIdeal(minimum_text_block_size);
+const minimum_atom_size = 64;
+pub const min_text_capacity = padToIdeal(minimum_atom_size);
 
 pub const PtrWidth = enum { p32, p64 };
 
-pub const Export = struct {
-    sym_index: ?u32 = null,
-};
-
 pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Options) !*Elf {
     assert(options.target.ofmt == .elf);
 
@@ -230,16 +238,19 @@ pub fn openPath(allocator: Allocator, sub_path: []const u8, options: link.Option
 
     // There must always be a null section in index 0
     try self.sections.append(allocator, .{
-        .sh_name = 0,
-        .sh_type = elf.SHT_NULL,
-        .sh_flags = 0,
-        .sh_addr = 0,
-        .sh_offset = 0,
-        .sh_size = 0,
-        .sh_link = 0,
-        .sh_info = 0,
-        .sh_addralign = 0,
-        .sh_entsize = 0,
+        .shdr = .{
+            .sh_name = 0,
+            .sh_type = elf.SHT_NULL,
+            .sh_flags = 0,
+            .sh_addr = 0,
+            .sh_offset = 0,
+            .sh_size = 0,
+            .sh_link = 0,
+            .sh_info = 0,
+            .sh_addralign = 0,
+            .sh_entsize = 0,
+        },
+        .phdr_index = undefined,
     });
 
     try self.populateMissingMetadata();
@@ -286,75 +297,67 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*Elf {
 }
 
 pub fn deinit(self: *Elf) void {
+    const gpa = self.base.allocator;
+
     if (build_options.have_llvm) {
-        if (self.llvm_object) |llvm_object| llvm_object.destroy(self.base.allocator);
-    }
-
-    self.sections.deinit(self.base.allocator);
-    self.program_headers.deinit(self.base.allocator);
-    self.shstrtab.deinit(self.base.allocator);
-    self.local_symbols.deinit(self.base.allocator);
-    self.global_symbols.deinit(self.base.allocator);
-    self.global_symbol_free_list.deinit(self.base.allocator);
-    self.local_symbol_free_list.deinit(self.base.allocator);
-    self.offset_table_free_list.deinit(self.base.allocator);
-    self.offset_table.deinit(self.base.allocator);
-    self.phdr_shdr_table.deinit(self.base.allocator);
-    self.decls.deinit(self.base.allocator);
-
-    self.atoms.deinit(self.base.allocator);
+        if (self.llvm_object) |llvm_object| llvm_object.destroy(gpa);
+    }
+
+    for (self.sections.items(.free_list)) |*free_list| {
+        free_list.deinit(gpa);
+    }
+    self.sections.deinit(gpa);
+
+    self.program_headers.deinit(gpa);
+    self.shstrtab.deinit(gpa);
+    self.local_symbols.deinit(gpa);
+    self.global_symbols.deinit(gpa);
+    self.global_symbol_free_list.deinit(gpa);
+    self.local_symbol_free_list.deinit(gpa);
+    self.offset_table_free_list.deinit(gpa);
+    self.offset_table.deinit(gpa);
+
     {
-        var it = self.atom_free_lists.valueIterator();
-        while (it.next()) |free_list| {
-            free_list.deinit(self.base.allocator);
+        var it = self.decls.iterator();
+        while (it.next()) |entry| {
+            entry.value_ptr.exports.deinit(gpa);
         }
-        self.atom_free_lists.deinit(self.base.allocator);
+        self.decls.deinit(gpa);
     }
 
-    for (self.managed_atoms.items) |atom| {
-        self.base.allocator.destroy(atom);
-    }
-    self.managed_atoms.deinit(self.base.allocator);
+    self.atoms.deinit(gpa);
+    self.atom_by_index_table.deinit(gpa);
 
     {
         var it = self.unnamed_const_atoms.valueIterator();
         while (it.next()) |atoms| {
-            atoms.deinit(self.base.allocator);
+            atoms.deinit(gpa);
         }
-        self.unnamed_const_atoms.deinit(self.base.allocator);
+        self.unnamed_const_atoms.deinit(gpa);
     }
 
     {
         var it = self.relocs.valueIterator();
         while (it.next()) |relocs| {
-            relocs.deinit(self.base.allocator);
+            relocs.deinit(gpa);
         }
-        self.relocs.deinit(self.base.allocator);
+        self.relocs.deinit(gpa);
     }
 
-    self.atom_by_index_table.deinit(self.base.allocator);
-
-    if (self.dwarf) |*dw| {
-        dw.deinit();
-    }
+    // if (self.dwarf) |*dw| {
+    //     dw.deinit();
+    // }
 }
 
 pub fn getDeclVAddr(self: *Elf, decl_index: Module.Decl.Index, reloc_info: File.RelocInfo) !u64 {
-    const mod = self.base.options.module.?;
-    const decl = mod.declPtr(decl_index);
-
     assert(self.llvm_object == null);
 
-    try decl.link.elf.ensureInitialized(self);
-    const target = decl.link.elf.getSymbolIndex().?;
-
-    const vaddr = self.local_symbols.items[target].st_value;
-    const atom = self.atom_by_index_table.get(reloc_info.parent_atom_index).?;
-    const gop = try self.relocs.getOrPut(self.base.allocator, atom);
-    if (!gop.found_existing) {
-        gop.value_ptr.* = .{};
-    }
-    try gop.value_ptr.append(self.base.allocator, .{
+    const this_atom_index = try self.getOrCreateAtomForDecl(decl_index);
+    const this_atom = self.getAtom(this_atom_index);
+    const target = this_atom.getSymbolIndex().?;
+    const vaddr = this_atom.getSymbol(self).st_value;
+    const atom_index = self.getAtomIndexForSymbol(reloc_info.parent_atom_index).?;
+    try Atom.addRelocation(self, atom_index, .{
         .target = target,
         .offset = reloc_info.offset,
         .addend = reloc_info.addend,
@@ -375,7 +378,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
 
     if (self.shdr_table_offset) |off| {
         const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr);
-        const tight_size = self.sections.items.len * shdr_size;
+        const tight_size = self.sections.slice().len * shdr_size;
         const increased_size = padToIdeal(tight_size);
         const test_end = off + increased_size;
         if (end > off and start < test_end) {
@@ -385,7 +388,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
 
     if (self.phdr_table_offset) |off| {
         const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr);
-        const tight_size = self.sections.items.len * phdr_size;
+        const tight_size = self.sections.slice().len * phdr_size;
         const increased_size = padToIdeal(tight_size);
         const test_end = off + increased_size;
         if (end > off and start < test_end) {
@@ -393,7 +396,7 @@ fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
         }
     }
 
-    for (self.sections.items) |section| {
+    for (self.sections.items(.shdr)) |section| {
         const increased_size = padToIdeal(section.sh_size);
         const test_end = section.sh_offset + increased_size;
         if (end > section.sh_offset and start < test_end) {
@@ -420,7 +423,7 @@ pub fn allocatedSize(self: *Elf, start: u64) u64 {
     if (self.phdr_table_offset) |off| {
         if (off > start and off < min_pos) min_pos = off;
     }
-    for (self.sections.items) |section| {
+    for (self.sections.items(.shdr)) |section| {
         if (section.sh_offset <= start) continue;
         if (section.sh_offset < min_pos) min_pos = section.sh_offset;
     }
@@ -439,31 +442,10 @@ pub fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u32) u64 {
     return start;
 }
 
-/// TODO Improve this to use a table.
-fn makeString(self: *Elf, bytes: []const u8) !u32 {
-    try self.shstrtab.ensureUnusedCapacity(self.base.allocator, bytes.len + 1);
-    const result = self.shstrtab.items.len;
-    self.shstrtab.appendSliceAssumeCapacity(bytes);
-    self.shstrtab.appendAssumeCapacity(0);
-    return @intCast(u32, result);
-}
-
-pub fn getString(self: Elf, str_off: u32) []const u8 {
-    assert(str_off < self.shstrtab.items.len);
-    return mem.sliceTo(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off), 0);
-}
-
-fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 {
-    const existing_name = self.getString(old_str_off);
-    if (mem.eql(u8, existing_name, new_name)) {
-        return old_str_off;
-    }
-    return self.makeString(new_name);
-}
-
 pub fn populateMissingMetadata(self: *Elf) !void {
     assert(self.llvm_object == null);
 
+    const gpa = self.base.allocator;
     const small_ptr = switch (self.ptr_width) {
         .p32 => true,
         .p64 => false,
@@ -477,7 +459,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         const off = self.findFreeSpace(file_size, p_align);
         log.debug("found PT_LOAD RE free space 0x{x} to 0x{x}", .{ off, off + file_size });
         const entry_addr: u64 = self.entry_addr orelse if (self.base.options.target.cpu.arch == .spu_2) @as(u64, 0) else default_entry_addr;
-        try self.program_headers.append(self.base.allocator, .{
+        try self.program_headers.append(gpa, .{
             .p_type = elf.PT_LOAD,
             .p_offset = off,
             .p_filesz = file_size,
@@ -487,7 +469,6 @@ pub fn populateMissingMetadata(self: *Elf) !void {
             .p_align = p_align,
             .p_flags = elf.PF_X | elf.PF_R,
         });
-        try self.atom_free_lists.putNoClobber(self.base.allocator, self.phdr_load_re_index.?, .{});
         self.entry_addr = null;
         self.phdr_table_dirty = true;
     }
@@ -504,7 +485,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         // we'll need to re-use that function anyway, in case the GOT grows and overlaps something
         // else in virtual memory.
         const got_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x4000000 else 0x8000;
-        try self.program_headers.append(self.base.allocator, .{
+        try self.program_headers.append(gpa, .{
             .p_type = elf.PT_LOAD,
             .p_offset = off,
             .p_filesz = file_size,
@@ -527,7 +508,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         log.debug("found PT_LOAD RO free space 0x{x} to 0x{x}", .{ off, off + file_size });
         // TODO Same as for GOT
         const rodata_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0xc000000 else 0xa000;
-        try self.program_headers.append(self.base.allocator, .{
+        try self.program_headers.append(gpa, .{
             .p_type = elf.PT_LOAD,
             .p_offset = off,
             .p_filesz = file_size,
@@ -537,7 +518,6 @@ pub fn populateMissingMetadata(self: *Elf) !void {
             .p_align = p_align,
             .p_flags = elf.PF_R,
         });
-        try self.atom_free_lists.putNoClobber(self.base.allocator, self.phdr_load_ro_index.?, .{});
         self.phdr_table_dirty = true;
     }
 
@@ -551,7 +531,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         log.debug("found PT_LOAD RW free space 0x{x} to 0x{x}", .{ off, off + file_size });
         // TODO Same as for GOT
         const rwdata_addr: u32 = if (self.base.options.target.cpu.arch.ptrBitWidth() >= 32) 0x10000000 else 0xc000;
-        try self.program_headers.append(self.base.allocator, .{
+        try self.program_headers.append(gpa, .{
             .p_type = elf.PT_LOAD,
             .p_offset = off,
             .p_filesz = file_size,
@@ -561,278 +541,290 @@ pub fn populateMissingMetadata(self: *Elf) !void {
             .p_align = p_align,
             .p_flags = elf.PF_R | elf.PF_W,
         });
-        try self.atom_free_lists.putNoClobber(self.base.allocator, self.phdr_load_rw_index.?, .{});
         self.phdr_table_dirty = true;
     }
 
     if (self.shstrtab_index == null) {
-        self.shstrtab_index = @intCast(u16, self.sections.items.len);
-        assert(self.shstrtab.items.len == 0);
-        try self.shstrtab.append(self.base.allocator, 0); // need a 0 at position 0
-        const off = self.findFreeSpace(self.shstrtab.items.len, 1);
-        log.debug("found shstrtab free space 0x{x} to 0x{x}", .{ off, off + self.shstrtab.items.len });
-        try self.sections.append(self.base.allocator, .{
-            .sh_name = try self.makeString(".shstrtab"),
-            .sh_type = elf.SHT_STRTAB,
-            .sh_flags = 0,
-            .sh_addr = 0,
-            .sh_offset = off,
-            .sh_size = self.shstrtab.items.len,
-            .sh_link = 0,
-            .sh_info = 0,
-            .sh_addralign = 1,
-            .sh_entsize = 0,
+        self.shstrtab_index = @intCast(u16, self.sections.slice().len);
+        assert(self.shstrtab.buffer.items.len == 0);
+        try self.shstrtab.buffer.append(gpa, 0); // need a 0 at position 0
+        const off = self.findFreeSpace(self.shstrtab.buffer.items.len, 1);
+        log.debug("found shstrtab free space 0x{x} to 0x{x}", .{ off, off + self.shstrtab.buffer.items.len });
+        try self.sections.append(gpa, .{
+            .shdr = .{
+                .sh_name = try self.shstrtab.insert(gpa, ".shstrtab"),
+                .sh_type = elf.SHT_STRTAB,
+                .sh_flags = 0,
+                .sh_addr = 0,
+                .sh_offset = off,
+                .sh_size = self.shstrtab.buffer.items.len,
+                .sh_link = 0,
+                .sh_info = 0,
+                .sh_addralign = 1,
+                .sh_entsize = 0,
+            },
+            .phdr_index = undefined,
         });
         self.shstrtab_dirty = true;
         self.shdr_table_dirty = true;
     }
 
     if (self.text_section_index == null) {
-        self.text_section_index = @intCast(u16, self.sections.items.len);
+        self.text_section_index = @intCast(u16, self.sections.slice().len);
         const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
 
-        try self.sections.append(self.base.allocator, .{
-            .sh_name = try self.makeString(".text"),
-            .sh_type = elf.SHT_PROGBITS,
-            .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR,
-            .sh_addr = phdr.p_vaddr,
-            .sh_offset = phdr.p_offset,
-            .sh_size = phdr.p_filesz,
-            .sh_link = 0,
-            .sh_info = 0,
-            .sh_addralign = 1,
-            .sh_entsize = 0,
+        try self.sections.append(gpa, .{
+            .shdr = .{
+                .sh_name = try self.shstrtab.insert(gpa, ".text"),
+                .sh_type = elf.SHT_PROGBITS,
+                .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR,
+                .sh_addr = phdr.p_vaddr,
+                .sh_offset = phdr.p_offset,
+                .sh_size = phdr.p_filesz,
+                .sh_link = 0,
+                .sh_info = 0,
+                .sh_addralign = 1,
+                .sh_entsize = 0,
+            },
+            .phdr_index = self.phdr_load_re_index.?,
         });
-        try self.phdr_shdr_table.putNoClobber(
-            self.base.allocator,
-            self.phdr_load_re_index.?,
-            self.text_section_index.?,
-        );
         self.shdr_table_dirty = true;
     }
 
     if (self.got_section_index == null) {
-        self.got_section_index = @intCast(u16, self.sections.items.len);
+        self.got_section_index = @intCast(u16, self.sections.slice().len);
         const phdr = &self.program_headers.items[self.phdr_got_index.?];
 
-        try self.sections.append(self.base.allocator, .{
-            .sh_name = try self.makeString(".got"),
-            .sh_type = elf.SHT_PROGBITS,
-            .sh_flags = elf.SHF_ALLOC,
-            .sh_addr = phdr.p_vaddr,
-            .sh_offset = phdr.p_offset,
-            .sh_size = phdr.p_filesz,
-            .sh_link = 0,
-            .sh_info = 0,
-            .sh_addralign = @as(u16, ptr_size),
-            .sh_entsize = 0,
+        try self.sections.append(gpa, .{
+            .shdr = .{
+                .sh_name = try self.shstrtab.insert(gpa, ".got"),
+                .sh_type = elf.SHT_PROGBITS,
+                .sh_flags = elf.SHF_ALLOC,
+                .sh_addr = phdr.p_vaddr,
+                .sh_offset = phdr.p_offset,
+                .sh_size = phdr.p_filesz,
+                .sh_link = 0,
+                .sh_info = 0,
+                .sh_addralign = @as(u16, ptr_size),
+                .sh_entsize = 0,
+            },
+            .phdr_index = self.phdr_got_index.?,
         });
-        try self.phdr_shdr_table.putNoClobber(
-            self.base.allocator,
-            self.phdr_got_index.?,
-            self.got_section_index.?,
-        );
         self.shdr_table_dirty = true;
     }
 
     if (self.rodata_section_index == null) {
-        self.rodata_section_index = @intCast(u16, self.sections.items.len);
+        self.rodata_section_index = @intCast(u16, self.sections.slice().len);
         const phdr = &self.program_headers.items[self.phdr_load_ro_index.?];
 
-        try self.sections.append(self.base.allocator, .{
-            .sh_name = try self.makeString(".rodata"),
-            .sh_type = elf.SHT_PROGBITS,
-            .sh_flags = elf.SHF_ALLOC,
-            .sh_addr = phdr.p_vaddr,
-            .sh_offset = phdr.p_offset,
-            .sh_size = phdr.p_filesz,
-            .sh_link = 0,
-            .sh_info = 0,
-            .sh_addralign = 1,
-            .sh_entsize = 0,
+        try self.sections.append(gpa, .{
+            .shdr = .{
+                .sh_name = try self.shstrtab.insert(gpa, ".rodata"),
+                .sh_type = elf.SHT_PROGBITS,
+                .sh_flags = elf.SHF_ALLOC,
+                .sh_addr = phdr.p_vaddr,
+                .sh_offset = phdr.p_offset,
+                .sh_size = phdr.p_filesz,
+                .sh_link = 0,
+                .sh_info = 0,
+                .sh_addralign = 1,
+                .sh_entsize = 0,
+            },
+            .phdr_index = self.phdr_load_ro_index.?,
         });
-        try self.phdr_shdr_table.putNoClobber(
-            self.base.allocator,
-            self.phdr_load_ro_index.?,
-            self.rodata_section_index.?,
-        );
         self.shdr_table_dirty = true;
     }
 
     if (self.data_section_index == null) {
-        self.data_section_index = @intCast(u16, self.sections.items.len);
+        self.data_section_index = @intCast(u16, self.sections.slice().len);
         const phdr = &self.program_headers.items[self.phdr_load_rw_index.?];
 
-        try self.sections.append(self.base.allocator, .{
-            .sh_name = try self.makeString(".data"),
-            .sh_type = elf.SHT_PROGBITS,
-            .sh_flags = elf.SHF_WRITE | elf.SHF_ALLOC,
-            .sh_addr = phdr.p_vaddr,
-            .sh_offset = phdr.p_offset,
-            .sh_size = phdr.p_filesz,
-            .sh_link = 0,
-            .sh_info = 0,
-            .sh_addralign = @as(u16, ptr_size),
-            .sh_entsize = 0,
+        try self.sections.append(gpa, .{
+            .shdr = .{
+                .sh_name = try self.shstrtab.insert(gpa, ".data"),
+                .sh_type = elf.SHT_PROGBITS,
+                .sh_flags = elf.SHF_WRITE | elf.SHF_ALLOC,
+                .sh_addr = phdr.p_vaddr,
+                .sh_offset = phdr.p_offset,
+                .sh_size = phdr.p_filesz,
+                .sh_link = 0,
+                .sh_info = 0,
+                .sh_addralign = @as(u16, ptr_size),
+                .sh_entsize = 0,
+            },
+            .phdr_index = self.phdr_load_rw_index.?,
         });
-        try self.phdr_shdr_table.putNoClobber(
-            self.base.allocator,
-            self.phdr_load_rw_index.?,
-            self.data_section_index.?,
-        );
         self.shdr_table_dirty = true;
     }
 
     if (self.symtab_section_index == null) {
-        self.symtab_section_index = @intCast(u16, self.sections.items.len);
+        self.symtab_section_index = @intCast(u16, self.sections.slice().len);
         const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
         const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
         const file_size = self.base.options.symbol_count_hint * each_size;
         const off = self.findFreeSpace(file_size, min_align);
         log.debug("found symtab free space 0x{x} to 0x{x}", .{ off, off + file_size });
 
-        try self.sections.append(self.base.allocator, .{
-            .sh_name = try self.makeString(".symtab"),
-            .sh_type = elf.SHT_SYMTAB,
-            .sh_flags = 0,
-            .sh_addr = 0,
-            .sh_offset = off,
-            .sh_size = file_size,
-            // The section header index of the associated string table.
-            .sh_link = self.shstrtab_index.?,
-            .sh_info = @intCast(u32, self.local_symbols.items.len),
-            .sh_addralign = min_align,
-            .sh_entsize = each_size,
+        try self.sections.append(gpa, .{
+            .shdr = .{
+                .sh_name = try self.shstrtab.insert(gpa, ".symtab"),
+                .sh_type = elf.SHT_SYMTAB,
+                .sh_flags = 0,
+                .sh_addr = 0,
+                .sh_offset = off,
+                .sh_size = file_size,
+                // The section header index of the associated string table.
+                .sh_link = self.shstrtab_index.?,
+                .sh_info = @intCast(u32, self.local_symbols.items.len),
+                .sh_addralign = min_align,
+                .sh_entsize = each_size,
+            },
+            .phdr_index = undefined,
         });
         self.shdr_table_dirty = true;
         try self.writeSymbol(0);
     }
 
-    if (self.dwarf) |*dw| {
-        if (self.debug_str_section_index == null) {
-            self.debug_str_section_index = @intCast(u16, self.sections.items.len);
-            assert(dw.strtab.items.len == 0);
-            try dw.strtab.append(self.base.allocator, 0);
-            try self.sections.append(self.base.allocator, .{
-                .sh_name = try self.makeString(".debug_str"),
-                .sh_type = elf.SHT_PROGBITS,
-                .sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS,
-                .sh_addr = 0,
-                .sh_offset = 0,
-                .sh_size = 0,
-                .sh_link = 0,
-                .sh_info = 0,
-                .sh_addralign = 1,
-                .sh_entsize = 1,
-            });
-            self.debug_strtab_dirty = true;
-            self.shdr_table_dirty = true;
-        }
-
-        if (self.debug_info_section_index == null) {
-            self.debug_info_section_index = @intCast(u16, self.sections.items.len);
-
-            const file_size_hint = 200;
-            const p_align = 1;
-            const off = self.findFreeSpace(file_size_hint, p_align);
-            log.debug("found .debug_info free space 0x{x} to 0x{x}", .{
-                off,
-                off + file_size_hint,
-            });
-            try self.sections.append(self.base.allocator, .{
-                .sh_name = try self.makeString(".debug_info"),
-                .sh_type = elf.SHT_PROGBITS,
-                .sh_flags = 0,
-                .sh_addr = 0,
-                .sh_offset = off,
-                .sh_size = file_size_hint,
-                .sh_link = 0,
-                .sh_info = 0,
-                .sh_addralign = p_align,
-                .sh_entsize = 0,
-            });
-            self.shdr_table_dirty = true;
-            self.debug_info_header_dirty = true;
-        }
-
-        if (self.debug_abbrev_section_index == null) {
-            self.debug_abbrev_section_index = @intCast(u16, self.sections.items.len);
-
-            const file_size_hint = 128;
-            const p_align = 1;
-            const off = self.findFreeSpace(file_size_hint, p_align);
-            log.debug("found .debug_abbrev free space 0x{x} to 0x{x}", .{
-                off,
-                off + file_size_hint,
-            });
-            try self.sections.append(self.base.allocator, .{
-                .sh_name = try self.makeString(".debug_abbrev"),
-                .sh_type = elf.SHT_PROGBITS,
-                .sh_flags = 0,
-                .sh_addr = 0,
-                .sh_offset = off,
-                .sh_size = file_size_hint,
-                .sh_link = 0,
-                .sh_info = 0,
-                .sh_addralign = p_align,
-                .sh_entsize = 0,
-            });
-            self.shdr_table_dirty = true;
-            self.debug_abbrev_section_dirty = true;
-        }
-
-        if (self.debug_aranges_section_index == null) {
-            self.debug_aranges_section_index = @intCast(u16, self.sections.items.len);
-
-            const file_size_hint = 160;
-            const p_align = 16;
-            const off = self.findFreeSpace(file_size_hint, p_align);
-            log.debug("found .debug_aranges free space 0x{x} to 0x{x}", .{
-                off,
-                off + file_size_hint,
-            });
-            try self.sections.append(self.base.allocator, .{
-                .sh_name = try self.makeString(".debug_aranges"),
-                .sh_type = elf.SHT_PROGBITS,
-                .sh_flags = 0,
-                .sh_addr = 0,
-                .sh_offset = off,
-                .sh_size = file_size_hint,
-                .sh_link = 0,
-                .sh_info = 0,
-                .sh_addralign = p_align,
-                .sh_entsize = 0,
-            });
-            self.shdr_table_dirty = true;
-            self.debug_aranges_section_dirty = true;
-        }
-
-        if (self.debug_line_section_index == null) {
-            self.debug_line_section_index = @intCast(u16, self.sections.items.len);
-
-            const file_size_hint = 250;
-            const p_align = 1;
-            const off = self.findFreeSpace(file_size_hint, p_align);
-            log.debug("found .debug_line free space 0x{x} to 0x{x}", .{
-                off,
-                off + file_size_hint,
-            });
-            try self.sections.append(self.base.allocator, .{
-                .sh_name = try self.makeString(".debug_line"),
-                .sh_type = elf.SHT_PROGBITS,
-                .sh_flags = 0,
-                .sh_addr = 0,
-                .sh_offset = off,
-                .sh_size = file_size_hint,
-                .sh_link = 0,
-                .sh_info = 0,
-                .sh_addralign = p_align,
-                .sh_entsize = 0,
-            });
-            self.shdr_table_dirty = true;
-            self.debug_line_header_dirty = true;
-        }
-    }
+    // if (self.dwarf) |*dw| {
+    //     if (self.debug_str_section_index == null) {
+    //         self.debug_str_section_index = @intCast(u16, self.sections.slice().len);
+    //         assert(dw.strtab.items.len == 0);
+    //         try dw.strtab.append(gpa, 0);
+    //         try self.sections.append(gpa, .{
+    //             .shdr = .{
+    //                 .sh_name = try self.shstrtab.insert(gpa, ".debug_str"),
+    //                 .sh_type = elf.SHT_PROGBITS,
+    //                 .sh_flags = elf.SHF_MERGE | elf.SHF_STRINGS,
+    //                 .sh_addr = 0,
+    //                 .sh_offset = 0,
+    //                 .sh_size = 0,
+    //                 .sh_link = 0,
+    //                 .sh_info = 0,
+    //                 .sh_addralign = 1,
+    //                 .sh_entsize = 1,
+    //             },
+    //             .phdr_index = undefined,
+    //         });
+    //         self.debug_strtab_dirty = true;
+    //         self.shdr_table_dirty = true;
+    //     }
+
+    //     if (self.debug_info_section_index == null) {
+    //         self.debug_info_section_index = @intCast(u16, self.sections.slice().len);
+
+    //         const file_size_hint = 200;
+    //         const p_align = 1;
+    //         const off = self.findFreeSpace(file_size_hint, p_align);
+    //         log.debug("found .debug_info free space 0x{x} to 0x{x}", .{
+    //             off,
+    //             off + file_size_hint,
+    //         });
+    //         try self.sections.append(gpa, .{
+    //             .shdr = .{
+    //                 .sh_name = try self.shstrtab.insert(gpa, ".debug_info"),
+    //                 .sh_type = elf.SHT_PROGBITS,
+    //                 .sh_flags = 0,
+    //                 .sh_addr = 0,
+    //                 .sh_offset = off,
+    //                 .sh_size = file_size_hint,
+    //                 .sh_link = 0,
+    //                 .sh_info = 0,
+    //                 .sh_addralign = p_align,
+    //                 .sh_entsize = 0,
+    //             },
+    //             .phdr_index = undefined,
+    //         });
+    //         self.shdr_table_dirty = true;
+    //         self.debug_info_header_dirty = true;
+    //     }
+
+    //     if (self.debug_abbrev_section_index == null) {
+    //         self.debug_abbrev_section_index = @intCast(u16, self.sections.slice().len);
+
+    //         const file_size_hint = 128;
+    //         const p_align = 1;
+    //         const off = self.findFreeSpace(file_size_hint, p_align);
+    //         log.debug("found .debug_abbrev free space 0x{x} to 0x{x}", .{
+    //             off,
+    //             off + file_size_hint,
+    //         });
+    //         try self.sections.append(gpa, .{
+    //             .shdr = .{
+    //                 .sh_name = try self.shstrtab.insert(gpa, ".debug_abbrev"),
+    //                 .sh_type = elf.SHT_PROGBITS,
+    //                 .sh_flags = 0,
+    //                 .sh_addr = 0,
+    //                 .sh_offset = off,
+    //                 .sh_size = file_size_hint,
+    //                 .sh_link = 0,
+    //                 .sh_info = 0,
+    //                 .sh_addralign = p_align,
+    //                 .sh_entsize = 0,
+    //             },
+    //             .phdr_index = undefined,
+    //         });
+    //         self.shdr_table_dirty = true;
+    //         self.debug_abbrev_section_dirty = true;
+    //     }
+
+    //     if (self.debug_aranges_section_index == null) {
+    //         self.debug_aranges_section_index = @intCast(u16, self.sections.slice().len);
+
+    //         const file_size_hint = 160;
+    //         const p_align = 16;
+    //         const off = self.findFreeSpace(file_size_hint, p_align);
+    //         log.debug("found .debug_aranges free space 0x{x} to 0x{x}", .{
+    //             off,
+    //             off + file_size_hint,
+    //         });
+    //         try self.sections.append(gpa, .{
+    //             .shdr = .{
+    //                 .sh_name = try self.shstrtab.insert(gpa, ".debug_aranges"),
+    //                 .sh_type = elf.SHT_PROGBITS,
+    //                 .sh_flags = 0,
+    //                 .sh_addr = 0,
+    //                 .sh_offset = off,
+    //                 .sh_size = file_size_hint,
+    //                 .sh_link = 0,
+    //                 .sh_info = 0,
+    //                 .sh_addralign = p_align,
+    //                 .sh_entsize = 0,
+    //             },
+    //             .phdr_index = undefined,
+    //         });
+    //         self.shdr_table_dirty = true;
+    //         self.debug_aranges_section_dirty = true;
+    //     }
+
+    //     if (self.debug_line_section_index == null) {
+    //         self.debug_line_section_index = @intCast(u16, self.sections.slice().len);
+
+    //         const file_size_hint = 250;
+    //         const p_align = 1;
+    //         const off = self.findFreeSpace(file_size_hint, p_align);
+    //         log.debug("found .debug_line free space 0x{x} to 0x{x}", .{
+    //             off,
+    //             off + file_size_hint,
+    //         });
+    //         try self.sections.append(gpa, .{
+    //             .shdr = .{
+    //                 .sh_name = try self.shstrtab.insert(gpa, ".debug_line"),
+    //                 .sh_type = elf.SHT_PROGBITS,
+    //                 .sh_flags = 0,
+    //                 .sh_addr = 0,
+    //                 .sh_offset = off,
+    //                 .sh_size = file_size_hint,
+    //                 .sh_link = 0,
+    //                 .sh_info = 0,
+    //                 .sh_addralign = p_align,
+    //                 .sh_entsize = 0,
+    //             },
+    //             .phdr_index = undefined,
+    //         });
+    //         self.shdr_table_dirty = true;
+    //         self.debug_line_header_dirty = true;
+    //     }
+    // }
 
     const shsize: u64 = switch (self.ptr_width) {
         .p32 => @sizeOf(elf.Elf32_Shdr),
@@ -843,7 +835,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         .p64 => @alignOf(elf.Elf64_Shdr),
     };
     if (self.shdr_table_offset == null) {
-        self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign);
+        self.shdr_table_offset = self.findFreeSpace(self.sections.slice().len * shsize, shalign);
         self.shdr_table_dirty = true;
     }
 
@@ -874,7 +866,7 @@ pub fn populateMissingMetadata(self: *Elf) !void {
         // offset + it's filesize.
         var max_file_offset: u64 = 0;
 
-        for (self.sections.items) |shdr| {
+        for (self.sections.items(.shdr)) |shdr| {
             if (shdr.sh_offset + shdr.sh_size > max_file_offset) {
                 max_file_offset = shdr.sh_offset + shdr.sh_size;
             }
@@ -884,15 +876,18 @@ pub fn populateMissingMetadata(self: *Elf) !void {
     }
 }
 
-fn growAllocSection(self: *Elf, shdr_index: u16, phdr_index: u16, needed_size: u64) !void {
+fn growAllocSection(self: *Elf, shdr_index: u16, needed_size: u64) !void {
     // TODO Also detect virtual address collisions.
-    const shdr = &self.sections.items[shdr_index];
+    const shdr = &self.sections.items(.shdr)[shdr_index];
+    const phdr_index = self.sections.items(.phdr_index)[shdr_index];
     const phdr = &self.program_headers.items[phdr_index];
+    const maybe_last_atom_index = self.sections.items(.last_atom_index)[shdr_index];
 
     if (needed_size > self.allocatedSize(shdr.sh_offset)) {
         // Must move the entire section.
         const new_offset = self.findFreeSpace(needed_size, self.page_size);
-        const existing_size = if (self.atoms.get(phdr_index)) |last| blk: {
+        const existing_size = if (maybe_last_atom_index) |last_atom_index| blk: {
+            const last = self.getAtom(last_atom_index);
             const sym = last.getSymbol(self);
             break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr;
         } else if (shdr_index == self.got_section_index.?) blk: {
@@ -900,8 +895,8 @@ fn growAllocSection(self: *Elf, shdr_index: u16, phdr_index: u16, needed_size: u
         } else 0;
         shdr.sh_size = 0;
 
-        log.debug("new '{s}' file offset 0x{x} to 0x{x}", .{
-            self.getString(shdr.sh_name),
+        log.debug("new '{?s}' file offset 0x{x} to 0x{x}", .{
+            self.shstrtab.get(shdr.sh_name),
             new_offset,
             new_offset + existing_size,
         });
@@ -927,7 +922,7 @@ pub fn growNonAllocSection(
     min_alignment: u32,
     requires_file_copy: bool,
 ) !void {
-    const shdr = &self.sections.items[shdr_index];
+    const shdr = &self.sections.items(.shdr)[shdr_index];
 
     if (needed_size > self.allocatedSize(shdr.sh_offset)) {
         const existing_size = if (self.symtab_section_index.? == shdr_index) blk: {
@@ -940,7 +935,7 @@ pub fn growNonAllocSection(
         shdr.sh_size = 0;
         // Move all the symbols to a new file location.
         const new_offset = self.findFreeSpace(needed_size, min_alignment);
-        log.debug("moving '{s}' from 0x{x} to 0x{x}", .{ self.getString(shdr.sh_name), shdr.sh_offset, new_offset });
+        log.debug("moving '{?s}' from 0x{x} to 0x{x}", .{ self.shstrtab.get(shdr.sh_name), shdr.sh_offset, new_offset });
 
         if (requires_file_copy) {
             const amt = try self.base.file.?.copyRangeAll(
@@ -961,25 +956,26 @@ pub fn growNonAllocSection(
 }
 
 pub fn markDirty(self: *Elf, shdr_index: u16, phdr_index: ?u16) void {
+    _ = shdr_index;
     self.shdr_table_dirty = true; // TODO look into only writing one section
 
     if (phdr_index) |_| {
         self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
     }
 
-    if (self.dwarf) |_| {
-        if (self.debug_info_section_index.? == shdr_index) {
-            self.debug_info_header_dirty = true;
-        } else if (self.debug_line_section_index.? == shdr_index) {
-            self.debug_line_header_dirty = true;
-        } else if (self.debug_abbrev_section_index.? == shdr_index) {
-            self.debug_abbrev_section_dirty = true;
-        } else if (self.debug_str_section_index.? == shdr_index) {
-            self.debug_strtab_dirty = true;
-        } else if (self.debug_aranges_section_index.? == shdr_index) {
-            self.debug_aranges_section_dirty = true;
-        }
-    }
+    // if (self.dwarf) |_| {
+    //     if (self.debug_info_section_index.? == shdr_index) {
+    //         self.debug_info_header_dirty = true;
+    //     } else if (self.debug_line_section_index.? == shdr_index) {
+    //         self.debug_line_header_dirty = true;
+    //     } else if (self.debug_abbrev_section_index.? == shdr_index) {
+    //         self.debug_abbrev_section_dirty = true;
+    //     } else if (self.debug_str_section_index.? == shdr_index) {
+    //         self.debug_strtab_dirty = true;
+    //     } else if (self.debug_aranges_section_index.? == shdr_index) {
+    //         self.debug_aranges_section_dirty = true;
+    //     }
+    // }
 }
 
 pub fn flush(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
@@ -1011,6 +1007,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
         }
     }
 
+    const gpa = self.base.allocator;
     var sub_prog_node = prog_node.start("ELF Flush", 0);
     sub_prog_node.activate();
     defer sub_prog_node.end();
@@ -1018,23 +1015,25 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     // TODO This linker code currently assumes there is only 1 compilation unit and it
     // corresponds to the Zig source code.
     const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
+    _ = module;
 
     const target_endian = self.base.options.target.cpu.arch.endian();
     const foreign_endian = target_endian != builtin.cpu.arch.endian();
 
-    if (self.dwarf) |*dw| {
-        try dw.flushModule(module);
-    }
+    // if (self.dwarf) |*dw| {
+    //     try dw.flushModule(module);
+    // }
 
     {
         var it = self.relocs.iterator();
         while (it.next()) |entry| {
-            const atom = entry.key_ptr.*;
+            const atom_index = entry.key_ptr.*;
             const relocs = entry.value_ptr.*;
+            const atom = self.getAtom(atom_index);
             const source_sym = atom.getSymbol(self);
-            const source_shdr = self.sections.items[source_sym.st_shndx];
+            const source_shdr = self.sections.items(.shdr)[source_sym.st_shndx];
 
-            log.debug("relocating '{s}'", .{self.getString(source_sym.st_name)});
+            log.debug("relocating '{?s}'", .{self.shstrtab.get(source_sym.st_name)});
 
             for (relocs.items) |*reloc| {
                 const target_sym = self.local_symbols.items[reloc.target];
@@ -1045,10 +1044,10 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
                 const section_offset = (source_sym.st_value + reloc.offset) - source_shdr.sh_addr;
                 const file_offset = source_shdr.sh_offset + section_offset;
 
-                log.debug("  ({x}: [() => 0x{x}] ({s}))", .{
+                log.debug("  ({x}: [() => 0x{x}] ({?s}))", .{
                     reloc.offset,
                     target_vaddr,
-                    self.getString(target_sym.st_name),
+                    self.shstrtab.get(target_sym.st_name),
                 });
 
                 switch (self.ptr_width) {
@@ -1069,43 +1068,43 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
         self.logSymtab();
     }
 
-    if (self.dwarf) |*dw| {
-        if (self.debug_abbrev_section_dirty) {
-            try dw.writeDbgAbbrev();
-            if (!self.shdr_table_dirty) {
-                // Then it won't get written with the others and we need to do it.
-                try self.writeSectHeader(self.debug_abbrev_section_index.?);
-            }
-            self.debug_abbrev_section_dirty = false;
-        }
-
-        if (self.debug_info_header_dirty) {
-            // Currently only one compilation unit is supported, so the address range is simply
-            // identical to the main program header virtual address and memory size.
-            const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
-            const low_pc = text_phdr.p_vaddr;
-            const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz;
-            try dw.writeDbgInfoHeader(module, low_pc, high_pc);
-            self.debug_info_header_dirty = false;
-        }
-
-        if (self.debug_aranges_section_dirty) {
-            // Currently only one compilation unit is supported, so the address range is simply
-            // identical to the main program header virtual address and memory size.
-            const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
-            try dw.writeDbgAranges(text_phdr.p_vaddr, text_phdr.p_memsz);
-            if (!self.shdr_table_dirty) {
-                // Then it won't get written with the others and we need to do it.
-                try self.writeSectHeader(self.debug_aranges_section_index.?);
-            }
-            self.debug_aranges_section_dirty = false;
-        }
-
-        if (self.debug_line_header_dirty) {
-            try dw.writeDbgLineHeader();
-            self.debug_line_header_dirty = false;
-        }
-    }
+    // if (self.dwarf) |*dw| {
+    //     if (self.debug_abbrev_section_dirty) {
+    //         try dw.writeDbgAbbrev();
+    //         if (!self.shdr_table_dirty) {
+    //             // Then it won't get written with the others and we need to do it.
+    //             try self.writeSectHeader(self.debug_abbrev_section_index.?);
+    //         }
+    //         self.debug_abbrev_section_dirty = false;
+    //     }
+
+    //     if (self.debug_info_header_dirty) {
+    //         // Currently only one compilation unit is supported, so the address range is simply
+    //         // identical to the main program header virtual address and memory size.
+    //         const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
+    //         const low_pc = text_phdr.p_vaddr;
+    //         const high_pc = text_phdr.p_vaddr + text_phdr.p_memsz;
+    //         try dw.writeDbgInfoHeader(module, low_pc, high_pc);
+    //         self.debug_info_header_dirty = false;
+    //     }
+
+    //     if (self.debug_aranges_section_dirty) {
+    //         // Currently only one compilation unit is supported, so the address range is simply
+    //         // identical to the main program header virtual address and memory size.
+    //         const text_phdr = &self.program_headers.items[self.phdr_load_re_index.?];
+    //         try dw.writeDbgAranges(text_phdr.p_vaddr, text_phdr.p_memsz);
+    //         if (!self.shdr_table_dirty) {
+    //             // Then it won't get written with the others and we need to do it.
+    //             try self.writeSectHeader(self.debug_aranges_section_index.?);
+    //         }
+    //         self.debug_aranges_section_dirty = false;
+    //     }
+
+    //     if (self.debug_line_header_dirty) {
+    //         try dw.writeDbgLineHeader();
+    //         self.debug_line_header_dirty = false;
+    //     }
+    // }
 
     if (self.phdr_table_dirty) {
         const phsize: u64 = switch (self.ptr_width) {
@@ -1126,8 +1125,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
         switch (self.ptr_width) {
             .p32 => {
-                const buf = try self.base.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len);
-                defer self.base.allocator.free(buf);
+                const buf = try gpa.alloc(elf.Elf32_Phdr, self.program_headers.items.len);
+                defer gpa.free(buf);
 
                 for (buf) |*phdr, i| {
                     phdr.* = progHeaderTo32(self.program_headers.items[i]);
@@ -1138,8 +1137,8 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
                 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
             },
             .p64 => {
-                const buf = try self.base.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len);
-                defer self.base.allocator.free(buf);
+                const buf = try gpa.alloc(elf.Elf64_Phdr, self.program_headers.items.len);
+                defer gpa.free(buf);
 
                 for (buf) |*phdr, i| {
                     phdr.* = self.program_headers.items[i];
@@ -1155,23 +1154,23 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
     {
         const shdr_index = self.shstrtab_index.?;
-        if (self.shstrtab_dirty or self.shstrtab.items.len != self.sections.items[shdr_index].sh_size) {
-            try self.growNonAllocSection(shdr_index, self.shstrtab.items.len, 1, false);
-            const shstrtab_sect = self.sections.items[shdr_index];
-            try self.base.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset);
+        if (self.shstrtab_dirty or self.shstrtab.buffer.items.len != self.sections.items(.shdr)[shdr_index].sh_size) {
+            try self.growNonAllocSection(shdr_index, self.shstrtab.buffer.items.len, 1, false);
+            const shstrtab_sect = self.sections.items(.shdr)[shdr_index];
+            try self.base.file.?.pwriteAll(self.shstrtab.buffer.items, shstrtab_sect.sh_offset);
             self.shstrtab_dirty = false;
         }
     }
 
-    if (self.dwarf) |dwarf| {
-        const shdr_index = self.debug_str_section_index.?;
-        if (self.debug_strtab_dirty or dwarf.strtab.items.len != self.sections.items[shdr_index].sh_size) {
-            try self.growNonAllocSection(shdr_index, dwarf.strtab.items.len, 1, false);
-            const debug_strtab_sect = self.sections.items[shdr_index];
-            try self.base.file.?.pwriteAll(dwarf.strtab.items, debug_strtab_sect.sh_offset);
-            self.debug_strtab_dirty = false;
-        }
-    }
+    // if (self.dwarf) |dwarf| {
+    //     const shdr_index = self.debug_str_section_index.?;
+    //     if (self.debug_strtab_dirty or dwarf.strtab.items.len != self.sections.items(.shdr)[shdr_index].sh_size) {
+    //         try self.growNonAllocSection(shdr_index, dwarf.strtab.items.len, 1, false);
+    //         const debug_strtab_sect = self.sections.items(.shdr)[shdr_index];
+    //         try self.base.file.?.pwriteAll(dwarf.strtab.items, debug_strtab_sect.sh_offset);
+    //         self.debug_strtab_dirty = false;
+    //     }
+    // }
 
     if (self.shdr_table_dirty) {
         const shsize: u64 = switch (self.ptr_width) {
@@ -1183,7 +1182,7 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
             .p64 => @alignOf(elf.Elf64_Shdr),
         };
         const allocated_size = self.allocatedSize(self.shdr_table_offset.?);
-        const needed_size = self.sections.items.len * shsize;
+        const needed_size = self.sections.slice().len * shsize;
 
         if (needed_size > allocated_size) {
             self.shdr_table_offset = null; // free the space
@@ -1192,12 +1191,13 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
 
         switch (self.ptr_width) {
             .p32 => {
-                const buf = try self.base.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len);
-                defer self.base.allocator.free(buf);
+                const slice = self.sections.slice();
+                const buf = try gpa.alloc(elf.Elf32_Shdr, slice.len);
+                defer gpa.free(buf);
 
                 for (buf) |*shdr, i| {
-                    shdr.* = sectHeaderTo32(self.sections.items[i]);
-                    log.debug("writing section {s}: {}", .{ self.getString(shdr.sh_name), shdr.* });
+                    shdr.* = sectHeaderTo32(slice.items(.shdr)[i]);
+                    log.debug("writing section {?s}: {}", .{ self.shstrtab.get(shdr.sh_name), shdr.* });
                     if (foreign_endian) {
                         mem.byteSwapAllFields(elf.Elf32_Shdr, shdr);
                     }
@@ -1205,12 +1205,13 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
                 try self.base.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);
             },
             .p64 => {
-                const buf = try self.base.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len);
-                defer self.base.allocator.free(buf);
+                const slice = self.sections.slice();
+                const buf = try gpa.alloc(elf.Elf64_Shdr, slice.len);
+                defer gpa.free(buf);
 
                 for (buf) |*shdr, i| {
-                    shdr.* = self.sections.items[i];
-                    log.debug("writing section {s}: {}", .{ self.getString(shdr.sh_name), shdr.* });
+                    shdr.* = slice.items(.shdr)[i];
+                    log.debug("writing section {?s}: {}", .{ self.shstrtab.get(shdr.sh_name), shdr.* });
                     if (foreign_endian) {
                         mem.byteSwapAllFields(elf.Elf64_Shdr, shdr);
                     }
@@ -2021,7 +2022,7 @@ fn writeElfHeader(self: *Elf) !void {
     mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian);
     index += 2;
 
-    const e_shnum = @intCast(u16, self.sections.items.len);
+    const e_shnum = @intCast(u16, self.sections.slice().len);
     mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian);
     index += 2;
 
@@ -2033,124 +2034,150 @@ fn writeElfHeader(self: *Elf) !void {
     try self.base.file.?.pwriteAll(hdr_buf[0..index], 0);
 }
 
-fn freeTextBlock(self: *Elf, text_block: *TextBlock, phdr_index: u16) void {
-    const local_sym = text_block.getSymbol(self);
-    const name_str_index = local_sym.st_name;
-    const name = self.getString(name_str_index);
-    log.debug("freeTextBlock {*} ({s})", .{ text_block, name });
+fn freeAtom(self: *Elf, atom_index: Atom.Index) void {
+    const atom = self.getAtom(atom_index);
+    log.debug("freeAtom {d} ({s})", .{ atom_index, atom.getName(self) });
 
-    self.freeRelocationsForTextBlock(text_block);
+    Atom.freeRelocations(self, atom_index);
 
-    const free_list = self.atom_free_lists.getPtr(phdr_index).?;
+    const gpa = self.base.allocator;
+    const shndx = atom.getSymbol(self).st_shndx;
+    const free_list = &self.sections.items(.free_list)[shndx];
     var already_have_free_list_node = false;
     {
         var i: usize = 0;
         // TODO turn free_list into a hash map
         while (i < free_list.items.len) {
-            if (free_list.items[i] == text_block) {
+            if (free_list.items[i] == atom_index) {
                 _ = free_list.swapRemove(i);
                 continue;
             }
-            if (free_list.items[i] == text_block.prev) {
+            if (free_list.items[i] == atom.prev_index) {
                 already_have_free_list_node = true;
             }
             i += 1;
         }
     }
 
-    if (self.atoms.getPtr(phdr_index)) |last_block| {
-        if (last_block.* == text_block) {
-            if (text_block.prev) |prev| {
+    const maybe_last_atom_index = &self.sections.items(.last_atom_index)[shndx];
+    if (maybe_last_atom_index.*) |last_atom_index| {
+        if (last_atom_index == atom_index) {
+            if (atom.prev_index) |prev_index| {
                 // TODO shrink the section size here
-                last_block.* = prev;
+                maybe_last_atom_index.* = prev_index;
             } else {
-                _ = self.atoms.fetchRemove(phdr_index);
+                maybe_last_atom_index.* = null;
             }
         }
     }
 
-    if (text_block.prev) |prev| {
-        prev.next = text_block.next;
+    if (atom.prev_index) |prev_index| {
+        const prev = self.getAtomPtr(prev_index);
+        prev.next_index = atom.next_index;
 
-        if (!already_have_free_list_node and prev.freeListEligible(self)) {
+        if (!already_have_free_list_node and prev.*.freeListEligible(self)) {
             // The free list is heuristics, it doesn't have to be perfect, so we can
             // ignore the OOM here.
-            free_list.append(self.base.allocator, prev) catch {};
+            free_list.append(gpa, prev_index) catch {};
         }
     } else {
-        text_block.prev = null;
+        self.getAtomPtr(atom_index).prev_index = null;
     }
 
-    if (text_block.next) |next| {
-        next.prev = text_block.prev;
+    if (atom.next_index) |next_index| {
+        self.getAtomPtr(next_index).prev_index = atom.prev_index;
     } else {
-        text_block.next = null;
+        self.getAtomPtr(atom_index).next_index = null;
     }
 
     // Appending to free lists is allowed to fail because the free lists are heuristics based anyway.
-    const local_sym_index = text_block.getSymbolIndex().?;
-    self.local_symbol_free_list.append(self.base.allocator, local_sym_index) catch {};
+    const local_sym_index = atom.getSymbolIndex().?;
+
+    self.local_symbol_free_list.append(gpa, local_sym_index) catch {};
     self.local_symbols.items[local_sym_index].st_info = 0;
+    self.local_symbols.items[local_sym_index].st_shndx = 0;
     _ = self.atom_by_index_table.remove(local_sym_index);
-    text_block.local_sym_index = 0;
+    self.getAtomPtr(atom_index).local_sym_index = 0;
 
-    self.offset_table_free_list.append(self.base.allocator, text_block.offset_table_index) catch {};
+    self.offset_table_free_list.append(self.base.allocator, atom.offset_table_index) catch {};
 
-    if (self.dwarf) |*dw| {
-        dw.freeAtom(&text_block.dbg_info_atom);
-    }
+    // if (self.dwarf) |*dw| {
+    //     dw.freeAtom(&atom.dbg_info_atom);
+    // }
 }
 
-fn shrinkTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, phdr_index: u16) void {
+fn shrinkAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64) void {
     _ = self;
-    _ = text_block;
+    _ = atom_index;
     _ = new_block_size;
-    _ = phdr_index;
 }
 
-fn growTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64, phdr_index: u16) !u64 {
-    const sym = text_block.getSymbol(self);
+fn growAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64, alignment: u64) !u64 {
+    const atom = self.getAtom(atom_index);
+    const sym = atom.getSymbol(self);
     const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value;
-    const need_realloc = !align_ok or new_block_size > text_block.capacity(self);
+    const need_realloc = !align_ok or new_block_size > atom.capacity(self);
     if (!need_realloc) return sym.st_value;
-    return self.allocateTextBlock(text_block, new_block_size, alignment, phdr_index);
+    return self.allocateAtom(atom_index, new_block_size, alignment);
+}
+
+pub fn createAtom(self: *Elf) !Atom.Index {
+    const gpa = self.base.allocator;
+    const atom_index = @intCast(Atom.Index, self.atoms.items.len);
+    const atom = try self.atoms.addOne(gpa);
+    const local_sym_index = try self.allocateLocalSymbol();
+    const offset_table_index = try self.allocateGotOffset();
+    try self.atom_by_index_table.putNoClobber(gpa, local_sym_index, atom_index);
+    atom.* = .{
+        .local_sym_index = local_sym_index,
+        .offset_table_index = offset_table_index,
+        .prev_index = null,
+        .next_index = null,
+        .dbg_info_atom = undefined,
+    };
+    log.debug("creating ATOM(%{d}) at index {d}", .{ local_sym_index, atom_index });
+    return atom_index;
 }
 
-fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64, phdr_index: u16) !u64 {
-    const shdr_index = self.phdr_shdr_table.get(phdr_index).?;
+fn allocateAtom(self: *Elf, atom_index: Atom.Index, new_block_size: u64, alignment: u64) !u64 {
+    const atom = self.getAtom(atom_index);
+    const sym = atom.getSymbol(self);
+    const phdr_index = self.sections.items(.phdr_index)[sym.st_shndx];
     const phdr = &self.program_headers.items[phdr_index];
-    const shdr = &self.sections.items[shdr_index];
-    const new_block_ideal_capacity = padToIdeal(new_block_size);
+    const shdr = &self.sections.items(.shdr)[sym.st_shndx];
+    const free_list = &self.sections.items(.free_list)[sym.st_shndx];
+    const maybe_last_atom_index = &self.sections.items(.last_atom_index)[sym.st_shndx];
+    const new_atom_ideal_capacity = padToIdeal(new_block_size);
 
-    // We use these to indicate our intention to update metadata, placing the new block,
+    // We use these to indicate our intention to update metadata, placing the new atom,
     // and possibly removing a free list node.
     // It would be simpler to do it inside the for loop below, but that would cause a
     // problem if an error was returned later in the function. So this action
     // is actually carried out at the end of the function, when errors are no longer possible.
-    var block_placement: ?*TextBlock = null;
+    var atom_placement: ?Atom.Index = null;
     var free_list_removal: ?usize = null;
-    var free_list = self.atom_free_lists.get(phdr_index).?;
 
     // First we look for an appropriately sized free list node.
     // The list is unordered. We'll just take the first thing that works.
     const vaddr = blk: {
         var i: usize = 0;
         while (i < free_list.items.len) {
-            const big_block = free_list.items[i];
-            // We now have a pointer to a live text block that has too much capacity.
-            // Is it enough that we could fit this new text block?
-            const sym = big_block.getSymbol(self);
-            const capacity = big_block.capacity(self);
+            const big_atom_index = free_list.items[i];
+            const big_atom = self.getAtom(big_atom_index);
+            // We now have a pointer to a live atom that has too much capacity.
+            // Is it enough that we could fit this new atom?
+            const big_atom_sym = big_atom.getSymbol(self);
+            const capacity = big_atom.capacity(self);
             const ideal_capacity = padToIdeal(capacity);
-            const ideal_capacity_end_vaddr = std.math.add(u64, sym.st_value, ideal_capacity) catch ideal_capacity;
-            const capacity_end_vaddr = sym.st_value + capacity;
-            const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity;
+            const ideal_capacity_end_vaddr = std.math.add(u64, big_atom_sym.st_value, ideal_capacity) catch ideal_capacity;
+            const capacity_end_vaddr = big_atom_sym.st_value + capacity;
+            const new_start_vaddr_unaligned = capacity_end_vaddr - new_atom_ideal_capacity;
             const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment);
             if (new_start_vaddr < ideal_capacity_end_vaddr) {
                 // Additional bookkeeping here to notice if this free list node
                 // should be deleted because the block that it points to has grown to take up
                 // more of the extra capacity.
-                if (!big_block.freeListEligible(self)) {
+                if (!big_atom.freeListEligible(self)) {
                     _ = free_list.swapRemove(i);
                 } else {
                     i += 1;
@@ -2164,60 +2191,69 @@ fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, al
             const keep_free_list_node = remaining_capacity >= min_text_capacity;
 
             // Set up the metadata to be updated, after errors are no longer possible.
-            block_placement = big_block;
+            atom_placement = big_atom_index;
             if (!keep_free_list_node) {
                 free_list_removal = i;
             }
             break :blk new_start_vaddr;
-        } else if (self.atoms.get(phdr_index)) |last| {
-            const sym = last.getSymbol(self);
-            const ideal_capacity = padToIdeal(sym.st_size);
-            const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
+        } else if (maybe_last_atom_index.*) |last_index| {
+            const last = self.getAtom(last_index);
+            const last_sym = last.getSymbol(self);
+            const ideal_capacity = padToIdeal(last_sym.st_size);
+            const ideal_capacity_end_vaddr = last_sym.st_value + ideal_capacity;
             const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment);
             // Set up the metadata to be updated, after errors are no longer possible.
-            block_placement = last;
+            atom_placement = last_index;
             break :blk new_start_vaddr;
         } else {
             break :blk phdr.p_vaddr;
         }
     };
 
-    const expand_text_section = block_placement == null or block_placement.?.next == null;
-    if (expand_text_section) {
+    const expand_section = if (atom_placement) |placement_index|
+        self.getAtom(placement_index).next_index == null
+    else
+        true;
+    if (expand_section) {
         const needed_size = (vaddr + new_block_size) - phdr.p_vaddr;
-        try self.growAllocSection(shdr_index, phdr_index, needed_size);
-        _ = try self.atoms.put(self.base.allocator, phdr_index, text_block);
-
-        if (self.dwarf) |_| {
-            // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
-            // range of the compilation unit. When we expand the text section, this range changes,
-            // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
-            self.debug_info_header_dirty = true;
-            // This becomes dirty for the same reason. We could potentially make this more
-            // fine-grained with the addition of support for more compilation units. It is planned to
-            // model each package as a different compilation unit.
-            self.debug_aranges_section_dirty = true;
-        }
+        try self.growAllocSection(sym.st_shndx, needed_size);
+        maybe_last_atom_index.* = atom_index;
+
+        // if (self.dwarf) |_| {
+        //     // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address
+        //     // range of the compilation unit. When we expand the text section, this range changes,
+        //     // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty.
+        //     self.debug_info_header_dirty = true;
+        //     // This becomes dirty for the same reason. We could potentially make this more
+        //     // fine-grained with the addition of support for more compilation units. It is planned to
+        //     // model each package as a different compilation unit.
+        //     self.debug_aranges_section_dirty = true;
+        // }
     }
     shdr.sh_addralign = math.max(shdr.sh_addralign, alignment);
 
-    // This function can also reallocate a text block.
+    // This function can also reallocate an atom.
     // In this case we need to "unplug" it from its previous location before
     // plugging it in to its new location.
-    if (text_block.prev) |prev| {
-        prev.next = text_block.next;
+    if (atom.prev_index) |prev_index| {
+        const prev = self.getAtomPtr(prev_index);
+        prev.next_index = atom.next_index;
     }
-    if (text_block.next) |next| {
-        next.prev = text_block.prev;
+    if (atom.next_index) |next_index| {
+        const next = self.getAtomPtr(next_index);
+        next.prev_index = atom.prev_index;
     }
 
-    if (block_placement) |big_block| {
-        text_block.prev = big_block;
-        text_block.next = big_block.next;
-        big_block.next = text_block;
+    if (atom_placement) |big_atom_index| {
+        const big_atom = self.getAtomPtr(big_atom_index);
+        const atom_ptr = self.getAtomPtr(atom_index);
+        atom_ptr.prev_index = big_atom_index;
+        atom_ptr.next_index = big_atom.next_index;
+        big_atom.next_index = atom_index;
     } else {
-        text_block.prev = null;
-        text_block.next = null;
+        const atom_ptr = self.getAtomPtr(atom_index);
+        atom_ptr.prev_index = null;
+        atom_ptr.next_index = null;
     }
     if (free_list_removal) |i| {
         _ = free_list.swapRemove(i);
@@ -2272,15 +2308,10 @@ pub fn allocateGotOffset(self: *Elf) !u32 {
     return index;
 }
 
-fn freeRelocationsForTextBlock(self: *Elf, text_block: *TextBlock) void {
-    var removed_relocs = self.relocs.fetchRemove(text_block);
-    if (removed_relocs) |*relocs| relocs.value.deinit(self.base.allocator);
-}
-
 fn freeUnnamedConsts(self: *Elf, decl_index: Module.Decl.Index) void {
     const unnamed_consts = self.unnamed_const_atoms.getPtr(decl_index) orelse return;
     for (unnamed_consts.items) |atom| {
-        self.freeTextBlock(atom, self.phdr_load_ro_index.?);
+        self.freeAtom(atom);
     }
     unnamed_consts.clearAndFree(self.base.allocator);
 }
@@ -2295,43 +2326,57 @@ pub fn freeDecl(self: *Elf, decl_index: Module.Decl.Index) void {
 
     log.debug("freeDecl {*}", .{decl});
 
-    if (self.decls.fetchRemove(decl_index)) |kv| {
-        if (kv.value) |index| {
-            self.freeTextBlock(&decl.link.elf, index);
-            self.freeUnnamedConsts(decl_index);
-        }
+    if (self.decls.fetchRemove(decl_index)) |const_kv| {
+        var kv = const_kv;
+        self.freeAtom(kv.value.atom);
+        self.freeUnnamedConsts(decl_index);
+        kv.value.exports.deinit(self.base.allocator);
     }
 
-    if (self.dwarf) |*dw| {
-        dw.freeDecl(decl);
+    // if (self.dwarf) |*dw| {
+    //     dw.freeDecl(decl);
+    // }
+}
+
+pub fn getOrCreateAtomForDecl(self: *Elf, decl_index: Module.Decl.Index) !Atom.Index {
+    const gop = try self.decls.getOrPut(self.base.allocator, decl_index);
+    if (!gop.found_existing) {
+        gop.value_ptr.* = .{
+            .atom = try self.createAtom(),
+            .shdr = self.getDeclShdrIndex(decl_index),
+            .exports = .{},
+        };
     }
+    return gop.value_ptr.atom;
 }
 
-fn getDeclPhdrIndex(self: *Elf, decl: *Module.Decl) !u16 {
+fn getDeclShdrIndex(self: *Elf, decl_index: Module.Decl.Index) u16 {
+    const decl = self.base.options.module.?.declPtr(decl_index);
     const ty = decl.ty;
     const zig_ty = ty.zigTypeTag();
     const val = decl.val;
-    const phdr_index: u16 = blk: {
+    const shdr_index: u16 = blk: {
         if (val.isUndefDeep()) {
             // TODO in release-fast and release-small, we should put undef in .bss
-            break :blk self.phdr_load_rw_index.?;
+            break :blk self.data_section_index.?;
         }
 
         switch (zig_ty) {
             // TODO: what if this is a function pointer?
-            .Fn => break :blk self.phdr_load_re_index.?,
+            .Fn => break :blk self.text_section_index.?,
             else => {
                 if (val.castTag(.variable)) |_| {
-                    break :blk self.phdr_load_rw_index.?;
+                    break :blk self.data_section_index.?;
                 }
-                break :blk self.phdr_load_ro_index.?;
+                break :blk self.rodata_section_index.?;
             },
         }
     };
-    return phdr_index;
+    return shdr_index;
 }
 
 fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, stt_bits: u8) !*elf.Elf64_Sym {
+    const gpa = self.base.allocator;
     const mod = self.base.options.module.?;
     const decl = mod.declPtr(decl_index);
 
@@ -2341,60 +2386,65 @@ fn updateDeclCode(self: *Elf, decl_index: Module.Decl.Index, code: []const u8, s
     log.debug("updateDeclCode {s}{*}", .{ decl_name, decl });
     const required_alignment = decl.getAlignment(self.base.options.target);
 
-    const decl_ptr = self.decls.getPtr(decl_index).?;
-    if (decl_ptr.* == null) {
-        decl_ptr.* = try self.getDeclPhdrIndex(decl);
-    }
-    const phdr_index = decl_ptr.*.?;
-    const shdr_index = self.phdr_shdr_table.get(phdr_index).?;
+    const decl_metadata = self.decls.get(decl_index).?;
+    const atom_index = decl_metadata.atom;
+    const atom = self.getAtom(atom_index);
+
+    const shdr_index = decl_metadata.shdr;
+    if (atom.getSymbol(self).st_size != 0) {
+        const local_sym = atom.getSymbolPtr(self);
+        local_sym.st_name = try self.shstrtab.insert(gpa, decl_name);
+        local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
+        local_sym.st_other = 0;
+        local_sym.st_shndx = shdr_index;
 
-    const local_sym = decl.link.elf.getSymbolPtr(self);
-    if (local_sym.st_size != 0) {
-        const capacity = decl.link.elf.capacity(self);
+        const capacity = atom.capacity(self);
         const need_realloc = code.len > capacity or
             !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);
+
         if (need_realloc) {
-            const vaddr = try self.growTextBlock(&decl.link.elf, code.len, required_alignment, phdr_index);
+            const vaddr = try self.growAtom(atom_index, code.len, required_alignment);
             log.debug("growing {s} from 0x{x} to 0x{x}", .{ decl_name, local_sym.st_value, vaddr });
             if (vaddr != local_sym.st_value) {
                 local_sym.st_value = vaddr;
 
                 log.debug("  (writing new offset table entry)", .{});
-                self.offset_table.items[decl.link.elf.offset_table_index] = vaddr;
-                try self.writeOffsetTableEntry(decl.link.elf.offset_table_index);
+                self.offset_table.items[atom.offset_table_index] = vaddr;
+                try self.writeOffsetTableEntry(atom.offset_table_index);
             }
         } else if (code.len < local_sym.st_size) {
-            self.shrinkTextBlock(&decl.link.elf, code.len, phdr_index);
+            self.shrinkAtom(atom_index, code.len);
         }
         local_sym.st_size = code.len;
-        local_sym.st_name = try self.updateString(local_sym.st_name, decl_name);
-        local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
-        local_sym.st_other = 0;
-        local_sym.st_shndx = shdr_index;
+
         // TODO this write could be avoided if no fields of the symbol were changed.
-        try self.writeSymbol(decl.link.elf.getSymbolIndex().?);
+        try self.writeSymbol(atom.getSymbolIndex().?);
     } else {
-        const name_str_index = try self.makeString(decl_name);
-        const vaddr = try self.allocateTextBlock(&decl.link.elf, code.len, required_alignment, phdr_index);
-        errdefer self.freeTextBlock(&decl.link.elf, phdr_index);
-        log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, vaddr });
-
+        const local_sym = atom.getSymbolPtr(self);
         local_sym.* = .{
-            .st_name = name_str_index,
+            .st_name = try self.shstrtab.insert(gpa, decl_name),
             .st_info = (elf.STB_LOCAL << 4) | stt_bits,
             .st_other = 0,
             .st_shndx = shdr_index,
-            .st_value = vaddr,
-            .st_size = code.len,
+            .st_value = 0,
+            .st_size = 0,
         };
-        self.offset_table.items[decl.link.elf.offset_table_index] = vaddr;
+        const vaddr = try self.allocateAtom(atom_index, code.len, required_alignment);
+        errdefer self.freeAtom(atom_index);
+        log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, vaddr });
 
-        try self.writeSymbol(decl.link.elf.getSymbolIndex().?);
-        try self.writeOffsetTableEntry(decl.link.elf.offset_table_index);
+        self.offset_table.items[atom.offset_table_index] = vaddr;
+        local_sym.st_value = vaddr;
+        local_sym.st_size = code.len;
+
+        try self.writeSymbol(atom.getSymbolIndex().?);
+        try self.writeOffsetTableEntry(atom.offset_table_index);
     }
 
+    const local_sym = atom.getSymbolPtr(self);
+    const phdr_index = self.sections.items(.phdr_index)[shdr_index];
     const section_offset = local_sym.st_value - self.program_headers.items[phdr_index].p_vaddr;
-    const file_offset = self.sections.items[shdr_index].sh_offset + section_offset;
+    const file_offset = self.sections.items(.shdr)[shdr_index].sh_offset + section_offset;
     try self.base.file.?.pwriteAll(code, file_offset);
 
     return local_sym;
@@ -2413,28 +2463,23 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven
 
     const decl_index = func.owner_decl;
     const decl = module.declPtr(decl_index);
-    const atom = &decl.link.elf;
-    try atom.ensureInitialized(self);
-    const gop = try self.decls.getOrPut(self.base.allocator, decl_index);
-    if (gop.found_existing) {
-        self.freeUnnamedConsts(decl_index);
-        self.freeRelocationsForTextBlock(atom);
-    } else {
-        gop.value_ptr.* = null;
-    }
+
+    const atom_index = try self.getOrCreateAtomForDecl(decl_index);
+    self.freeUnnamedConsts(decl_index);
+    Atom.freeRelocations(self, atom_index);
 
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
-    var decl_state: ?Dwarf.DeclState = if (self.dwarf) |*dw| try dw.initDeclState(module, decl_index) else null;
-    defer if (decl_state) |*ds| ds.deinit();
+    // var decl_state: ?Dwarf.DeclState = if (self.dwarf) |*dw| try dw.initDeclState(module, decl_index) else null;
+    // defer if (decl_state) |*ds| ds.deinit();
 
-    const res = if (decl_state) |*ds|
-        try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
-            .dwarf = ds,
-        })
-    else
-        try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
+    // const res = if (decl_state) |*ds|
+    //     try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
+    //         .dwarf = ds,
+    //     })
+    // else
+    const res = try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
 
     const code = switch (res) {
         .ok => code_buffer.items,
@@ -2445,15 +2490,16 @@ pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liven
         },
     };
     const local_sym = try self.updateDeclCode(decl_index, code, elf.STT_FUNC);
-    if (decl_state) |*ds| {
-        try self.dwarf.?.commitDeclState(
-            module,
-            decl_index,
-            local_sym.st_value,
-            local_sym.st_size,
-            ds,
-        );
-    }
+    _ = local_sym;
+    // if (decl_state) |*ds| {
+    //     try self.dwarf.?.commitDeclState(
+    //         module,
+    //         decl_index,
+    //         local_sym.st_value,
+    //         local_sym.st_size,
+    //         ds,
+    //     );
+    // }
 
     // Since we updated the vaddr and the size, each corresponding export
     // symbol also needs to be updated.
@@ -2483,41 +2529,34 @@ pub fn updateDecl(self: *Elf, module: *Module, decl_index: Module.Decl.Index) !v
         }
     }
 
-    assert(!self.unnamed_const_atoms.contains(decl_index));
-
-    const atom = &decl.link.elf;
-    try atom.ensureInitialized(self);
-    const gop = try self.decls.getOrPut(self.base.allocator, decl_index);
-    if (gop.found_existing) {
-        self.freeRelocationsForTextBlock(atom);
-    } else {
-        gop.value_ptr.* = null;
-    }
+    const atom_index = try self.getOrCreateAtomForDecl(decl_index);
+    Atom.freeRelocations(self, atom_index);
+    const atom = self.getAtom(atom_index);
 
     var code_buffer = std.ArrayList(u8).init(self.base.allocator);
     defer code_buffer.deinit();
 
-    var decl_state: ?Dwarf.DeclState = if (self.dwarf) |*dw| try dw.initDeclState(module, decl_index) else null;
-    defer if (decl_state) |*ds| ds.deinit();
+    // var decl_state: ?Dwarf.DeclState = if (self.dwarf) |*dw| try dw.initDeclState(module, decl_index) else null;
+    // defer if (decl_state) |*ds| ds.deinit();
 
     // TODO implement .debug_info for global variables
     const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
-    const res = if (decl_state) |*ds|
-        try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
-            .ty = decl.ty,
-            .val = decl_val,
-        }, &code_buffer, .{
-            .dwarf = ds,
-        }, .{
-            .parent_atom_index = decl.link.elf.getSymbolIndex().?,
-        })
-    else
-        try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
-            .ty = decl.ty,
-            .val = decl_val,
-        }, &code_buffer, .none, .{
-            .parent_atom_index = decl.link.elf.getSymbolIndex().?,
-        });
+    // const res = if (decl_state) |*ds|
+    //     try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
+    //         .ty = decl.ty,
+    //         .val = decl_val,
+    //     }, &code_buffer, .{
+    //         .dwarf = ds,
+    //     }, .{
+    //         .parent_atom_index = atom.getSymbolIndex().?,
+    //     })
+    // else
+    const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
+        .ty = decl.ty,
+        .val = decl_val,
+    }, &code_buffer, .none, .{
+        .parent_atom_index = atom.getSymbolIndex().?,
+    });
 
     const code = switch (res) {
         .ok => code_buffer.items,
@@ -2529,15 +2568,16 @@ pub fn updateDecl(self: *Elf, module: *Module, decl_index: Module.Decl.Index) !v
     };
 
     const local_sym = try self.updateDeclCode(decl_index, code, elf.STT_OBJECT);
-    if (decl_state) |*ds| {
-        try self.dwarf.?.commitDeclState(
-            module,
-            decl_index,
-            local_sym.st_value,
-            local_sym.st_size,
-            ds,
-        );
-    }
+    _ = local_sym;
+    // if (decl_state) |*ds| {
+    //     try self.dwarf.?.commitDeclState(
+    //         module,
+    //         decl_index,
+    //         local_sym.st_value,
+    //         local_sym.st_size,
+    //         ds,
+    //     );
+    // }
 
     // Since we updated the vaddr and the size, each corresponding export
     // symbol also needs to be updated.
@@ -2545,36 +2585,31 @@ pub fn updateDecl(self: *Elf, module: *Module, decl_index: Module.Decl.Index) !v
 }
 
 pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl_index: Module.Decl.Index) !u32 {
-    var code_buffer = std.ArrayList(u8).init(self.base.allocator);
+    const gpa = self.base.allocator;
+
+    var code_buffer = std.ArrayList(u8).init(gpa);
     defer code_buffer.deinit();
 
     const mod = self.base.options.module.?;
-    const decl = mod.declPtr(decl_index);
-
-    const gop = try self.unnamed_const_atoms.getOrPut(self.base.allocator, decl_index);
+    const gop = try self.unnamed_const_atoms.getOrPut(gpa, decl_index);
     if (!gop.found_existing) {
         gop.value_ptr.* = .{};
     }
     const unnamed_consts = gop.value_ptr;
 
-    const atom = try self.base.allocator.create(TextBlock);
-    errdefer self.base.allocator.destroy(atom);
-    atom.* = TextBlock.empty;
-    // TODO for unnamed consts we don't need GOT offset/entry allocated
-    try atom.ensureInitialized(self);
-    try self.managed_atoms.append(self.base.allocator, atom);
-
+    const decl = mod.declPtr(decl_index);
     const name_str_index = blk: {
         const decl_name = try decl.getFullyQualifiedName(mod);
-        defer self.base.allocator.free(decl_name);
-
+        defer gpa.free(decl_name);
         const index = unnamed_consts.items.len;
-        const name = try std.fmt.allocPrint(self.base.allocator, "__unnamed_{s}_{d}", .{ decl_name, index });
-        defer self.base.allocator.free(name);
-
-        break :blk try self.makeString(name);
+        const name = try std.fmt.allocPrint(gpa, "__unnamed_{s}_{d}", .{ decl_name, index });
+        defer gpa.free(name);
+        break :blk try self.shstrtab.insert(gpa, name);
     };
-    const name = self.getString(name_str_index);
+    const name = self.shstrtab.get(name_str_index).?;
+
+    const atom_index = try self.createAtom();
+    const atom = self.getAtomPtr(atom_index);
 
     const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), typed_value, &code_buffer, .{
         .none = {},
@@ -2592,28 +2627,24 @@ pub fn lowerUnnamedConst(self: *Elf, typed_value: TypedValue, decl_index: Module
     };
 
     const required_alignment = typed_value.ty.abiAlignment(self.base.options.target);
-    const phdr_index = self.phdr_load_ro_index.?;
-    const shdr_index = self.phdr_shdr_table.get(phdr_index).?;
-    const vaddr = try self.allocateTextBlock(atom, code.len, required_alignment, phdr_index);
-    errdefer self.freeTextBlock(atom, phdr_index);
-
-    log.debug("allocated text block for {s} at 0x{x}", .{ name, vaddr });
-
+    const shdr_index = self.rodata_section_index.?;
+    const phdr_index = self.sections.items(.phdr_index)[shdr_index];
     const local_sym = atom.getSymbolPtr(self);
-    local_sym.* = .{
-        .st_name = name_str_index,
-        .st_info = (elf.STB_LOCAL << 4) | elf.STT_OBJECT,
-        .st_other = 0,
-        .st_shndx = shdr_index,
-        .st_value = vaddr,
-        .st_size = code.len,
-    };
+    local_sym.st_name = name_str_index;
+    local_sym.st_info = (elf.STB_LOCAL << 4) | elf.STT_OBJECT;
+    local_sym.st_other = 0;
+    local_sym.st_shndx = shdr_index;
+    local_sym.st_size = code.len;
+    local_sym.st_value = try self.allocateAtom(atom_index, code.len, required_alignment);
+    errdefer self.freeAtom(atom_index);
+
+    log.debug("allocated text block for {s} at 0x{x}", .{ name, local_sym.st_value });
 
     try self.writeSymbol(atom.getSymbolIndex().?);
-    try unnamed_consts.append(self.base.allocator, atom);
+    try unnamed_consts.append(gpa, atom_index);
 
     const section_offset = local_sym.st_value - self.program_headers.items[phdr_index].p_vaddr;
-    const file_offset = self.sections.items[shdr_index].sh_offset + section_offset;
+    const file_offset = self.sections.items(.shdr)[shdr_index].sh_offset + section_offset;
     try self.base.file.?.pwriteAll(code, file_offset);
 
     return atom.getSymbolIndex().?;
@@ -2635,20 +2666,16 @@ pub fn updateDeclExports(
     const tracy = trace(@src());
     defer tracy.end();
 
-    const decl = module.declPtr(decl_index);
-    const atom = &decl.link.elf;
-
-    if (atom.getSymbolIndex() == null) return;
+    const gpa = self.base.allocator;
 
+    const decl = module.declPtr(decl_index);
+    const atom_index = try self.getOrCreateAtomForDecl(decl_index);
+    const atom = self.getAtom(atom_index);
     const decl_sym = atom.getSymbol(self);
-    try self.global_symbols.ensureUnusedCapacity(self.base.allocator, exports.len);
+    const decl_metadata = self.decls.getPtr(decl_index).?;
+    const shdr_index = decl_metadata.shdr;
 
-    const gop = try self.decls.getOrPut(self.base.allocator, decl_index);
-    if (!gop.found_existing) {
-        gop.value_ptr.* = try self.getDeclPhdrIndex(decl);
-    }
-    const phdr_index = gop.value_ptr.*.?;
-    const shdr_index = self.phdr_shdr_table.get(phdr_index).?;
+    try self.global_symbols.ensureUnusedCapacity(gpa, exports.len);
 
     for (exports) |exp| {
         if (exp.options.section) |section_name| {
@@ -2681,10 +2708,10 @@ pub fn updateDeclExports(
             },
         };
         const stt_bits: u8 = @truncate(u4, decl_sym.st_info);
-        if (exp.link.elf.sym_index) |i| {
+        if (decl_metadata.getExport(self, exp.options.name)) |i| {
             const sym = &self.global_symbols.items[i];
             sym.* = .{
-                .st_name = try self.updateString(sym.st_name, exp.options.name),
+                .st_name = try self.shstrtab.insert(gpa, exp.options.name),
                 .st_info = (stb_bits << 4) | stt_bits,
                 .st_other = 0,
                 .st_shndx = shdr_index,
@@ -2692,21 +2719,19 @@ pub fn updateDeclExports(
                 .st_size = decl_sym.st_size,
             };
         } else {
-            const name = try self.makeString(exp.options.name);
             const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: {
                 _ = self.global_symbols.addOneAssumeCapacity();
                 break :blk self.global_symbols.items.len - 1;
             };
+            try decl_metadata.exports.append(gpa, @intCast(u32, i));
             self.global_symbols.items[i] = .{
-                .st_name = name,
+                .st_name = try self.shstrtab.insert(gpa, exp.options.name),
                 .st_info = (stb_bits << 4) | stt_bits,
                 .st_other = 0,
                 .st_shndx = shdr_index,
                 .st_value = decl_sym.st_value,
                 .st_size = decl_sym.st_size,
             };
-
-            exp.link.elf.sym_index = @intCast(u32, i);
         }
     }
 }
@@ -2722,17 +2747,19 @@ pub fn updateDeclLineNumber(self: *Elf, mod: *Module, decl: *const Module.Decl)
     log.debug("updateDeclLineNumber {s}{*}", .{ decl_name, decl });
 
     if (self.llvm_object) |_| return;
-    if (self.dwarf) |*dw| {
-        try dw.updateDeclLineNumber(decl);
-    }
+    // if (self.dwarf) |*dw| {
+    //     try dw.updateDeclLineNumber(decl);
+    // }
 }
 
-pub fn deleteExport(self: *Elf, exp: Export) void {
+pub fn deleteDeclExport(self: *Elf, decl_index: Module.Decl.Index, name: []const u8) void {
     if (self.llvm_object) |_| return;
-
-    const sym_index = exp.sym_index orelse return;
-    self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {};
-    self.global_symbols.items[sym_index].st_info = 0;
+    const metadata = self.decls.getPtr(decl_index) orelse return;
+    const sym_index = metadata.getExportPtr(self, name) orelse return;
+    log.debug("deleting export '{s}'", .{name});
+    self.global_symbol_free_list.append(self.base.allocator, sym_index.*) catch {};
+    self.global_symbols.items[sym_index.*].st_info = 0;
+    sym_index.* = 0;
 }
 
 fn writeProgHeader(self: *Elf, index: usize) !void {
@@ -2761,7 +2788,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
     switch (self.ptr_width) {
         .p32 => {
             var shdr: [1]elf.Elf32_Shdr = undefined;
-            shdr[0] = sectHeaderTo32(self.sections.items[index]);
+            shdr[0] = sectHeaderTo32(self.sections.items(.shdr)[index]);
             if (foreign_endian) {
                 mem.byteSwapAllFields(elf.Elf32_Shdr, &shdr[0]);
             }
@@ -2769,7 +2796,7 @@ fn writeSectHeader(self: *Elf, index: usize) !void {
             return self.base.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
         },
         .p64 => {
-            var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};
+            var shdr = [1]elf.Elf64_Shdr{self.sections.items(.shdr)[index]};
             if (foreign_endian) {
                 mem.byteSwapAllFields(elf.Elf64_Shdr, &shdr[0]);
             }
@@ -2783,11 +2810,11 @@ fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
     const entry_size: u16 = self.archPtrWidthBytes();
     if (self.offset_table_count_dirty) {
         const needed_size = self.offset_table.items.len * entry_size;
-        try self.growAllocSection(self.got_section_index.?, self.phdr_got_index.?, needed_size);
+        try self.growAllocSection(self.got_section_index.?, needed_size);
         self.offset_table_count_dirty = false;
     }
     const endian = self.base.options.target.cpu.arch.endian();
-    const shdr = &self.sections.items[self.got_section_index.?];
+    const shdr = &self.sections.items(.shdr)[self.got_section_index.?];
     const off = shdr.sh_offset + @as(u64, entry_size) * index;
     switch (entry_size) {
         2 => {
@@ -2813,7 +2840,7 @@ fn writeSymbol(self: *Elf, index: usize) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
-    const syms_sect = &self.sections.items[self.symtab_section_index.?];
+    const syms_sect = &self.sections.items(.shdr)[self.symtab_section_index.?];
     // Make sure we are not pointlessly writing symbol data that will have to get relocated
     // due to running out of space.
     if (self.local_symbols.items.len != syms_sect.sh_info) {
@@ -2835,7 +2862,7 @@ fn writeSymbol(self: *Elf, index: usize) !void {
         .p64 => syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index,
     };
     const local = self.local_symbols.items[index];
-    log.debug("writing symbol {d}, '{s}' at 0x{x}", .{ index, self.getString(local.st_name), off });
+    log.debug("writing symbol {d}, '{?s}' at 0x{x}", .{ index, self.shstrtab.get(local.st_name), off });
     log.debug("  ({})", .{local});
     switch (self.ptr_width) {
         .p32 => {
@@ -2865,7 +2892,7 @@ fn writeSymbol(self: *Elf, index: usize) !void {
 }
 
 fn writeAllGlobalSymbols(self: *Elf) !void {
-    const syms_sect = &self.sections.items[self.symtab_section_index.?];
+    const syms_sect = &self.sections.items(.shdr)[self.symtab_section_index.?];
     const sym_size: u64 = switch (self.ptr_width) {
         .p32 => @sizeOf(elf.Elf32_Sym),
         .p64 => @sizeOf(elf.Elf64_Sym),
@@ -3215,10 +3242,52 @@ const CsuObjects = struct {
 fn logSymtab(self: Elf) void {
     log.debug("locals:", .{});
     for (self.local_symbols.items) |sym, id| {
-        log.debug("  {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.st_name), sym.st_value, sym.st_shndx });
+        log.debug("  {d}: {?s}: @{x} in {d}", .{ id, self.shstrtab.get(sym.st_name), sym.st_value, sym.st_shndx });
     }
     log.debug("globals:", .{});
     for (self.global_symbols.items) |sym, id| {
-        log.debug("  {d}: {s}: @{x} in {d}", .{ id, self.getString(sym.st_name), sym.st_value, sym.st_shndx });
+        log.debug("  {d}: {?s}: @{x} in {d}", .{ id, self.shstrtab.get(sym.st_name), sym.st_value, sym.st_shndx });
     }
 }
+
+pub fn getProgramHeader(self: *const Elf, shdr_index: u16) elf.Elf64_Phdr {
+    const index = self.sections.items(.phdr_index)[shdr_index];
+    return self.program_headers.items[index];
+}
+
+pub fn getProgramHeaderPtr(self: *Elf, shdr_index: u16) *elf.Elf64_Phdr {
+    const index = self.sections.items(.phdr_index)[shdr_index];
+    return &self.program_headers.items[index];
+}
+
+/// Returns pointer-to-symbol described at sym_index.
+pub fn getSymbolPtr(self: *Elf, sym_index: u32) *elf.Elf64_Sym {
+    return &self.local_symbols.items[sym_index];
+}
+
+/// Returns symbol at sym_index.
+pub fn getSymbol(self: *const Elf, sym_index: u32) elf.Elf64_Sym {
+    return self.local_symbols.items[sym_index];
+}
+
+/// Returns name of the symbol at sym_index.
+pub fn getSymbolName(self: *const Elf, sym_index: u32) []const u8 {
+    const sym = self.local_symbols.items[sym_index];
+    return self.shstrtab.get(sym.st_name).?;
+}
+
+pub fn getAtom(self: *const Elf, atom_index: Atom.Index) Atom {
+    assert(atom_index < self.atoms.items.len);
+    return self.atoms.items[atom_index];
+}
+
+pub fn getAtomPtr(self: *Elf, atom_index: Atom.Index) *Atom {
+    assert(atom_index < self.atoms.items.len);
+    return &self.atoms.items[atom_index];
+}
+
+/// Returns atom if there is an atom referenced by the symbol.
+/// Returns null on failure.
+pub fn getAtomIndexForSymbol(self: *Elf, sym_index: u32) ?Atom.Index {
+    return self.atom_by_index_table.get(sym_index);
+}
src/link/MachO.zig
@@ -2604,9 +2604,11 @@ pub fn freeDecl(self: *MachO, decl_index: Module.Decl.Index) void {
 
     log.debug("freeDecl {*}", .{decl});
 
-    if (self.decls.fetchSwapRemove(decl_index)) |kv| {
+    if (self.decls.fetchSwapRemove(decl_index)) |const_kv| {
+        var kv = const_kv;
         self.freeAtom(kv.value.atom);
         self.freeUnnamedConsts(decl_index);
+        kv.value.exports.deinit(self.base.allocator);
     }
 
     // if (self.d_sym) |*d_sym| {
src/link.zig
@@ -262,7 +262,7 @@ pub const File = struct {
     lock: ?Cache.Lock = null,
 
     pub const LinkBlock = union {
-        elf: Elf.TextBlock,
+        elf: void,
         coff: Coff.Atom,
         macho: void,
         plan9: Plan9.DeclBlock,
@@ -284,7 +284,7 @@ pub const File = struct {
     };
 
     pub const Export = union {
-        elf: Elf.Export,
+        elf: void,
         coff: Coff.Export,
         macho: void,
         plan9: Plan9.Export,
src/Module.zig
@@ -5275,7 +5275,7 @@ pub fn clearDecl(
             // and allow it to be variably sized.
             decl.link = switch (mod.comp.bin_file.tag) {
                 .coff => .{ .coff = link.File.Coff.Atom.empty },
-                .elf => .{ .elf = link.File.Elf.TextBlock.empty },
+                .elf => .{ .elf = {} },
                 .macho => .{ .macho = {} },
                 .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
                 .c => .{ .c = {} },
@@ -5381,7 +5381,7 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) Allocator.Error!void
             }
         }
         if (mod.comp.bin_file.cast(link.File.Elf)) |elf| {
-            elf.deleteExport(exp.link.elf);
+            elf.deleteDeclExport(decl_index, exp.options.name);
         }
         if (mod.comp.bin_file.cast(link.File.MachO)) |macho| {
             try macho.deleteDeclExport(decl_index, exp.options.name);
@@ -5695,7 +5695,7 @@ pub fn allocateNewDecl(
         .src_scope = src_scope,
         .link = switch (mod.comp.bin_file.tag) {
             .coff => .{ .coff = link.File.Coff.Atom.empty },
-            .elf => .{ .elf = link.File.Elf.TextBlock.empty },
+            .elf => .{ .elf = {} },
             .macho => .{ .macho = {} },
             .plan9 => .{ .plan9 = link.File.Plan9.DeclBlock.empty },
             .c => .{ .c = {} },
src/Sema.zig
@@ -5566,7 +5566,7 @@ pub fn analyzeExport(
         .src = src,
         .link = switch (mod.comp.bin_file.tag) {
             .coff => .{ .coff = .{} },
-            .elf => .{ .elf = .{} },
+            .elf => .{ .elf = {} },
             .macho => .{ .macho = {} },
             .plan9 => .{ .plan9 = null },
             .c => .{ .c = {} },