Commit f1b9c365f2

Jakub Konka <kubkon@jakubkonka.com>
2023-10-04 13:06:26
elf: add incomplete handling of build-obj -fllvm -fno-lld
1 parent 976d4f5
Changed files (2)
src
link
test
link
src/link/Elf.zig
@@ -1069,10 +1069,11 @@ pub fn flush(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) link
     if (use_lld) {
         return self.linkWithLLD(comp, prog_node);
     }
-    switch (self.base.options.output_mode) {
-        .Exe, .Obj => return self.flushModule(comp, prog_node),
-        .Lib => return error.TODOImplementWritingLibFiles,
+    if (self.base.options.output_mode == .Lib and self.isStatic()) {
+        // TODO writing static library files
+        return error.TODOImplementWritingLibFiles;
     }
+    try self.flushModule(comp, prog_node);
 }
 
 pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) link.File.FlushError!void {
@@ -1098,21 +1099,44 @@ pub fn flushModule(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node
     const target = self.base.options.target;
     const directory = self.base.options.emit.?.directory; // Just an alias to make it shorter to type.
     const full_out_path = try directory.join(arena, &[_][]const u8{self.base.options.emit.?.sub_path});
+    const module_obj_path: ?[]const u8 = if (self.base.intermediary_basename) |path| blk: {
+        if (fs.path.dirname(full_out_path)) |dirname| {
+            break :blk try fs.path.join(arena, &.{ dirname, path });
+        } else {
+            break :blk path;
+        }
+    } else null;
+
+    if (self.base.options.output_mode == .Obj and self.zig_module_index == null) {
+        // TODO this will become -r route I guess. For now, just copy the object file.
+        const the_object_path = blk: {
+            if (self.base.options.objects.len != 0) {
+                break :blk self.base.options.objects[0].path;
+            }
+
+            if (comp.c_object_table.count() != 0)
+                break :blk comp.c_object_table.keys()[0].status.success.object_path;
+
+            if (module_obj_path) |p|
+                break :blk p;
+
+            // TODO I think this is unreachable. Audit this situation when solving the above TODO
+            // regarding eliding redundant object -> object transformations.
+            return error.NoObjectsToLink;
+        };
+        // This can happen when using --enable-cache and using the stage1 backend. In this case
+        // we can skip the file copy.
+        if (!mem.eql(u8, the_object_path, full_out_path)) {
+            try fs.cwd().copyFile(the_object_path, fs.cwd(), full_out_path, .{});
+        }
+        return;
+    }
 
     // Here we will parse input positional and library files (if referenced).
     // This will roughly match in any linker backend we support.
     var positionals = std.ArrayList(Compilation.LinkObject).init(arena);
 
-    if (self.base.intermediary_basename) |path| {
-        const full_path = blk: {
-            if (fs.path.dirname(full_out_path)) |dirname| {
-                break :blk try fs.path.join(arena, &.{ dirname, path });
-            } else {
-                break :blk path;
-            }
-        };
-        try positionals.append(.{ .path = full_path });
-    }
+    if (module_obj_path) |path| try positionals.append(.{ .path = path });
 
     try positionals.ensureUnusedCapacity(self.base.options.objects.len);
     positionals.appendSliceAssumeCapacity(self.base.options.objects);
test/link/elf.zig
@@ -17,6 +17,7 @@ pub fn build(b: *Build) void {
 
     // Exercise linker with LLVM backend
     elf_step.dependOn(testEmptyObject(b, .{ .target = musl_target }));
+    elf_step.dependOn(testGcSections(b, .{ .target = musl_target }));
     elf_step.dependOn(testLinkingC(b, .{ .target = musl_target }));
     elf_step.dependOn(testLinkingCpp(b, .{ .target = musl_target }));
     elf_step.dependOn(testLinkingZig(b, .{ .target = musl_target }));
@@ -38,6 +39,93 @@ fn testEmptyObject(b: *Build, opts: Options) *Step {
     return test_step;
 }
 
+fn testGcSections(b: *Build, opts: Options) *Step {
+    const test_step = addTestStep(b, "gc-sections", opts);
+
+    const obj = addObject(b, opts);
+    addCppSourceBytes(obj,
+        \\#include <stdio.h>
+        \\int two() { return 2; }
+        \\int live_var1 = 1;
+        \\int live_var2 = two();
+        \\int dead_var1 = 3;
+        \\int dead_var2 = 4;
+        \\void live_fn1() {}
+        \\void live_fn2() { live_fn1(); }
+        \\void dead_fn1() {}
+        \\void dead_fn2() { dead_fn1(); }
+        \\int main() {
+        \\  printf("%d %d\n", live_var1, live_var2);
+        \\  live_fn2();
+        \\}
+    );
+    obj.link_function_sections = true;
+    obj.is_linking_libc = true;
+    obj.is_linking_libcpp = true;
+
+    {
+        const exe = addExecutable(b, opts);
+        exe.addObject(obj);
+        exe.link_gc_sections = false;
+        exe.is_linking_libc = true;
+        exe.is_linking_libcpp = true;
+
+        const run = addRunArtifact(exe);
+        run.expectStdOutEqual("1 2\n");
+        test_step.dependOn(&run.step);
+
+        const check = exe.checkObject();
+        check.checkInSymtab();
+        check.checkContains("live_var1");
+        check.checkInSymtab();
+        check.checkContains("live_var2");
+        check.checkInSymtab();
+        check.checkContains("dead_var1");
+        check.checkInSymtab();
+        check.checkContains("dead_var2");
+        check.checkInSymtab();
+        check.checkContains("live_fn1");
+        check.checkInSymtab();
+        check.checkContains("live_fn2");
+        check.checkInSymtab();
+        check.checkContains("dead_fn1");
+        check.checkInSymtab();
+        check.checkContains("dead_fn2");
+        test_step.dependOn(&check.step);
+    }
+
+    // {
+    //     const exe = cc(b, opts);
+    //     exe.addFileSource(obj_out.file);
+    //     exe.addArg("-Wl,-gc-sections");
+
+    //     const run = exe.run();
+    //     run.expectStdOutEqual("1 2\n");
+    //     test_step.dependOn(run.step());
+
+    //     const check = exe.check();
+    //     check.checkInSymtab();
+    //     check.checkContains("live_var1");
+    //     check.checkInSymtab();
+    //     check.checkContains("live_var2");
+    //     check.checkInSymtab();
+    //     check.checkNotPresent("dead_var1");
+    //     check.checkInSymtab();
+    //     check.checkNotPresent("dead_var2");
+    //     check.checkInSymtab();
+    //     check.checkContains("live_fn1");
+    //     check.checkInSymtab();
+    //     check.checkContains("live_fn2");
+    //     check.checkInSymtab();
+    //     check.checkNotPresent("dead_fn1");
+    //     check.checkInSymtab();
+    //     check.checkNotPresent("dead_fn2");
+    //     test_step.dependOn(&check.step);
+    // }
+
+    return test_step;
+}
+
 fn testLinkingC(b: *Build, opts: Options) *Step {
     const test_step = addTestStep(b, "linking-c", opts);
 
@@ -182,6 +270,16 @@ fn addExecutable(b: *Build, opts: Options) *Compile {
     });
 }
 
+fn addObject(b: *Build, opts: Options) *Compile {
+    return b.addObject(.{
+        .name = "a.o",
+        .target = opts.target,
+        .optimize = opts.optimize,
+        .use_llvm = opts.use_llvm,
+        .use_lld = false,
+    });
+}
+
 fn addRunArtifact(comp: *Compile) *Run {
     const b = comp.step.owner;
     const run = b.addRunArtifact(comp);