Commit d05db52616

Jakub Konka <kubkon@jakubkonka.com>
2023-12-05 17:29:26
elf: copy out committed ZigObject to a buffer when creating static lib
1 parent ab423bd
Changed files (4)
src/link/Elf/file.zig
@@ -162,17 +162,17 @@ pub const File = union(enum) {
         state.name_off = try ar_strtab.insert(allocator, path);
     }
 
-    pub fn updateArSize(file: File, elf_file: *Elf) void {
+    pub fn updateArSize(file: File) void {
         return switch (file) {
-            .zig_object => |x| x.updateArSize(elf_file),
+            .zig_object => |x| x.updateArSize(),
             .object => |x| x.updateArSize(),
             inline else => unreachable,
         };
     }
 
-    pub fn writeAr(file: File, elf_file: *Elf, writer: anytype) !void {
+    pub fn writeAr(file: File, writer: anytype) !void {
         return switch (file) {
-            .zig_object => |x| x.writeAr(elf_file, writer),
+            .zig_object => |x| x.writeAr(writer),
             .object => |x| x.writeAr(writer),
             inline else => unreachable,
         };
src/link/Elf/ZigObject.zig
@@ -3,6 +3,7 @@
 //! and any relocations that may have been emitted.
 //! Think about this as fake in-memory Object file for the Zig module.
 
+data: std.ArrayListUnmanaged(u8) = .{},
 path: []const u8,
 index: File.Index,
 
@@ -101,6 +102,7 @@ pub fn init(self: *ZigObject, elf_file: *Elf) !void {
 }
 
 pub fn deinit(self: *ZigObject, allocator: Allocator) void {
+    self.data.deinit(allocator);
     allocator.free(self.path);
     self.local_esyms.deinit(allocator);
     self.global_esyms.deinit(allocator);
@@ -441,6 +443,27 @@ pub fn markLive(self: *ZigObject, elf_file: *Elf) void {
     }
 }
 
+/// This is just a temporary helper function that allows us to re-read what we wrote to file into a buffer.
+/// We need this so that we can write to an archive.
+/// TODO implement writing ZigObject data directly to a buffer instead.
+pub fn readFileContents(self: *ZigObject, elf_file: *Elf) !void {
+    const gpa = elf_file.base.allocator;
+    const shsize: u64 = switch (elf_file.ptr_width) {
+        .p32 => @sizeOf(elf.Elf32_Shdr),
+        .p64 => @sizeOf(elf.Elf64_Shdr),
+    };
+    var end_pos: u64 = elf_file.shdr_table_offset.? + elf_file.shdrs.items.len * shsize;
+    for (elf_file.shdrs.items) |shdr| {
+        if (shdr.sh_type == elf.SHT_NOBITS) continue;
+        end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
+    }
+    const size = std.math.cast(usize, end_pos) orelse return error.Overflow;
+    try self.data.resize(gpa, size);
+
+    const amt = try elf_file.base.file.?.preadAll(self.data.items, 0);
+    if (amt != size) return error.InputOutput;
+}
+
 pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *Elf) error{OutOfMemory}!void {
     const gpa = elf_file.base.allocator;
 
@@ -457,34 +480,21 @@ pub fn updateArSymtab(self: ZigObject, ar_symtab: *Archive.ArSymtab, elf_file: *
     }
 }
 
-pub fn updateArSize(self: *ZigObject, elf_file: *Elf) void {
-    var end_pos: u64 = elf_file.shdr_table_offset.?;
-    for (elf_file.shdrs.items) |shdr| {
-        end_pos = @max(end_pos, shdr.sh_offset + shdr.sh_size);
-    }
-    self.output_ar_state.size = end_pos;
+pub fn updateArSize(self: *ZigObject) void {
+    self.output_ar_state.size = self.data.items.len;
 }
 
-pub fn writeAr(self: ZigObject, elf_file: *Elf, writer: anytype) !void {
-    const gpa = elf_file.base.allocator;
-
-    const size = std.math.cast(usize, self.output_ar_state.size) orelse return error.Overflow;
-    const contents = try gpa.alloc(u8, size);
-    defer gpa.free(contents);
-
-    const amt = try elf_file.base.file.?.preadAll(contents, 0);
-    if (amt != self.output_ar_state.size) return error.InputOutput;
-
+pub fn writeAr(self: ZigObject, writer: anytype) !void {
     const name = self.path;
     const hdr = Archive.setArHdr(.{
         .name = if (name.len <= Archive.max_member_name_len)
             .{ .name = name }
         else
             .{ .name_off = self.output_ar_state.name_off },
-        .size = @intCast(size),
+        .size = @intCast(self.data.items.len),
     });
     try writer.writeAll(mem.asBytes(&hdr));
-    try writer.writeAll(contents);
+    try writer.writeAll(self.data.items);
 }
 
 pub fn addAtomsToRelaSections(self: ZigObject, elf_file: *Elf) !void {
src/link/Elf.zig
@@ -1344,23 +1344,25 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const
         try zig_object.addAtomsToRelaSections(self);
         try self.updateSectionSizesObject();
 
+        try self.allocateAllocSectionsObject();
         try self.allocateNonAllocSections();
 
         if (build_options.enable_logging) {
-            state_log.debug("{}", .{self.dumpState()});
+            log.debug("{}", .{self.dumpState()});
         }
 
         try self.writeSyntheticSectionsObject();
         try self.writeShdrTable();
         try self.writeElfHeader();
+
+        // TODO we can avoid reading in the file contents we just wrote if we give the linker
+        // ability to write directly to a buffer.
+        try zig_object.readFileContents(self);
     }
 
     var files = std.ArrayList(File.Index).init(gpa);
     defer files.deinit();
     try files.ensureTotalCapacityPrecise(self.objects.items.len + 1);
-    // Note to self: we currently must have ZigObject written out first as we write the object
-    // file into the same file descriptor and then re-read its contents.
-    // TODO implement writing ZigObject to a buffer instead of file.
     if (self.zigObjectPtr()) |zig_object| files.appendAssumeCapacity(zig_object.index);
     for (self.objects.items) |index| files.appendAssumeCapacity(index);
 
@@ -1381,7 +1383,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const
     for (files.items) |index| {
         const file_ptr = self.file(index).?;
         try file_ptr.updateArStrtab(gpa, &ar_strtab);
-        file_ptr.updateArSize(self);
+        file_ptr.updateArSize();
     }
 
     // Update file offsets of contributing objects.
@@ -1433,7 +1435,7 @@ pub fn flushStaticLib(self: *Elf, comp: *Compilation, module_obj_path: ?[]const
     // Write object files
     for (files.items) |index| {
         if (!mem.isAligned(buffer.items.len, 2)) try buffer.writer().writeByte(0);
-        try self.file(index).?.writeAr(self, buffer.writer());
+        try self.file(index).?.writeAr(buffer.writer());
     }
 
     assert(buffer.items.len == total_size);
@@ -2970,6 +2972,7 @@ fn writeShdrTable(self: *Elf) !void {
             defer gpa.free(buf);
 
             for (buf, 0..) |*shdr, i| {
+                assert(self.shdrs.items[i].sh_offset != math.maxInt(u64));
                 shdr.* = shdrTo32(self.shdrs.items[i]);
                 if (foreign_endian) {
                     mem.byteSwapAllFields(elf.Elf32_Shdr, shdr);
@@ -2982,6 +2985,7 @@ fn writeShdrTable(self: *Elf) !void {
             defer gpa.free(buf);
 
             for (buf, 0..) |*shdr, i| {
+                assert(self.shdrs.items[i].sh_offset != math.maxInt(u64));
                 shdr.* = self.shdrs.items[i];
                 if (foreign_endian) {
                     mem.byteSwapAllFields(elf.Elf64_Shdr, shdr);
test/link/elf.zig
@@ -29,6 +29,7 @@ pub fn testAll(b: *Build) *Step {
 
     // Exercise linker in ar mode
     elf_step.dependOn(testEmitStaticLib(b, .{ .target = musl_target }));
+    elf_step.dependOn(testEmitStaticLibZig(b, .{ .use_llvm = false, .target = musl_target }));
 
     // Exercise linker with self-hosted backend (no LLVM)
     elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target }));
@@ -743,6 +744,42 @@ fn testEmitStaticLib(b: *Build, opts: Options) *Step {
     return test_step;
 }
 
+fn testEmitStaticLibZig(b: *Build, opts: Options) *Step {
+    const test_step = addTestStep(b, "emit-static-lib-zig", opts);
+
+    const obj1 = addObject(b, "obj1", opts);
+    addZigSourceBytes(obj1,
+        \\export var foo: i32 = 42;
+        \\export var bar: i32 = 2;
+    );
+
+    const lib = addStaticLibrary(b, "lib", opts);
+    addZigSourceBytes(lib,
+        \\extern var foo: i32;
+        \\extern var bar: i32;
+        \\export fn fooBar() i32 {
+        \\  return foo + bar;
+        \\}
+    );
+    lib.addObject(obj1);
+
+    const exe = addExecutable(b, "test", opts);
+    addZigSourceBytes(exe,
+        \\const std = @import("std");
+        \\extern fn fooBar() i32;
+        \\pub fn main() void {
+        \\  std.debug.print("{d}", .{fooBar()});
+        \\}
+    );
+    exe.linkLibrary(lib);
+
+    const run = addRunArtifact(exe);
+    run.expectStdErrEqual("44");
+    test_step.dependOn(&run.step);
+
+    return test_step;
+}
+
 fn testEmptyObject(b: *Build, opts: Options) *Step {
     const test_step = addTestStep(b, "empty-object", opts);