Commit 1254509d78

Jakub Konka <kubkon@jakubkonka.com>
2024-08-26 22:19:51
elf: make Atom.allocate and related ZigObject-independent
1 parent 20240e9
Changed files (3)
src/link/Elf/Atom.zig
@@ -25,10 +25,9 @@ relocs_section_index: u32 = 0,
 /// Index of this atom in the linker's atoms table.
 atom_index: Index = 0,
 
-/// 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_index: Index = 0,
-next_index: Index = 0,
+/// Points to the previous and next neighbors.
+prev_atom_ref: Elf.Ref = .{},
+next_atom_ref: Elf.Ref = .{},
 
 /// Specifies whether this atom is alive or has been garbage collected.
 alive: bool = true,
@@ -52,6 +51,18 @@ pub fn address(self: Atom, elf_file: *Elf) i64 {
     return @as(i64, @intCast(shdr.sh_addr)) + self.value;
 }
 
+pub fn ref(self: Atom) Elf.Ref {
+    return .{ .index = self.atom_index, .file = self.file_index };
+}
+
+pub fn prevAtom(self: Atom, elf_file: *Elf) ?*Atom {
+    return elf_file.atom(self.prev_atom_ref);
+}
+
+pub fn nextAtom(self: Atom, elf_file: *Elf) ?*Atom {
+    return elf_file.atom(self.next_atom_ref);
+}
+
 pub fn debugTombstoneValue(self: Atom, target: Symbol, elf_file: *Elf) ?u64 {
     if (target.mergeSubsection(elf_file)) |msub| {
         if (msub.alive) return null;
@@ -95,18 +106,16 @@ pub fn priority(self: Atom, elf_file: *Elf) u64 {
 /// File offset relocation happens transparently, so it is not included in
 /// this calculation.
 pub fn capacity(self: Atom, elf_file: *Elf) u64 {
-    const zo = elf_file.zigObjectPtr().?;
-    const next_addr = if (zo.atom(self.next_index)) |next|
-        next.address(elf_file)
+    const next_addr = if (self.nextAtom(elf_file)) |next_atom|
+        next_atom.address(elf_file)
     else
         std.math.maxInt(u32);
     return @intCast(next_addr - self.address(elf_file));
 }
 
 pub fn freeListEligible(self: Atom, elf_file: *Elf) bool {
-    const zo = elf_file.zigObjectPtr().?;
     // No need to keep a free list node for the last block.
-    const next = zo.atom(self.next_index) orelse return false;
+    const next = self.nextAtom(elf_file) orelse return false;
     const cap: u64 = @intCast(next.address(elf_file) - self.address(elf_file));
     const ideal_cap = Elf.padToIdeal(self.size);
     if (cap <= ideal_cap) return false;
@@ -115,11 +124,10 @@ pub fn freeListEligible(self: Atom, elf_file: *Elf) bool {
 }
 
 pub fn allocate(self: *Atom, elf_file: *Elf) !void {
-    const zo = elf_file.zigObjectPtr().?;
     const slice = elf_file.sections.slice();
     const shdr = &slice.items(.shdr)[self.output_section_index];
     const free_list = &slice.items(.free_list)[self.output_section_index];
-    const last_atom_index = &slice.items(.last_atom_index)[self.output_section_index];
+    const last_atom_ref = &slice.items(.last_atom)[self.output_section_index];
     const new_atom_ideal_capacity = Elf.padToIdeal(self.size);
 
     // We use these to indicate our intention to update metadata, placing the new atom,
@@ -127,7 +135,7 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void {
     // 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 atom_placement: ?Atom.Index = null;
+    var atom_placement: ?Elf.Ref = null;
     var free_list_removal: ?usize = null;
 
     // First we look for an appropriately sized free list node.
@@ -135,8 +143,8 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void {
     self.value = blk: {
         var i: usize = if (elf_file.base.child_pid == null) 0 else free_list.items.len;
         while (i < free_list.items.len) {
-            const big_atom_index = free_list.items[i];
-            const big_atom = zo.atom(big_atom_index).?;
+            const big_atom_ref = free_list.items[i];
+            const big_atom = elf_file.atom(big_atom_ref).?;
             // 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 cap = big_atom.capacity(elf_file);
@@ -163,50 +171,52 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void {
             const keep_free_list_node = remaining_capacity >= Elf.min_text_capacity;
 
             // Set up the metadata to be updated, after errors are no longer possible.
-            atom_placement = big_atom_index;
+            atom_placement = big_atom_ref;
             if (!keep_free_list_node) {
                 free_list_removal = i;
             }
             break :blk @intCast(new_start_vaddr);
-        } else if (zo.atom(last_atom_index.*)) |last| {
-            const ideal_capacity = Elf.padToIdeal(last.size);
-            const ideal_capacity_end_vaddr = @as(u64, @intCast(last.value)) + ideal_capacity;
+        } else if (elf_file.atom(last_atom_ref.*)) |last_atom| {
+            const ideal_capacity = Elf.padToIdeal(last_atom.size);
+            const ideal_capacity_end_vaddr = @as(u64, @intCast(last_atom.value)) + ideal_capacity;
             const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr);
             // Set up the metadata to be updated, after errors are no longer possible.
-            atom_placement = last.atom_index;
+            atom_placement = last_atom.ref();
             break :blk @intCast(new_start_vaddr);
         } else {
             break :blk 0;
         }
     };
 
-    log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{
-        self.atom_index,
+    log.debug("allocated atom({}) : '{s}' at 0x{x} to 0x{x}", .{
+        self.ref(),
         self.name(elf_file),
         self.address(elf_file),
         self.address(elf_file) + @as(i64, @intCast(self.size)),
     });
 
-    const expand_section = if (atom_placement) |placement_index|
-        zo.atom(placement_index).?.next_index == 0
+    const expand_section = if (atom_placement) |placement_ref|
+        elf_file.atom(placement_ref).?.nextAtom(elf_file) == null
     else
         true;
     if (expand_section) {
         const needed_size: u64 = @intCast(self.value + @as(i64, @intCast(self.size)));
         try elf_file.growAllocSection(self.output_section_index, needed_size);
-        last_atom_index.* = self.atom_index;
-
-        const zig_object = elf_file.zigObjectPtr().?;
-        if (zig_object.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.
-            zig_object.debug_info_section_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.
-            zig_object.debug_aranges_section_dirty = true;
-            zig_object.debug_rnglists_section_dirty = true;
+        last_atom_ref.* = self.ref();
+
+        switch (self.file(elf_file).?) {
+            .zig_object => |zo| if (zo.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.
+                zo.debug_info_section_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.
+                zo.debug_aranges_section_dirty = true;
+                zo.debug_rnglists_section_dirty = true;
+            },
+            else => {},
         }
     }
     shdr.sh_addralign = @max(shdr.sh_addralign, self.alignment.toByteUnits().?);
@@ -214,21 +224,21 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void {
     // 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 (zo.atom(self.prev_index)) |prev| {
-        prev.next_index = self.next_index;
+    if (self.prevAtom(elf_file)) |prev| {
+        prev.next_atom_ref = self.next_atom_ref;
     }
-    if (zo.atom(self.next_index)) |next| {
-        next.prev_index = self.prev_index;
+    if (self.nextAtom(elf_file)) |next| {
+        next.prev_atom_ref = self.prev_atom_ref;
     }
 
-    if (atom_placement) |big_atom_index| {
-        const big_atom = zo.atom(big_atom_index).?;
-        self.prev_index = big_atom_index;
-        self.next_index = big_atom.next_index;
-        big_atom.next_index = self.atom_index;
+    if (atom_placement) |big_atom_ref| {
+        const big_atom = elf_file.atom(big_atom_ref).?;
+        self.prev_atom_ref = big_atom_ref;
+        self.next_atom_ref = big_atom.next_atom_ref;
+        big_atom.next_atom_ref = self.ref();
     } else {
-        self.prev_index = 0;
-        self.next_index = 0;
+        self.prev_atom_ref = .{ .index = 0, .file = 0 };
+        self.next_atom_ref = .{ .index = 0, .file = 0 };
     }
     if (free_list_removal) |i| {
         _ = free_list.swapRemove(i);
@@ -248,64 +258,70 @@ pub fn grow(self: *Atom, elf_file: *Elf) !void {
 }
 
 pub fn free(self: *Atom, elf_file: *Elf) void {
-    log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.name(elf_file) });
+    log.debug("freeAtom atom({}) ({s})", .{ self.ref(), self.name(elf_file) });
 
-    const zo = elf_file.zigObjectPtr().?;
     const comp = elf_file.base.comp;
     const gpa = comp.gpa;
     const shndx = self.output_section_index;
     const slice = elf_file.sections.slice();
     const free_list = &slice.items(.free_list)[shndx];
-    const last_atom_index = &slice.items(.last_atom_index)[shndx];
+    const last_atom_ref = &slice.items(.last_atom)[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] == self.atom_index) {
+            if (free_list.items[i].eql(self.ref())) {
                 _ = free_list.swapRemove(i);
                 continue;
             }
-            if (free_list.items[i] == self.prev_index) {
-                already_have_free_list_node = true;
+            if (self.prevAtom(elf_file)) |prev_atom| {
+                if (free_list.items[i].eql(prev_atom.ref())) {
+                    already_have_free_list_node = true;
+                }
             }
             i += 1;
         }
     }
 
-    if (zo.atom(last_atom_index.*)) |last_atom| {
-        if (last_atom.atom_index == self.atom_index) {
-            if (zo.atom(self.prev_index)) |_| {
+    if (elf_file.atom(last_atom_ref.*)) |last_atom| {
+        if (last_atom.ref().eql(self.ref())) {
+            if (self.prevAtom(elf_file)) |prev_atom| {
                 // TODO shrink the section size here
-                last_atom_index.* = self.prev_index;
+                last_atom_ref.* = prev_atom.ref();
             } else {
-                last_atom_index.* = 0;
+                last_atom_ref.* = .{};
             }
         }
     }
 
-    if (zo.atom(self.prev_index)) |prev| {
-        prev.next_index = self.next_index;
-        if (!already_have_free_list_node and prev.*.freeListEligible(elf_file)) {
+    if (self.prevAtom(elf_file)) |prev_atom| {
+        prev_atom.next_atom_ref = self.next_atom_ref;
+        if (!already_have_free_list_node and prev_atom.*.freeListEligible(elf_file)) {
             // The free list is heuristics, it doesn't have to be perfect, so we can
             // ignore the OOM here.
-            free_list.append(gpa, prev.atom_index) catch {};
+            free_list.append(gpa, prev_atom.ref()) catch {};
         }
     } else {
-        self.prev_index = 0;
+        self.prev_atom_ref = .{};
     }
 
-    if (zo.atom(self.next_index)) |next| {
-        next.prev_index = self.prev_index;
+    if (self.nextAtom(elf_file)) |next_atom| {
+        next_atom.prev_atom_ref = self.prev_atom_ref;
     } else {
-        self.next_index = 0;
+        self.next_atom_ref = .{};
     }
 
-    // TODO create relocs free list
-    self.freeRelocs(zo);
-    // TODO figure out how to free input section mappind in ZigModule
-    // const zig_object = elf_file.zigObjectPtr().?
-    // assert(zig_object.atoms.swapRemove(self.atom_index));
+    switch (self.file(elf_file).?) {
+        .zig_object => |zo| {
+            // TODO create relocs free list
+            self.freeRelocs(zo);
+            // TODO figure out how to free input section mappind in ZigModule
+            // const zig_object = elf_file.zigObjectPtr().?
+            // assert(zig_object.atoms.swapRemove(self.atom_index));
+        },
+        else => {},
+    }
     self.* = .{};
 }
 
src/link/Elf/Symbol.zig
@@ -11,7 +11,7 @@ file_index: File.Index = 0,
 
 /// Reference to Atom or merge subsection containing this symbol if any.
 /// Use `atom` or `mergeSubsection` to get the pointer to the atom.
-ref: Elf.Ref = .{ .index = 0, .file = 0 },
+ref: Elf.Ref = .{},
 
 /// Assigned output section index for this symbol.
 output_section_index: u32 = 0,
src/link/Elf.zig
@@ -5402,8 +5402,8 @@ pub const SystemLib = struct {
 };
 
 pub const Ref = struct {
-    index: u32,
-    file: u32,
+    index: u32 = 0,
+    file: u32 = 0,
 
     pub fn eql(ref: Ref, other: Ref) bool {
         return ref.index == other.index and ref.file == other.file;
@@ -5522,7 +5522,7 @@ const Section = struct {
     atom_list: std.ArrayListUnmanaged(Ref) = .{},
 
     /// Index of the last allocated atom in this section.
-    last_atom_index: Atom.Index = 0,
+    last_atom: Ref = .{ .index = 0, .file = 0 },
 
     /// 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
@@ -5539,7 +5539,7 @@ const Section = struct {
     /// 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) = .{},
+    free_list: std.ArrayListUnmanaged(Ref) = .{},
 };
 
 fn defaultEntrySymbolName(cpu_arch: std.Target.Cpu.Arch) []const u8 {