diff --git a/src/link/Elf/file.zig b/src/link/Elf/file.zig index 2b3112e7aa..a401203ba7 100644 --- a/src/link/Elf/file.zig +++ b/src/link/Elf/file.zig @@ -99,6 +99,21 @@ pub const File = union(enum) { }; } + pub fn cies(file: File) []const Cie { + return switch (file) { + .zig_object => &[0]Cie{}, + .object => |x| x.cies.items, + inline else => unreachable, + }; + } + + pub fn symbol(file: File, ind: Symbol.Index) Symbol.Index { + return switch (file) { + .zig_object => |x| x.symbol(ind), + inline else => |x| x.symbols.items[ind], + }; + } + pub fn locals(file: File) []const Symbol.Index { return switch (file) { .linker_defined, .shared_object => &[0]Symbol.Index{}, @@ -196,6 +211,7 @@ const elf = std.elf; const Allocator = std.mem.Allocator; const Atom = @import("Atom.zig"); +const Cie = @import("eh_frame.zig").Cie; const Elf = @import("../Elf.zig"); const LinkerDefined = @import("LinkerDefined.zig"); const Object = @import("Object.zig"); diff --git a/src/link/Elf/gc.zig b/src/link/Elf/gc.zig index 26489d3d1a..ade2b75782 100644 --- a/src/link/Elf/gc.zig +++ b/src/link/Elf/gc.zig @@ -1,19 +1,27 @@ pub fn gcAtoms(elf_file: *Elf) !void { - var roots = std.ArrayList(*Atom).init(elf_file.base.allocator); + const gpa = elf_file.base.allocator; + const num_files = elf_file.objects.items.len + @intFromBool(elf_file.zig_object_index != null); + var files = try std.ArrayList(File.Index).initCapacity(gpa, num_files); + defer files.deinit(); + if (elf_file.zig_object_index) |index| files.appendAssumeCapacity(index); + for (elf_file.objects.items) |index| files.appendAssumeCapacity(index); + + var roots = std.ArrayList(*Atom).init(gpa); defer roots.deinit(); - try collectRoots(&roots, elf_file); + try collectRoots(&roots, files.items, elf_file); + mark(roots, elf_file); - prune(elf_file); + prune(files.items, elf_file); } -fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void { +fn collectRoots(roots: *std.ArrayList(*Atom), files: []const File.Index, elf_file: *Elf) !void { if (elf_file.entry_index) |index| { const global = elf_file.symbol(index); try markSymbol(global, roots, elf_file); } - for (elf_file.objects.items) |index| { - for (elf_file.file(index).?.object.globals()) |global_index| { + for (files) |index| { + for (elf_file.file(index).?.globals()) |global_index| { const global = elf_file.symbol(global_index); if (global.file(elf_file)) |file| { if (file.index() == index and global.flags.@"export") @@ -22,10 +30,10 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void { } } - for (elf_file.objects.items) |index| { - const object = elf_file.file(index).?.object; + for (files) |index| { + const file = elf_file.file(index).?; - for (object.atoms.items) |atom_index| { + for (file.atoms()) |atom_index| { const atom = elf_file.atom(atom_index) orelse continue; if (!atom.flags.alive) continue; @@ -49,9 +57,9 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void { } // Mark every atom referenced by CIE as alive. - for (object.cies.items) |cie| { + for (file.cies()) |cie| { for (cie.relocs(elf_file)) |rel| { - const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]); + const sym = elf_file.symbol(file.symbol(rel.r_sym())); try markSymbol(sym, roots, elf_file); } } @@ -73,11 +81,11 @@ fn markLive(atom: *Atom, elf_file: *Elf) void { if (@import("build_options").enable_logging) track_live_level.incr(); assert(atom.flags.visited); - const object = atom.file(elf_file).?.object; + const file = atom.file(elf_file).?; for (atom.fdes(elf_file)) |fde| { for (fde.relocs(elf_file)[1..]) |rel| { - const target_sym = elf_file.symbol(object.symbols.items[rel.r_sym()]); + const target_sym = elf_file.symbol(file.symbol(rel.r_sym())); const target_atom = target_sym.atom(elf_file) orelse continue; target_atom.flags.alive = true; gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index }); @@ -86,7 +94,7 @@ fn markLive(atom: *Atom, elf_file: *Elf) void { } for (atom.relocs(elf_file)) |rel| { - const target_sym = elf_file.symbol(object.symbols.items[rel.r_sym()]); + const target_sym = elf_file.symbol(file.symbol(rel.r_sym())); const target_atom = target_sym.atom(elf_file) orelse continue; target_atom.flags.alive = true; gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index }); @@ -101,9 +109,9 @@ fn mark(roots: std.ArrayList(*Atom), elf_file: *Elf) void { } } -fn prune(elf_file: *Elf) void { - for (elf_file.objects.items) |index| { - for (elf_file.file(index).?.object.atoms.items) |atom_index| { +fn prune(files: []const File.Index, elf_file: *Elf) void { + for (files) |index| { + for (elf_file.file(index).?.atoms()) |atom_index| { const atom = elf_file.atom(atom_index) orelse continue; if (atom.flags.alive and !atom.flags.visited) { atom.flags.alive = false; @@ -158,4 +166,5 @@ const mem = std.mem; const Allocator = mem.Allocator; const Atom = @import("Atom.zig"); const Elf = @import("../Elf.zig"); +const File = @import("file.zig").File; const Symbol = @import("Symbol.zig"); diff --git a/test/link/elf.zig b/test/link/elf.zig index dad3604c31..766d1980e2 100644 --- a/test/link/elf.zig +++ b/test/link/elf.zig @@ -6,9 +6,13 @@ pub fn build(b: *Build) void { const elf_step = b.step("test-elf", "Run ELF tests"); b.default_step = elf_step; - const musl_target = CrossTarget{ + const default_target = CrossTarget{ .cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs .os_tag = .linux, + }; + const musl_target = CrossTarget{ + .cpu_arch = .x86_64, + .os_tag = .linux, .abi = .musl, }; const glibc_target = CrossTarget{ @@ -18,7 +22,8 @@ pub fn build(b: *Build) void { }; // Exercise linker with self-hosted backend (no LLVM) - elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false })); + elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target })); + elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false, .target = default_target })); elf_step.dependOn(testImportingDataDynamic(b, .{ .use_llvm = false, .target = glibc_target })); elf_step.dependOn(testImportingDataStatic(b, .{ .use_llvm = false, .target = musl_target })); @@ -876,6 +881,110 @@ fn testGcSections(b: *Build, opts: Options) *Step { return test_step; } +fn testGcSectionsZig(b: *Build, opts: Options) *Step { + const test_step = addTestStep(b, "gc-sections-zig", opts); + + const obj = addObject(b, "obj", .{ + .target = opts.target, + .use_llvm = true, + .use_lld = true, + }); + addCSourceBytes(obj, + \\int live_var1 = 1; + \\int live_var2 = 2; + \\int dead_var1 = 3; + \\int dead_var2 = 4; + \\void live_fn1() {} + \\void live_fn2() { live_fn1(); } + \\void dead_fn1() {} + \\void dead_fn2() { dead_fn1(); } + , &.{}); + obj.link_function_sections = true; + obj.link_data_sections = true; + + { + const exe = addExecutable(b, "test1", opts); + addZigSourceBytes(exe, + \\const std = @import("std"); + \\extern var live_var1: i32; + \\extern var live_var2: i32; + \\extern fn live_fn2() void; + \\pub fn main() void { + \\ const stdout = std.io.getStdOut(); + \\ stdout.writer().print("{d} {d}\n", .{ live_var1, live_var2 }) catch unreachable; + \\ live_fn2(); + \\} + ); + exe.addObject(obj); + exe.link_gc_sections = false; + + 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 = addExecutable(b, "test2", opts); + addZigSourceBytes(exe, + \\const std = @import("std"); + \\extern var live_var1: i32; + \\extern var live_var2: i32; + \\extern fn live_fn2() void; + \\pub fn main() void { + \\ const stdout = std.io.getStdOut(); + \\ stdout.writer().print("{d} {d}\n", .{ live_var1, live_var2 }) catch unreachable; + \\ live_fn2(); + \\} + ); + exe.addObject(obj); + exe.link_gc_sections = 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.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 testHiddenWeakUndef(b: *Build, opts: Options) *Step { const test_step = addTestStep(b, "hidden-weak-undef", opts); @@ -3114,6 +3223,7 @@ const Options = struct { target: CrossTarget = .{ .cpu_arch = .x86_64, .os_tag = .linux }, optimize: std.builtin.OptimizeMode = .Debug, use_llvm: bool = true, + use_lld: bool = false, }; fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step { @@ -3134,7 +3244,7 @@ fn addExecutable(b: *Build, name: []const u8, opts: Options) *Compile { .target = opts.target, .optimize = opts.optimize, .use_llvm = opts.use_llvm, - .use_lld = false, + .use_lld = opts.use_lld, }); } @@ -3144,7 +3254,7 @@ fn addObject(b: *Build, name: []const u8, opts: Options) *Compile { .target = opts.target, .optimize = opts.optimize, .use_llvm = opts.use_llvm, - .use_lld = false, + .use_lld = opts.use_lld, }); } @@ -3164,7 +3274,7 @@ fn addSharedLibrary(b: *Build, name: []const u8, opts: Options) *Compile { .target = opts.target, .optimize = opts.optimize, .use_llvm = opts.use_llvm, - .use_lld = false, + .use_lld = opts.use_lld, }); }