authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-09 10:54:40-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-09-09 10:54:40-07:00
logb37955f2739dc20f65cc8d352cab98dc07f44b5f
tree715317e4651dd899020dc3880abebad9aa679c2a
parent193ad413f03322b047bbfe17c4b2b368ba6bc097

stage2 linker code supports opening an intermediate object file

For when linking with LLD, we always create an object rather than going straight to the executable. Next step is putting this object on the LLD linker line.

8 files changed, 78 insertions(+), 76 deletions(-)

src-self-hosted/Module.zig+6-7
......@@ -35,8 +35,6 @@ root_pkg: ?*Package,
3535/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`.
3636root_scope: *Scope,
3737bin_file: *link.File,
38bin_file_dir: std.fs.Dir,
39bin_file_path: []const u8,
4038/// It's rare for a decl to be exported, so we save memory by having a sparse map of
4139/// Decl pointers to details about them being exported.
4240/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.
......@@ -934,6 +932,7 @@ pub const InitOptions = struct {
934932 root_pkg: ?*Package,
935933 output_mode: std.builtin.OutputMode,
936934 rand: *std.rand.Random,
935 bin_file_dir_path: ?[]const u8 = null,
937936 bin_file_dir: ?std.fs.Dir = null,
938937 bin_file_path: []const u8,
939938 emit_h: ?[]const u8 = null,
......@@ -1018,8 +1017,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
10181017 break :blk false;
10191018 };
10201019
1021 const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();
1022 const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{
1020 const bin_file = try link.File.openPath(gpa, .{
1021 .dir = options.bin_file_dir orelse std.fs.cwd(),
1022 .dir_path = options.bin_file_dir_path,
1023 .sub_path = options.bin_file_path,
10231024 .root_name = root_name,
10241025 .root_pkg = options.root_pkg,
10251026 .target = options.target,
......@@ -1165,8 +1166,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
11651166 .root_name = root_name,
11661167 .root_pkg = options.root_pkg,
11671168 .root_scope = root_scope,
1168 .bin_file_dir = bin_file_dir,
1169 .bin_file_path = options.bin_file_path,
11701169 .bin_file = bin_file,
11711170 .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
11721171 .keep_source_files_loaded = options.keep_source_files_loaded,
......@@ -1350,7 +1349,7 @@ pub fn makeBinFileExecutable(self: *Module) !void {
13501349}
13511350
13521351pub fn makeBinFileWritable(self: *Module) !void {
1353 return self.bin_file.makeWritable(self.bin_file_dir, self.bin_file_path);
1352 return self.bin_file.makeWritable();
13541353}
13551354
13561355pub fn totalErrorCount(self: *Module) usize {
src-self-hosted/link.zig+41-14
......@@ -10,6 +10,12 @@ const build_options = @import("build_options");
1010pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
1111
1212pub const Options = struct {
13 dir: fs.Dir,
14 /// Redundant with dir. Needed when linking with LLD because we have to pass paths rather
15 /// than file descriptors. `null` means cwd. OK to pass `null` when `use_lld` is `false`.
16 dir_path: ?[]const u8,
17 /// Path to the output file, relative to dir.
18 sub_path: []const u8,
1319 target: std.Target,
1420 output_mode: std.builtin.OutputMode,
1521 link_mode: std.builtin.LinkMode,
......@@ -56,6 +62,9 @@ pub const File = struct {
5662 options: Options,
5763 file: ?fs.File,
5864 allocator: *Allocator,
65 /// When linking with LLD, this linker code will output an object file only at
66 /// this location, and then this path can be placed on the LLD linker line.
67 intermediary_basename: ?[]const u8 = null,
5968
6069 pub const LinkBlock = union {
6170 elf: Elf.TextBlock,
......@@ -90,16 +99,29 @@ pub const File = struct {
9099 /// incremental linking fails, falls back to truncating the file and
91100 /// rewriting it. A malicious file is detected as incremental link failure
92101 /// and does not cause Illegal Behavior. This operation is not atomic.
93 pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File {
94 switch (options.object_format) {
95 .coff, .pe => return Coff.openPath(allocator, dir, sub_path, options),
96 .elf => return Elf.openPath(allocator, dir, sub_path, options),
97 .macho => return MachO.openPath(allocator, dir, sub_path, options),
98 .wasm => return Wasm.openPath(allocator, dir, sub_path, options),
99 .c => return C.openPath(allocator, dir, sub_path, options),
102 pub fn openPath(allocator: *Allocator, options: Options) !*File {
103 const use_lld = build_options.have_llvm and options.use_lld; // comptime known false when !have_llvm
104 const sub_path = if (use_lld) blk: {
105 // Open a temporary object file, not the final output file because we want to link with LLD.
106 break :blk try std.fmt.allocPrint(allocator, "{}{}", .{ options.sub_path, options.target.oFileExt() });
107 } else options.sub_path;
108 errdefer if (use_lld) allocator.free(sub_path);
109
110 const file: *File = switch (options.object_format) {
111 .coff, .pe => try Coff.openPath(allocator, sub_path, options),
112 .elf => try Elf.openPath(allocator, sub_path, options),
113 .macho => try MachO.openPath(allocator, sub_path, options),
114 .wasm => try Wasm.openPath(allocator, sub_path, options),
115 .c => try C.openPath(allocator, sub_path, options),
100116 .hex => return error.HexObjectFormatUnimplemented,
101117 .raw => return error.RawObjectFormatUnimplemented,
118 };
119
120 if (use_lld) {
121 file.intermediary_basename = sub_path;
102122 }
123
124 return file;
103125 }
104126
105127 pub fn cast(base: *File, comptime T: type) ?*T {
......@@ -109,11 +131,11 @@ pub const File = struct {
109131 return @fieldParentPtr(T, "base", base);
110132 }
111133
112 pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
134 pub fn makeWritable(base: *File) !void {
113135 switch (base.tag) {
114136 .coff, .elf, .macho => {
115137 if (base.file != null) return;
116 base.file = try dir.createFile(sub_path, .{
138 base.file = try base.options.dir.createFile(base.options.sub_path, .{
117139 .truncate = false,
118140 .read = true,
119141 .mode = determineMode(base.options),
......@@ -125,12 +147,16 @@ pub const File = struct {
125147
126148 pub fn makeExecutable(base: *File) !void {
127149 switch (base.tag) {
128 .c => unreachable,
129 .wasm => {},
130 else => if (base.file) |f| {
150 .coff, .elf, .macho => if (base.file) |f| {
151 if (base.intermediary_basename != null) {
152 // The file we have open is not the final file that we want to
153 // make executable, so we don't have to close it.
154 return;
155 }
131156 f.close();
132157 base.file = null;
133158 },
159 .c, .wasm => {},
134160 }
135161 }
136162
......@@ -167,7 +193,6 @@ pub const File = struct {
167193 }
168194
169195 pub fn deinit(base: *File) void {
170 if (base.file) |f| f.close();
171196 switch (base.tag) {
172197 .coff => @fieldParentPtr(Coff, "base", base).deinit(),
173198 .elf => @fieldParentPtr(Elf, "base", base).deinit(),
......@@ -175,6 +200,8 @@ pub const File = struct {
175200 .c => @fieldParentPtr(C, "base", base).deinit(),
176201 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
177202 }
203 if (base.file) |f| f.close();
204 if (base.intermediary_basename) |sub_path| base.allocator.free(sub_path);
178205 }
179206
180207 pub fn destroy(base: *File) void {
......@@ -292,7 +319,7 @@ pub fn determineMode(options: Options) fs.File.Mode {
292319 // more leniently. As another data point, C's fopen seems to open files with the
293320 // 666 mode.
294321 const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
295 switch (options.output_mode) {
322 switch (options.effectiveOutputMode()) {
296323 .Lib => return switch (options.link_mode) {
297324 .Dynamic => executable_mode,
298325 .Static => fs.File.default_mode,
src-self-hosted/link/C.zig+4-4
......@@ -22,13 +22,13 @@ need_stddef: bool = false,
2222need_stdint: bool = false,
2323error_msg: *Module.ErrorMsg = undefined,
2424
25pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File {
25pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*File {
2626 assert(options.object_format == .c);
2727
28 if (options.use_llvm) return error.LLVM_HasNoCBackend;
29 if (options.use_lld) return error.LLD_HasNoCBackend;
28 if (options.use_llvm) return error.LLVMHasNoCBackend;
29 if (options.use_lld) return error.LLDHasNoCBackend;
3030
31 const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
31 const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
3232 errdefer file.close();
3333
3434 var c_file = try allocator.create(C);
src-self-hosted/link/Coff.zig+7-3
......@@ -19,7 +19,7 @@ const file_alignment = 512;
1919const image_base = 0x400_000;
2020const section_table_size = 2 * 40;
2121comptime {
22 std.debug.assert(std.mem.isAligned(image_base, section_alignment));
22 assert(std.mem.isAligned(image_base, section_alignment));
2323}
2424
2525pub const base_tag: link.File.Tag = .coff;
......@@ -110,13 +110,17 @@ pub const TextBlock = struct {
110110
111111pub const SrcFn = void;
112112
113pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
113pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*link.File {
114114 assert(options.object_format == .coff);
115115
116116 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO
117117 if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO
118118
119 const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) });
119 const file = try options.dir.createFile(sub_path, .{
120 .truncate = false,
121 .read = true,
122 .mode = link.determineMode(options),
123 });
120124 errdefer file.close();
121125
122126 var coff_file = try allocator.create(Coff);
src-self-hosted/link/Elf.zig+11-44
......@@ -216,65 +216,28 @@ pub const SrcFn = struct {
216216 };
217217};
218218
219pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File {
219pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*File {
220220 assert(options.object_format == .elf);
221221
222222 if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO
223223
224 if (build_options.have_llvm and options.use_lld) {
225 std.debug.print("TODO open a temporary object file, not the final output file because we want to link with LLD\n", .{});
226 }
227
228 const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) });
224 const file = try options.dir.createFile(sub_path, .{
225 .truncate = false,
226 .read = true,
227 .mode = link.determineMode(options),
228 });
229229 errdefer file.close();
230230
231231 var elf_file = try allocator.create(Elf);
232232 errdefer allocator.destroy(elf_file);
233233
234 elf_file.* = openFile(allocator, file, options) catch |err| switch (err) {
235 error.IncrFailed => try createFile(allocator, file, options),
236 else => |e| return e,
237 };
238
234 elf_file.* = try createFile(allocator, file, options);
239235 return &elf_file.base;
240236}
241237
242/// Returns error.IncrFailed if incremental update could not be performed.
243fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {
244 switch (options.effectiveOutputMode()) {
245 .Exe => {},
246 .Obj => {},
247 .Lib => return error.IncrFailed,
248 }
249 var self: Elf = .{
250 .base = .{
251 .file = file,
252 .tag = .elf,
253 .options = options,
254 .allocator = allocator,
255 },
256 .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) {
257 0 ... 32 => .p32,
258 33 ... 64 => .p64,
259 else => return error.UnsupportedELFArchitecture,
260 },
261 };
262 errdefer self.deinit();
263
264 // TODO implement reading the elf file
265 return error.IncrFailed;
266 //try self.populateMissingMetadata();
267 //return self;
268}
269
270238/// Truncates the existing file contents and overwrites the contents.
271239/// Returns an error if `file` is not already open with +read +write +seek abilities.
272240fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {
273 switch (options.effectiveOutputMode()) {
274 .Exe => {},
275 .Obj => {},
276 .Lib => return error.TODOImplementWritingLibFiles,
277 }
278241 var self: Elf = .{
279242 .base = .{
280243 .tag = .elf,
......@@ -753,6 +716,10 @@ pub fn flush(self: *Elf, module: *Module) !void {
753716 }
754717 std.debug.print("TODO create an LLD command line and invoke it\n", .{});
755718 } else {
719 switch (self.base.options.effectiveOutputMode()) {
720 .Exe, .Obj => {},
721 .Lib => return error.TODOImplementWritingLibFiles,
722 }
756723 return self.flushInner(module);
757724 }
758725}
src-self-hosted/link/MachO.zig+6-2
......@@ -134,13 +134,17 @@ pub const SrcFn = struct {
134134 pub const empty = SrcFn{};
135135};
136136
137pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File {
137pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*File {
138138 assert(options.object_format == .macho);
139139
140140 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO
141141 if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO
142142
143 const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) });
143 const file = try options.dir.createFile(sub_path, .{
144 .truncate = false,
145 .read = true,
146 .mode = link.determineMode(options),
147 });
144148 errdefer file.close();
145149
146150 var macho_file = try allocator.create(MachO);
src-self-hosted/link/Wasm.zig+2-2
......@@ -49,14 +49,14 @@ base: link.File,
4949/// TODO: can/should we access some data structure in Module directly?
5050funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},
5151
52pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File {
52pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*link.File {
5353 assert(options.object_format == .wasm);
5454
5555 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
5656 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
5757
5858 // TODO: read the file and keep vaild parts instead of truncating
59 const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true });
59 const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true });
6060 errdefer file.close();
6161
6262 const wasm = try allocator.create(Wasm);
src-self-hosted/main.zig+1
......@@ -1000,6 +1000,7 @@ pub fn buildOutputType(
10001000 .target = target_info.target,
10011001 .output_mode = output_mode,
10021002 .root_pkg = root_pkg,
1003 .bin_file_dir_path = null,
10031004 .bin_file_dir = fs.cwd(),
10041005 .bin_file_path = bin_path,
10051006 .link_mode = link_mode,