Commit dc222c9ba5

Jakub Konka <kubkon@jakubkonka.com>
2024-02-02 20:26:50
macho: use findFreeSpace for all sections
1 parent c515517
Changed files (2)
src/link/MachO/relocatable.zig
@@ -59,8 +59,7 @@ pub fn flush(macho_file: *MachO, comp: *Compilation, module_obj_path: ?[]const u
     try calcSectionSizes(macho_file);
 
     try createSegment(macho_file);
-    try allocateSectionsVM(macho_file);
-    try allocateSectionsFile(macho_file);
+    try allocateSections(macho_file);
     allocateSegment(macho_file);
     macho_file.allocateAtoms();
 
@@ -224,58 +223,20 @@ fn calcCompactUnwindSize(macho_file: *MachO, sect_index: u8) void {
     sect.@"align" = 3;
 }
 
-fn allocateSectionsVM(macho_file: *MachO) !void {
-    var vmaddr: u64 = 0;
-    const slice = macho_file.sections.slice();
-
-    for (slice.items(.header)) |*header| {
-        const alignment = try math.powi(u32, 2, header.@"align");
-        vmaddr = mem.alignForward(u64, vmaddr, alignment);
-        header.addr = vmaddr;
-        vmaddr += header.size;
-    }
-}
-
-fn allocateSectionsFile(macho_file: *MachO) !void {
-    var fileoff = load_commands.calcLoadCommandsSizeObject(macho_file) + @sizeOf(macho.mach_header_64);
+fn allocateSections(macho_file: *MachO) !void {
     const slice = macho_file.sections.slice();
 
     const last_index = for (slice.items(.header), 0..) |header, i| {
         if (mem.indexOf(u8, header.segName(), "ZIG")) |_| break i;
     } else slice.items(.header).len;
 
-    // TODO: I actually think for relocatable we can just use findFreeSpace
-    // all the way since there is a single segment involved anyhow.
     for (slice.items(.header)[0..last_index]) |*header| {
-        if (header.isZerofill()) continue;
         const alignment = try math.powi(u32, 2, header.@"align");
-        fileoff = mem.alignForward(u32, fileoff, alignment);
-        header.offset = fileoff;
-        fileoff += @intCast(header.size);
-    }
-
-    for (slice.items(.header)[last_index..]) |*header| {
-        if (header.isZerofill()) continue;
-        if (header.offset < fileoff) {
-            const existing_size = header.size;
-            header.size = 0;
-
-            // Must move the entire section.
-            const alignment = try math.powi(u32, 2, header.@"align");
-            const new_offset = macho_file.findFreeSpace(existing_size, alignment);
-
-            log.debug("new '{s},{s}' file offset 0x{x} to 0x{x}", .{
-                header.segName(),
-                header.sectName(),
-                new_offset,
-                new_offset + existing_size,
-            });
-
-            try macho_file.copyRangeAll(header.offset, new_offset, existing_size);
-
-            header.offset = @intCast(new_offset);
-            header.size = existing_size;
+        if (!header.isZerofill()) {
+            header.offset = math.cast(u32, macho_file.findFreeSpace(header.size, alignment)) orelse
+                return error.Overflow;
         }
+        header.addr = macho_file.findFreeSpaceVirtual(header.size, alignment);
     }
 }
 
@@ -308,7 +269,6 @@ fn allocateSegment(macho_file: *MachO) void {
         if (!header.isZerofill()) {
             fileoff = @max(fileoff, header.offset + header.size);
         }
-        std.debug.print("fileoff={x},vmaddr={x}\n", .{ fileoff, vmaddr });
     }
 
     seg.vmsize = vmaddr - seg.vmaddr;
src/link/MachO.zig
@@ -3275,6 +3275,34 @@ fn detectAllocCollision(self: *MachO, start: u64, size: u64) ?u64 {
     return null;
 }
 
+fn detectAllocCollisionVirtual(self: *MachO, start: u64, size: u64) ?u64 {
+    // Conservatively commit one page size as reserved space for the headers as we
+    // expect it to grow and everything else be moved in flush anyhow.
+    const header_size = self.getPageSize();
+    if (start < header_size)
+        return header_size;
+
+    const end = start + padToIdeal(size);
+
+    for (self.sections.items(.header)) |header| {
+        const increased_size = padToIdeal(header.size);
+        const test_end = header.addr + increased_size;
+        if (end > header.addr and start < test_end) {
+            return test_end;
+        }
+    }
+
+    for (self.segments.items) |seg| {
+        const increased_size = padToIdeal(seg.vmsize);
+        const test_end = seg.vmaddr +| increased_size;
+        if (end > seg.vmaddr and start < test_end) {
+            return test_end;
+        }
+    }
+
+    return null;
+}
+
 fn allocatedSize(self: *MachO, start: u64) u64 {
     if (start == 0) return 0;
     var min_pos: u64 = std.math.maxInt(u64);
@@ -3307,6 +3335,14 @@ pub fn findFreeSpace(self: *MachO, object_size: u64, min_alignment: u32) u64 {
     return start;
 }
 
+pub fn findFreeSpaceVirtual(self: *MachO, object_size: u64, min_alignment: u32) u64 {
+    var start: u64 = 0;
+    while (self.detectAllocCollisionVirtual(start, object_size)) |item_end| {
+        start = mem.alignForward(u64, item_end, min_alignment);
+    }
+    return start;
+}
+
 pub fn copyRangeAll(self: *MachO, old_offset: u64, new_offset: u64, size: u64) !void {
     const file = self.base.file.?;
     const amt = try file.copyRangeAll(old_offset, file, new_offset, size);
@@ -3411,7 +3447,11 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
         fn allocSect(macho_file: *MachO, sect_id: u8, size: u64) !void {
             const sect = &macho_file.sections.items(.header)[sect_id];
             const alignment = try math.powi(u32, 2, sect.@"align");
-            sect.offset = @intCast(macho_file.findFreeSpace(size, alignment));
+            if (!sect.isZerofill()) {
+                sect.offset = math.cast(u32, macho_file.findFreeSpace(size, alignment)) orelse
+                    return error.Overflow;
+            }
+            sect.addr = macho_file.findFreeSpaceVirtual(size, alignment);
             sect.size = size;
         }
     }.allocSect;
@@ -3462,7 +3502,7 @@ fn initMetadata(self: *MachO, options: InitMetadataOptions) !void {
             .flags = macho.S_ZEROFILL,
         });
         if (self.base.isRelocatable()) {
-            self.sections.items(.header)[self.zig_bss_sect_index.?].size = 1024;
+            try allocSect(self, self.zig_bss_sect_index.?, 1024);
         } else {
             appendSect(self, self.zig_bss_sect_index.?, self.zig_bss_seg_index.?);
         }