authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-11-01 10:26:38+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2023-11-04 09:08:27+01:00
logb8c8565e93d3625e5eb46a3f64a461f86b8d8eb4
tree1e40889825b691397d0aed0e7349044b26fa41dc
parent25c53f08a6add493043a407ce15bc727dc33356d

elf: implement --gc-sections for non-LLVM Zig source


3 files changed, 157 insertions(+), 22 deletions(-)

src/link/Elf/file.zig+16
...@@ -99,6 +99,21 @@ pub const File = union(enum) {...@@ -99,6 +99,21 @@ pub const File = union(enum) {
99 };99 };
100 }100 }
101101
102 pub fn cies(file: File) []const Cie {
103 return switch (file) {
104 .zig_object => &[0]Cie{},
105 .object => |x| x.cies.items,
106 inline else => unreachable,
107 };
108 }
109
110 pub fn symbol(file: File, ind: Symbol.Index) Symbol.Index {
111 return switch (file) {
112 .zig_object => |x| x.symbol(ind),
113 inline else => |x| x.symbols.items[ind],
114 };
115 }
116
102 pub fn locals(file: File) []const Symbol.Index {117 pub fn locals(file: File) []const Symbol.Index {
103 return switch (file) {118 return switch (file) {
104 .linker_defined, .shared_object => &[0]Symbol.Index{},119 .linker_defined, .shared_object => &[0]Symbol.Index{},
...@@ -196,6 +211,7 @@ const elf = std.elf;...@@ -196,6 +211,7 @@ const elf = std.elf;
196211
197const Allocator = std.mem.Allocator;212const Allocator = std.mem.Allocator;
198const Atom = @import("Atom.zig");213const Atom = @import("Atom.zig");
214const Cie = @import("eh_frame.zig").Cie;
199const Elf = @import("../Elf.zig");215const Elf = @import("../Elf.zig");
200const LinkerDefined = @import("LinkerDefined.zig");216const LinkerDefined = @import("LinkerDefined.zig");
201const Object = @import("Object.zig");217const Object = @import("Object.zig");
src/link/Elf/gc.zig+26-17
...@@ -1,19 +1,27 @@...@@ -1,19 +1,27 @@
1pub fn gcAtoms(elf_file: *Elf) !void {1pub fn gcAtoms(elf_file: *Elf) !void {
2 var roots = std.ArrayList(*Atom).init(elf_file.base.allocator);2 const gpa = elf_file.base.allocator;
3 const num_files = elf_file.objects.items.len + @intFromBool(elf_file.zig_object_index != null);
4 var files = try std.ArrayList(File.Index).initCapacity(gpa, num_files);
5 defer files.deinit();
6 if (elf_file.zig_object_index) |index| files.appendAssumeCapacity(index);
7 for (elf_file.objects.items) |index| files.appendAssumeCapacity(index);
8
9 var roots = std.ArrayList(*Atom).init(gpa);
3 defer roots.deinit();10 defer roots.deinit();
4 try collectRoots(&roots, elf_file);11 try collectRoots(&roots, files.items, elf_file);
12
5 mark(roots, elf_file);13 mark(roots, elf_file);
6 prune(elf_file);14 prune(files.items, elf_file);
7}15}
816
9fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {17fn collectRoots(roots: *std.ArrayList(*Atom), files: []const File.Index, elf_file: *Elf) !void {
10 if (elf_file.entry_index) |index| {18 if (elf_file.entry_index) |index| {
11 const global = elf_file.symbol(index);19 const global = elf_file.symbol(index);
12 try markSymbol(global, roots, elf_file);20 try markSymbol(global, roots, elf_file);
13 }21 }
1422
15 for (elf_file.objects.items) |index| {23 for (files) |index| {
16 for (elf_file.file(index).?.object.globals()) |global_index| {24 for (elf_file.file(index).?.globals()) |global_index| {
17 const global = elf_file.symbol(global_index);25 const global = elf_file.symbol(global_index);
18 if (global.file(elf_file)) |file| {26 if (global.file(elf_file)) |file| {
19 if (file.index() == index and global.flags.@"export")27 if (file.index() == index and global.flags.@"export")
...@@ -22,10 +30,10 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {...@@ -22,10 +30,10 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {
22 }30 }
23 }31 }
2432
25 for (elf_file.objects.items) |index| {33 for (files) |index| {
26 const object = elf_file.file(index).?.object;34 const file = elf_file.file(index).?;
2735
28 for (object.atoms.items) |atom_index| {36 for (file.atoms()) |atom_index| {
29 const atom = elf_file.atom(atom_index) orelse continue;37 const atom = elf_file.atom(atom_index) orelse continue;
30 if (!atom.flags.alive) continue;38 if (!atom.flags.alive) continue;
3139
...@@ -49,9 +57,9 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {...@@ -49,9 +57,9 @@ fn collectRoots(roots: *std.ArrayList(*Atom), elf_file: *Elf) !void {
49 }57 }
5058
51 // Mark every atom referenced by CIE as alive.59 // Mark every atom referenced by CIE as alive.
52 for (object.cies.items) |cie| {60 for (file.cies()) |cie| {
53 for (cie.relocs(elf_file)) |rel| {61 for (cie.relocs(elf_file)) |rel| {
54 const sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);62 const sym = elf_file.symbol(file.symbol(rel.r_sym()));
55 try markSymbol(sym, roots, elf_file);63 try markSymbol(sym, roots, elf_file);
56 }64 }
57 }65 }
...@@ -73,11 +81,11 @@ fn markLive(atom: *Atom, elf_file: *Elf) void {...@@ -73,11 +81,11 @@ fn markLive(atom: *Atom, elf_file: *Elf) void {
73 if (@import("build_options").enable_logging) track_live_level.incr();81 if (@import("build_options").enable_logging) track_live_level.incr();
7482
75 assert(atom.flags.visited);83 assert(atom.flags.visited);
76 const object = atom.file(elf_file).?.object;84 const file = atom.file(elf_file).?;
7785
78 for (atom.fdes(elf_file)) |fde| {86 for (atom.fdes(elf_file)) |fde| {
79 for (fde.relocs(elf_file)[1..]) |rel| {87 for (fde.relocs(elf_file)[1..]) |rel| {
80 const target_sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);88 const target_sym = elf_file.symbol(file.symbol(rel.r_sym()));
81 const target_atom = target_sym.atom(elf_file) orelse continue;89 const target_atom = target_sym.atom(elf_file) orelse continue;
82 target_atom.flags.alive = true;90 target_atom.flags.alive = true;
83 gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index });91 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 {...@@ -86,7 +94,7 @@ fn markLive(atom: *Atom, elf_file: *Elf) void {
86 }94 }
8795
88 for (atom.relocs(elf_file)) |rel| {96 for (atom.relocs(elf_file)) |rel| {
89 const target_sym = elf_file.symbol(object.symbols.items[rel.r_sym()]);97 const target_sym = elf_file.symbol(file.symbol(rel.r_sym()));
90 const target_atom = target_sym.atom(elf_file) orelse continue;98 const target_atom = target_sym.atom(elf_file) orelse continue;
91 target_atom.flags.alive = true;99 target_atom.flags.alive = true;
92 gc_track_live_log.debug("{}marking live atom({d})", .{ track_live_level, target_atom.atom_index });100 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 {...@@ -101,9 +109,9 @@ fn mark(roots: std.ArrayList(*Atom), elf_file: *Elf) void {
101 }109 }
102}110}
103111
104fn prune(elf_file: *Elf) void {112fn prune(files: []const File.Index, elf_file: *Elf) void {
105 for (elf_file.objects.items) |index| {113 for (files) |index| {
106 for (elf_file.file(index).?.object.atoms.items) |atom_index| {114 for (elf_file.file(index).?.atoms()) |atom_index| {
107 const atom = elf_file.atom(atom_index) orelse continue;115 const atom = elf_file.atom(atom_index) orelse continue;
108 if (atom.flags.alive and !atom.flags.visited) {116 if (atom.flags.alive and !atom.flags.visited) {
109 atom.flags.alive = false;117 atom.flags.alive = false;
...@@ -158,4 +166,5 @@ const mem = std.mem;...@@ -158,4 +166,5 @@ const mem = std.mem;
158const Allocator = mem.Allocator;166const Allocator = mem.Allocator;
159const Atom = @import("Atom.zig");167const Atom = @import("Atom.zig");
160const Elf = @import("../Elf.zig");168const Elf = @import("../Elf.zig");
169const File = @import("file.zig").File;
161const Symbol = @import("Symbol.zig");170const Symbol = @import("Symbol.zig");
test/link/elf.zig+115-5
...@@ -6,9 +6,13 @@ pub fn build(b: *Build) void {...@@ -6,9 +6,13 @@ pub fn build(b: *Build) void {
6 const elf_step = b.step("test-elf", "Run ELF tests");6 const elf_step = b.step("test-elf", "Run ELF tests");
7 b.default_step = elf_step;7 b.default_step = elf_step;
88
9 const musl_target = CrossTarget{9 const default_target = CrossTarget{
10 .cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs10 .cpu_arch = .x86_64, // TODO relax this once ELF linker is able to handle other archs
11 .os_tag = .linux,11 .os_tag = .linux,
12 };
13 const musl_target = CrossTarget{
14 .cpu_arch = .x86_64,
15 .os_tag = .linux,
12 .abi = .musl,16 .abi = .musl,
13 };17 };
14 const glibc_target = CrossTarget{18 const glibc_target = CrossTarget{
...@@ -18,7 +22,8 @@ pub fn build(b: *Build) void {...@@ -18,7 +22,8 @@ pub fn build(b: *Build) void {
18 };22 };
1923
20 // Exercise linker with self-hosted backend (no LLVM)24 // Exercise linker with self-hosted backend (no LLVM)
21 elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false }));25 elf_step.dependOn(testGcSectionsZig(b, .{ .use_llvm = false, .target = default_target }));
26 elf_step.dependOn(testLinkingZig(b, .{ .use_llvm = false, .target = default_target }));
22 elf_step.dependOn(testImportingDataDynamic(b, .{ .use_llvm = false, .target = glibc_target }));27 elf_step.dependOn(testImportingDataDynamic(b, .{ .use_llvm = false, .target = glibc_target }));
23 elf_step.dependOn(testImportingDataStatic(b, .{ .use_llvm = false, .target = musl_target }));28 elf_step.dependOn(testImportingDataStatic(b, .{ .use_llvm = false, .target = musl_target }));
2429
...@@ -876,6 +881,110 @@ fn testGcSections(b: *Build, opts: Options) *Step {...@@ -876,6 +881,110 @@ fn testGcSections(b: *Build, opts: Options) *Step {
876 return test_step;881 return test_step;
877}882}
878883
884fn testGcSectionsZig(b: *Build, opts: Options) *Step {
885 const test_step = addTestStep(b, "gc-sections-zig", opts);
886
887 const obj = addObject(b, "obj", .{
888 .target = opts.target,
889 .use_llvm = true,
890 .use_lld = true,
891 });
892 addCSourceBytes(obj,
893 \\int live_var1 = 1;
894 \\int live_var2 = 2;
895 \\int dead_var1 = 3;
896 \\int dead_var2 = 4;
897 \\void live_fn1() {}
898 \\void live_fn2() { live_fn1(); }
899 \\void dead_fn1() {}
900 \\void dead_fn2() { dead_fn1(); }
901 , &.{});
902 obj.link_function_sections = true;
903 obj.link_data_sections = true;
904
905 {
906 const exe = addExecutable(b, "test1", opts);
907 addZigSourceBytes(exe,
908 \\const std = @import("std");
909 \\extern var live_var1: i32;
910 \\extern var live_var2: i32;
911 \\extern fn live_fn2() void;
912 \\pub fn main() void {
913 \\ const stdout = std.io.getStdOut();
914 \\ stdout.writer().print("{d} {d}\n", .{ live_var1, live_var2 }) catch unreachable;
915 \\ live_fn2();
916 \\}
917 );
918 exe.addObject(obj);
919 exe.link_gc_sections = false;
920
921 const run = addRunArtifact(exe);
922 run.expectStdOutEqual("1 2\n");
923 test_step.dependOn(&run.step);
924
925 const check = exe.checkObject();
926 check.checkInSymtab();
927 check.checkContains("live_var1");
928 check.checkInSymtab();
929 check.checkContains("live_var2");
930 check.checkInSymtab();
931 check.checkContains("dead_var1");
932 check.checkInSymtab();
933 check.checkContains("dead_var2");
934 check.checkInSymtab();
935 check.checkContains("live_fn1");
936 check.checkInSymtab();
937 check.checkContains("live_fn2");
938 check.checkInSymtab();
939 check.checkContains("dead_fn1");
940 check.checkInSymtab();
941 check.checkContains("dead_fn2");
942 test_step.dependOn(&check.step);
943 }
944
945 {
946 const exe = addExecutable(b, "test2", opts);
947 addZigSourceBytes(exe,
948 \\const std = @import("std");
949 \\extern var live_var1: i32;
950 \\extern var live_var2: i32;
951 \\extern fn live_fn2() void;
952 \\pub fn main() void {
953 \\ const stdout = std.io.getStdOut();
954 \\ stdout.writer().print("{d} {d}\n", .{ live_var1, live_var2 }) catch unreachable;
955 \\ live_fn2();
956 \\}
957 );
958 exe.addObject(obj);
959 exe.link_gc_sections = true;
960
961 const run = addRunArtifact(exe);
962 run.expectStdOutEqual("1 2\n");
963 test_step.dependOn(&run.step);
964
965 const check = exe.checkObject();
966 check.checkInSymtab();
967 check.checkContains("live_var1");
968 check.checkInSymtab();
969 check.checkContains("live_var2");
970 check.checkInSymtab();
971 check.checkNotPresent("dead_var1");
972 check.checkInSymtab();
973 check.checkNotPresent("dead_var2");
974 check.checkInSymtab();
975 check.checkContains("live_fn1");
976 check.checkInSymtab();
977 check.checkContains("live_fn2");
978 check.checkInSymtab();
979 check.checkNotPresent("dead_fn1");
980 check.checkInSymtab();
981 check.checkNotPresent("dead_fn2");
982 test_step.dependOn(&check.step);
983 }
984
985 return test_step;
986}
987
879fn testHiddenWeakUndef(b: *Build, opts: Options) *Step {988fn testHiddenWeakUndef(b: *Build, opts: Options) *Step {
880 const test_step = addTestStep(b, "hidden-weak-undef", opts);989 const test_step = addTestStep(b, "hidden-weak-undef", opts);
881990
...@@ -3114,6 +3223,7 @@ const Options = struct {...@@ -3114,6 +3223,7 @@ const Options = struct {
3114 target: CrossTarget = .{ .cpu_arch = .x86_64, .os_tag = .linux },3223 target: CrossTarget = .{ .cpu_arch = .x86_64, .os_tag = .linux },
3115 optimize: std.builtin.OptimizeMode = .Debug,3224 optimize: std.builtin.OptimizeMode = .Debug,
3116 use_llvm: bool = true,3225 use_llvm: bool = true,
3226 use_lld: bool = false,
3117};3227};
31183228
3119fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {3229fn addTestStep(b: *Build, comptime prefix: []const u8, opts: Options) *Step {
...@@ -3134,7 +3244,7 @@ fn addExecutable(b: *Build, name: []const u8, opts: Options) *Compile {...@@ -3134,7 +3244,7 @@ fn addExecutable(b: *Build, name: []const u8, opts: Options) *Compile {
3134 .target = opts.target,3244 .target = opts.target,
3135 .optimize = opts.optimize,3245 .optimize = opts.optimize,
3136 .use_llvm = opts.use_llvm,3246 .use_llvm = opts.use_llvm,
3137 .use_lld = false,3247 .use_lld = opts.use_lld,
3138 });3248 });
3139}3249}
31403250
...@@ -3144,7 +3254,7 @@ fn addObject(b: *Build, name: []const u8, opts: Options) *Compile {...@@ -3144,7 +3254,7 @@ fn addObject(b: *Build, name: []const u8, opts: Options) *Compile {
3144 .target = opts.target,3254 .target = opts.target,
3145 .optimize = opts.optimize,3255 .optimize = opts.optimize,
3146 .use_llvm = opts.use_llvm,3256 .use_llvm = opts.use_llvm,
3147 .use_lld = false,3257 .use_lld = opts.use_lld,
3148 });3258 });
3149}3259}
31503260
...@@ -3164,7 +3274,7 @@ fn addSharedLibrary(b: *Build, name: []const u8, opts: Options) *Compile {...@@ -3164,7 +3274,7 @@ fn addSharedLibrary(b: *Build, name: []const u8, opts: Options) *Compile {
3164 .target = opts.target,3274 .target = opts.target,
3165 .optimize = opts.optimize,3275 .optimize = opts.optimize,
3166 .use_llvm = opts.use_llvm,3276 .use_llvm = opts.use_llvm,
3167 .use_lld = false,3277 .use_lld = opts.use_lld,
3168 });3278 });
3169}3279}
31703280