Skip to content

Commit

Permalink
elf: make Atom.allocate and related ZigObject-independent
Browse files Browse the repository at this point in the history
  • Loading branch information
kubkon committed Aug 26, 2024
1 parent 20240e9 commit 1254509
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 76 deletions.
8 changes: 4 additions & 4 deletions src/link/Elf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down
158 changes: 87 additions & 71 deletions src/link/Elf/Atom.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -115,28 +124,27 @@ 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,
// 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 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.
// The list is unordered. We'll just take the first thing that works.
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);
Expand All @@ -163,72 +171,74 @@ 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().?);

// 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);
Expand All @@ -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.* = .{};
}

Expand Down
2 changes: 1 addition & 1 deletion src/link/Elf/Symbol.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 1254509

Please sign in to comment.