diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 22f8d9c89..b2e81916f 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1019,7 +1019,18 @@ pub fn deleteExport(self: *MachO, exp: Export) void { self.global_symbols.items[sym_index].n_type = 0; } -pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} +pub fn freeDecl(self: *MachO, decl: *Module.Decl) void { + // Appending to free lists is allowed to fail because the free lists are heuristics based anyway. + self.freeTextBlock(&decl.link.macho); + if (decl.link.macho.local_sym_index != 0) { + self.local_symbol_free_list.append(self.base.allocator, decl.link.macho.local_sym_index) catch {}; + self.offset_table_free_list.append(self.base.allocator, decl.link.macho.offset_table_index) catch {}; + + self.local_symbols.items[decl.link.macho.local_sym_index].n_type = 0; + + decl.link.macho.local_sym_index = 0; + } +} pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { assert(decl.link.macho.local_sym_index != 0); @@ -1341,18 +1352,63 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, const text_section = &self.sections.items[self.text_section_index.?]; const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den; + // We use these to indicate our intention to update metadata, placing the new block, + // 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; - const addr = blk: { - if (self.last_text_block) |last| { + 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. + const vaddr = blk: { + var i: usize = 0; + while (i < self.text_block_free_list.items.len) { + const big_block = self.text_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 = self.local_symbols.items[big_block.local_sym_index]; + const capacity = big_block.capacity(self.*); + const ideal_capacity = capacity * alloc_num / alloc_den; + const ideal_capacity_end_vaddr = sym.n_value + ideal_capacity; + const capacity_end_vaddr = sym.n_value + capacity; + const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_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.*)) { + _ = self.text_block_free_list.swapRemove(i); + } else { + i += 1; + } + continue; + } + // At this point we know that we will place the new block here. But the + // remaining question is whether there is still yet enough capacity left + // over for there to still be a free list node. + const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr; + 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; + if (!keep_free_list_node) { + free_list_removal = i; + } + break :blk new_start_vaddr; + } + else if (self.last_text_block) |last| { const last_symbol = self.local_symbols.items[last.local_sym_index]; // TODO pad out with NOPs and reenable // const ideal_capacity = last.size * alloc_num / alloc_den; // const ideal_capacity_end_addr = last_symbol.n_value + ideal_capacity; // const new_start_addr = mem.alignForwardGeneric(u64, ideal_capacity_end_addr, alignment); - const end_addr = last_symbol.n_value + last.size; - const new_start_addr = mem.alignForwardGeneric(u64, end_addr, alignment); + const end_vaddr = last_symbol.n_value + last.size; + const new_start_vaddr = mem.alignForwardGeneric(u64, end_vaddr, alignment); block_placement = last; - break :blk new_start_addr; + break :blk new_start_vaddr; } else { break :blk text_section.addr; } @@ -1361,11 +1417,13 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, const expand_text_section = block_placement == null or block_placement.?.next == null; if (expand_text_section) { const text_capacity = self.allocatedSize(text_section.offset); - const needed_size = (addr + new_block_size) - text_section.addr; - assert(needed_size <= text_capacity); // TODO handle growth + const needed_size = (vaddr + new_block_size) - text_section.addr; + assert(needed_size <= text_capacity); // TODO must move the entire text section. self.last_text_block = text_block; text_section.size = needed_size; // TODO temp until we pad out with NOPs + + self.cmd_table_dirty = true; } text_block.size = new_block_size; @@ -1384,8 +1442,11 @@ fn allocateTextBlock(self: *MachO, text_block: *TextBlock, new_block_size: u64, text_block.prev = null; text_block.next = null; } + if (free_list_removal) |i| { + _ = self.text_block_free_list.swapRemove(i); + } - return addr; + return vaddr; } fn makeStaticString(comptime bytes: []const u8) [16]u8 {