| ... | ... | @@ -25,10 +25,9 @@ relocs_section_index: u32 = 0, |
| 25 | 25 | /// Index of this atom in the linker's atoms table. |
| 26 | 26 | atom_index: Index = 0, |
| 27 | 27 | |
| 28 | | /// Points to the previous and next neighbors, based on the `text_offset`. |
| 29 | | /// This can be used to find, for example, the capacity of this `TextBlock`. |
| 30 | | prev_index: Index = 0, |
| 31 | | next_index: Index = 0, |
| 28 | /// Points to the previous and next neighbors. |
| 29 | prev_atom_ref: Elf.Ref = .{}, |
| 30 | next_atom_ref: Elf.Ref = .{}, |
| 32 | 31 | |
| 33 | 32 | /// Specifies whether this atom is alive or has been garbage collected. |
| 34 | 33 | alive: bool = true, |
| ... | ... | @@ -52,6 +51,18 @@ pub fn address(self: Atom, elf_file: *Elf) i64 { |
| 52 | 51 | return @as(i64, @intCast(shdr.sh_addr)) + self.value; |
| 53 | 52 | } |
| 54 | 53 | |
| 54 | pub fn ref(self: Atom) Elf.Ref { |
| 55 | return .{ .index = self.atom_index, .file = self.file_index }; |
| 56 | } |
| 57 | |
| 58 | pub fn prevAtom(self: Atom, elf_file: *Elf) ?*Atom { |
| 59 | return elf_file.atom(self.prev_atom_ref); |
| 60 | } |
| 61 | |
| 62 | pub fn nextAtom(self: Atom, elf_file: *Elf) ?*Atom { |
| 63 | return elf_file.atom(self.next_atom_ref); |
| 64 | } |
| 65 | |
| 55 | 66 | pub fn debugTombstoneValue(self: Atom, target: Symbol, elf_file: *Elf) ?u64 { |
| 56 | 67 | if (target.mergeSubsection(elf_file)) |msub| { |
| 57 | 68 | if (msub.alive) return null; |
| ... | ... | @@ -95,18 +106,16 @@ pub fn priority(self: Atom, elf_file: *Elf) u64 { |
| 95 | 106 | /// File offset relocation happens transparently, so it is not included in |
| 96 | 107 | /// this calculation. |
| 97 | 108 | pub fn capacity(self: Atom, elf_file: *Elf) u64 { |
| 98 | | const zo = elf_file.zigObjectPtr().?; |
| 99 | | const next_addr = if (zo.atom(self.next_index)) |next| |
| 100 | | next.address(elf_file) |
| 109 | const next_addr = if (self.nextAtom(elf_file)) |next_atom| |
| 110 | next_atom.address(elf_file) |
| 101 | 111 | else |
| 102 | 112 | std.math.maxInt(u32); |
| 103 | 113 | return @intCast(next_addr - self.address(elf_file)); |
| 104 | 114 | } |
| 105 | 115 | |
| 106 | 116 | pub fn freeListEligible(self: Atom, elf_file: *Elf) bool { |
| 107 | | const zo = elf_file.zigObjectPtr().?; |
| 108 | 117 | // No need to keep a free list node for the last block. |
| 109 | | const next = zo.atom(self.next_index) orelse return false; |
| 118 | const next = self.nextAtom(elf_file) orelse return false; |
| 110 | 119 | const cap: u64 = @intCast(next.address(elf_file) - self.address(elf_file)); |
| 111 | 120 | const ideal_cap = Elf.padToIdeal(self.size); |
| 112 | 121 | if (cap <= ideal_cap) return false; |
| ... | ... | @@ -115,11 +124,10 @@ pub fn freeListEligible(self: Atom, elf_file: *Elf) bool { |
| 115 | 124 | } |
| 116 | 125 | |
| 117 | 126 | pub fn allocate(self: *Atom, elf_file: *Elf) !void { |
| 118 | | const zo = elf_file.zigObjectPtr().?; |
| 119 | 127 | const slice = elf_file.sections.slice(); |
| 120 | 128 | const shdr = &slice.items(.shdr)[self.output_section_index]; |
| 121 | 129 | const free_list = &slice.items(.free_list)[self.output_section_index]; |
| 122 | | const last_atom_index = &slice.items(.last_atom_index)[self.output_section_index]; |
| 130 | const last_atom_ref = &slice.items(.last_atom)[self.output_section_index]; |
| 123 | 131 | const new_atom_ideal_capacity = Elf.padToIdeal(self.size); |
| 124 | 132 | |
| 125 | 133 | // 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 { |
| 127 | 135 | // It would be simpler to do it inside the for loop below, but that would cause a |
| 128 | 136 | // problem if an error was returned later in the function. So this action |
| 129 | 137 | // is actually carried out at the end of the function, when errors are no longer possible. |
| 130 | | var atom_placement: ?Atom.Index = null; |
| 138 | var atom_placement: ?Elf.Ref = null; |
| 131 | 139 | var free_list_removal: ?usize = null; |
| 132 | 140 | |
| 133 | 141 | // First we look for an appropriately sized free list node. |
| ... | ... | @@ -135,8 +143,8 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { |
| 135 | 143 | self.value = blk: { |
| 136 | 144 | var i: usize = if (elf_file.base.child_pid == null) 0 else free_list.items.len; |
| 137 | 145 | while (i < free_list.items.len) { |
| 138 | | const big_atom_index = free_list.items[i]; |
| 139 | | const big_atom = zo.atom(big_atom_index).?; |
| 146 | const big_atom_ref = free_list.items[i]; |
| 147 | const big_atom = elf_file.atom(big_atom_ref).?; |
| 140 | 148 | // We now have a pointer to a live atom that has too much capacity. |
| 141 | 149 | // Is it enough that we could fit this new atom? |
| 142 | 150 | const cap = big_atom.capacity(elf_file); |
| ... | ... | @@ -163,50 +171,52 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { |
| 163 | 171 | const keep_free_list_node = remaining_capacity >= Elf.min_text_capacity; |
| 164 | 172 | |
| 165 | 173 | // Set up the metadata to be updated, after errors are no longer possible. |
| 166 | | atom_placement = big_atom_index; |
| 174 | atom_placement = big_atom_ref; |
| 167 | 175 | if (!keep_free_list_node) { |
| 168 | 176 | free_list_removal = i; |
| 169 | 177 | } |
| 170 | 178 | break :blk @intCast(new_start_vaddr); |
| 171 | | } else if (zo.atom(last_atom_index.*)) |last| { |
| 172 | | const ideal_capacity = Elf.padToIdeal(last.size); |
| 173 | | const ideal_capacity_end_vaddr = @as(u64, @intCast(last.value)) + ideal_capacity; |
| 179 | } else if (elf_file.atom(last_atom_ref.*)) |last_atom| { |
| 180 | const ideal_capacity = Elf.padToIdeal(last_atom.size); |
| 181 | const ideal_capacity_end_vaddr = @as(u64, @intCast(last_atom.value)) + ideal_capacity; |
| 174 | 182 | const new_start_vaddr = self.alignment.forward(ideal_capacity_end_vaddr); |
| 175 | 183 | // Set up the metadata to be updated, after errors are no longer possible. |
| 176 | | atom_placement = last.atom_index; |
| 184 | atom_placement = last_atom.ref(); |
| 177 | 185 | break :blk @intCast(new_start_vaddr); |
| 178 | 186 | } else { |
| 179 | 187 | break :blk 0; |
| 180 | 188 | } |
| 181 | 189 | }; |
| 182 | 190 | |
| 183 | | log.debug("allocated atom({d}) : '{s}' at 0x{x} to 0x{x}", .{ |
| 184 | | self.atom_index, |
| 191 | log.debug("allocated atom({}) : '{s}' at 0x{x} to 0x{x}", .{ |
| 192 | self.ref(), |
| 185 | 193 | self.name(elf_file), |
| 186 | 194 | self.address(elf_file), |
| 187 | 195 | self.address(elf_file) + @as(i64, @intCast(self.size)), |
| 188 | 196 | }); |
| 189 | 197 | |
| 190 | | const expand_section = if (atom_placement) |placement_index| |
| 191 | | zo.atom(placement_index).?.next_index == 0 |
| 198 | const expand_section = if (atom_placement) |placement_ref| |
| 199 | elf_file.atom(placement_ref).?.nextAtom(elf_file) == null |
| 192 | 200 | else |
| 193 | 201 | true; |
| 194 | 202 | if (expand_section) { |
| 195 | 203 | const needed_size: u64 = @intCast(self.value + @as(i64, @intCast(self.size))); |
| 196 | 204 | try elf_file.growAllocSection(self.output_section_index, needed_size); |
| 197 | | last_atom_index.* = self.atom_index; |
| 198 | | |
| 199 | | const zig_object = elf_file.zigObjectPtr().?; |
| 200 | | if (zig_object.dwarf) |_| { |
| 201 | | // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address |
| 202 | | // range of the compilation unit. When we expand the text section, this range changes, |
| 203 | | // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty. |
| 204 | | zig_object.debug_info_section_dirty = true; |
| 205 | | // This becomes dirty for the same reason. We could potentially make this more |
| 206 | | // fine-grained with the addition of support for more compilation units. It is planned to |
| 207 | | // model each package as a different compilation unit. |
| 208 | | zig_object.debug_aranges_section_dirty = true; |
| 209 | | zig_object.debug_rnglists_section_dirty = true; |
| 205 | last_atom_ref.* = self.ref(); |
| 206 | |
| 207 | switch (self.file(elf_file).?) { |
| 208 | .zig_object => |zo| if (zo.dwarf) |_| { |
| 209 | // The .debug_info section has `low_pc` and `high_pc` values which is the virtual address |
| 210 | // range of the compilation unit. When we expand the text section, this range changes, |
| 211 | // so the DW_TAG.compile_unit tag of the .debug_info section becomes dirty. |
| 212 | zo.debug_info_section_dirty = true; |
| 213 | // This becomes dirty for the same reason. We could potentially make this more |
| 214 | // fine-grained with the addition of support for more compilation units. It is planned to |
| 215 | // model each package as a different compilation unit. |
| 216 | zo.debug_aranges_section_dirty = true; |
| 217 | zo.debug_rnglists_section_dirty = true; |
| 218 | }, |
| 219 | else => {}, |
| 210 | 220 | } |
| 211 | 221 | } |
| 212 | 222 | shdr.sh_addralign = @max(shdr.sh_addralign, self.alignment.toByteUnits().?); |
| ... | ... | @@ -214,21 +224,21 @@ pub fn allocate(self: *Atom, elf_file: *Elf) !void { |
| 214 | 224 | // This function can also reallocate an atom. |
| 215 | 225 | // In this case we need to "unplug" it from its previous location before |
| 216 | 226 | // plugging it in to its new location. |
| 217 | | if (zo.atom(self.prev_index)) |prev| { |
| 218 | | prev.next_index = self.next_index; |
| 227 | if (self.prevAtom(elf_file)) |prev| { |
| 228 | prev.next_atom_ref = self.next_atom_ref; |
| 219 | 229 | } |
| 220 | | if (zo.atom(self.next_index)) |next| { |
| 221 | | next.prev_index = self.prev_index; |
| 230 | if (self.nextAtom(elf_file)) |next| { |
| 231 | next.prev_atom_ref = self.prev_atom_ref; |
| 222 | 232 | } |
| 223 | 233 | |
| 224 | | if (atom_placement) |big_atom_index| { |
| 225 | | const big_atom = zo.atom(big_atom_index).?; |
| 226 | | self.prev_index = big_atom_index; |
| 227 | | self.next_index = big_atom.next_index; |
| 228 | | big_atom.next_index = self.atom_index; |
| 234 | if (atom_placement) |big_atom_ref| { |
| 235 | const big_atom = elf_file.atom(big_atom_ref).?; |
| 236 | self.prev_atom_ref = big_atom_ref; |
| 237 | self.next_atom_ref = big_atom.next_atom_ref; |
| 238 | big_atom.next_atom_ref = self.ref(); |
| 229 | 239 | } else { |
| 230 | | self.prev_index = 0; |
| 231 | | self.next_index = 0; |
| 240 | self.prev_atom_ref = .{ .index = 0, .file = 0 }; |
| 241 | self.next_atom_ref = .{ .index = 0, .file = 0 }; |
| 232 | 242 | } |
| 233 | 243 | if (free_list_removal) |i| { |
| 234 | 244 | _ = free_list.swapRemove(i); |
| ... | ... | @@ -248,64 +258,70 @@ pub fn grow(self: *Atom, elf_file: *Elf) !void { |
| 248 | 258 | } |
| 249 | 259 | |
| 250 | 260 | pub fn free(self: *Atom, elf_file: *Elf) void { |
| 251 | | log.debug("freeAtom {d} ({s})", .{ self.atom_index, self.name(elf_file) }); |
| 261 | log.debug("freeAtom atom({}) ({s})", .{ self.ref(), self.name(elf_file) }); |
| 252 | 262 | |
| 253 | | const zo = elf_file.zigObjectPtr().?; |
| 254 | 263 | const comp = elf_file.base.comp; |
| 255 | 264 | const gpa = comp.gpa; |
| 256 | 265 | const shndx = self.output_section_index; |
| 257 | 266 | const slice = elf_file.sections.slice(); |
| 258 | 267 | const free_list = &slice.items(.free_list)[shndx]; |
| 259 | | const last_atom_index = &slice.items(.last_atom_index)[shndx]; |
| 268 | const last_atom_ref = &slice.items(.last_atom)[shndx]; |
| 260 | 269 | var already_have_free_list_node = false; |
| 261 | 270 | { |
| 262 | 271 | var i: usize = 0; |
| 263 | 272 | // TODO turn free_list into a hash map |
| 264 | 273 | while (i < free_list.items.len) { |
| 265 | | if (free_list.items[i] == self.atom_index) { |
| 274 | if (free_list.items[i].eql(self.ref())) { |
| 266 | 275 | _ = free_list.swapRemove(i); |
| 267 | 276 | continue; |
| 268 | 277 | } |
| 269 | | if (free_list.items[i] == self.prev_index) { |
| 270 | | already_have_free_list_node = true; |
| 278 | if (self.prevAtom(elf_file)) |prev_atom| { |
| 279 | if (free_list.items[i].eql(prev_atom.ref())) { |
| 280 | already_have_free_list_node = true; |
| 281 | } |
| 271 | 282 | } |
| 272 | 283 | i += 1; |
| 273 | 284 | } |
| 274 | 285 | } |
| 275 | 286 | |
| 276 | | if (zo.atom(last_atom_index.*)) |last_atom| { |
| 277 | | if (last_atom.atom_index == self.atom_index) { |
| 278 | | if (zo.atom(self.prev_index)) |_| { |
| 287 | if (elf_file.atom(last_atom_ref.*)) |last_atom| { |
| 288 | if (last_atom.ref().eql(self.ref())) { |
| 289 | if (self.prevAtom(elf_file)) |prev_atom| { |
| 279 | 290 | // TODO shrink the section size here |
| 280 | | last_atom_index.* = self.prev_index; |
| 291 | last_atom_ref.* = prev_atom.ref(); |
| 281 | 292 | } else { |
| 282 | | last_atom_index.* = 0; |
| 293 | last_atom_ref.* = .{}; |
| 283 | 294 | } |
| 284 | 295 | } |
| 285 | 296 | } |
| 286 | 297 | |
| 287 | | if (zo.atom(self.prev_index)) |prev| { |
| 288 | | prev.next_index = self.next_index; |
| 289 | | if (!already_have_free_list_node and prev.*.freeListEligible(elf_file)) { |
| 298 | if (self.prevAtom(elf_file)) |prev_atom| { |
| 299 | prev_atom.next_atom_ref = self.next_atom_ref; |
| 300 | if (!already_have_free_list_node and prev_atom.*.freeListEligible(elf_file)) { |
| 290 | 301 | // The free list is heuristics, it doesn't have to be perfect, so we can |
| 291 | 302 | // ignore the OOM here. |
| 292 | | free_list.append(gpa, prev.atom_index) catch {}; |
| 303 | free_list.append(gpa, prev_atom.ref()) catch {}; |
| 293 | 304 | } |
| 294 | 305 | } else { |
| 295 | | self.prev_index = 0; |
| 306 | self.prev_atom_ref = .{}; |
| 296 | 307 | } |
| 297 | 308 | |
| 298 | | if (zo.atom(self.next_index)) |next| { |
| 299 | | next.prev_index = self.prev_index; |
| 309 | if (self.nextAtom(elf_file)) |next_atom| { |
| 310 | next_atom.prev_atom_ref = self.prev_atom_ref; |
| 300 | 311 | } else { |
| 301 | | self.next_index = 0; |
| 312 | self.next_atom_ref = .{}; |
| 302 | 313 | } |
| 303 | 314 | |
| 304 | | // TODO create relocs free list |
| 305 | | self.freeRelocs(zo); |
| 306 | | // TODO figure out how to free input section mappind in ZigModule |
| 307 | | // const zig_object = elf_file.zigObjectPtr().? |
| 308 | | // assert(zig_object.atoms.swapRemove(self.atom_index)); |
| 315 | switch (self.file(elf_file).?) { |
| 316 | .zig_object => |zo| { |
| 317 | // TODO create relocs free list |
| 318 | self.freeRelocs(zo); |
| 319 | // TODO figure out how to free input section mappind in ZigModule |
| 320 | // const zig_object = elf_file.zigObjectPtr().? |
| 321 | // assert(zig_object.atoms.swapRemove(self.atom_index)); |
| 322 | }, |
| 323 | else => {}, |
| 324 | } |
| 309 | 325 | self.* = .{}; |
| 310 | 326 | } |
| 311 | 327 | |