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,...@@ -35,8 +35,6 @@ root_pkg: ?*Package,
35/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`.35/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`.
36root_scope: *Scope,36root_scope: *Scope,
37bin_file: *link.File,37bin_file: *link.File,
38bin_file_dir: std.fs.Dir,
39bin_file_path: []const u8,
40/// It's rare for a decl to be exported, so we save memory by having a sparse map of38/// It's rare for a decl to be exported, so we save memory by having a sparse map of
41/// Decl pointers to details about them being exported.39/// Decl pointers to details about them being exported.
42/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.40/// 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 {...@@ -934,6 +932,7 @@ pub const InitOptions = struct {
934 root_pkg: ?*Package,932 root_pkg: ?*Package,
935 output_mode: std.builtin.OutputMode,933 output_mode: std.builtin.OutputMode,
936 rand: *std.rand.Random,934 rand: *std.rand.Random,
935 bin_file_dir_path: ?[]const u8 = null,
937 bin_file_dir: ?std.fs.Dir = null,936 bin_file_dir: ?std.fs.Dir = null,
938 bin_file_path: []const u8,937 bin_file_path: []const u8,
939 emit_h: ?[]const u8 = null,938 emit_h: ?[]const u8 = null,
...@@ -1018,8 +1017,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {...@@ -1018,8 +1017,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
1018 break :blk false;1017 break :blk false;
1019 };1018 };
10201019
1021 const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();1020 const bin_file = try link.File.openPath(gpa, .{
1022 const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{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,
1023 .root_name = root_name,1024 .root_name = root_name,
1024 .root_pkg = options.root_pkg,1025 .root_pkg = options.root_pkg,
1025 .target = options.target,1026 .target = options.target,
...@@ -1165,8 +1166,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {...@@ -1165,8 +1166,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module {
1165 .root_name = root_name,1166 .root_name = root_name,
1166 .root_pkg = options.root_pkg,1167 .root_pkg = options.root_pkg,
1167 .root_scope = root_scope,1168 .root_scope = root_scope,
1168 .bin_file_dir = bin_file_dir,
1169 .bin_file_path = options.bin_file_path,
1170 .bin_file = bin_file,1169 .bin_file = bin_file,
1171 .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),1170 .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa),
1172 .keep_source_files_loaded = options.keep_source_files_loaded,1171 .keep_source_files_loaded = options.keep_source_files_loaded,
...@@ -1350,7 +1349,7 @@ pub fn makeBinFileExecutable(self: *Module) !void {...@@ -1350,7 +1349,7 @@ pub fn makeBinFileExecutable(self: *Module) !void {
1350}1349}
13511350
1352pub fn makeBinFileWritable(self: *Module) !void {1351pub 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();
1354}1353}
13551354
1356pub fn totalErrorCount(self: *Module) usize {1355pub fn totalErrorCount(self: *Module) usize {
src-self-hosted/link.zig+41-14
...@@ -10,6 +10,12 @@ const build_options = @import("build_options");...@@ -10,6 +10,12 @@ const build_options = @import("build_options");
10pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;10pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
1111
12pub const Options = struct {12pub 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,
13 target: std.Target,19 target: std.Target,
14 output_mode: std.builtin.OutputMode,20 output_mode: std.builtin.OutputMode,
15 link_mode: std.builtin.LinkMode,21 link_mode: std.builtin.LinkMode,
...@@ -56,6 +62,9 @@ pub const File = struct {...@@ -56,6 +62,9 @@ pub const File = struct {
56 options: Options,62 options: Options,
57 file: ?fs.File,63 file: ?fs.File,
58 allocator: *Allocator,64 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
60 pub const LinkBlock = union {69 pub const LinkBlock = union {
61 elf: Elf.TextBlock,70 elf: Elf.TextBlock,
...@@ -90,16 +99,29 @@ pub const File = struct {...@@ -90,16 +99,29 @@ pub const File = struct {
90 /// incremental linking fails, falls back to truncating the file and99 /// incremental linking fails, falls back to truncating the file and
91 /// rewriting it. A malicious file is detected as incremental link failure100 /// rewriting it. A malicious file is detected as incremental link failure
92 /// and does not cause Illegal Behavior. This operation is not atomic.101 /// 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 {102 pub fn openPath(allocator: *Allocator, options: Options) !*File {
94 switch (options.object_format) {103 const use_lld = build_options.have_llvm and options.use_lld; // comptime known false when !have_llvm
95 .coff, .pe => return Coff.openPath(allocator, dir, sub_path, options),104 const sub_path = if (use_lld) blk: {
96 .elf => return Elf.openPath(allocator, dir, sub_path, options),105 // Open a temporary object file, not the final output file because we want to link with LLD.
97 .macho => return MachO.openPath(allocator, dir, sub_path, options),106 break :blk try std.fmt.allocPrint(allocator, "{}{}", .{ options.sub_path, options.target.oFileExt() });
98 .wasm => return Wasm.openPath(allocator, dir, sub_path, options),107 } else options.sub_path;
99 .c => return C.openPath(allocator, dir, sub_path, options),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),
100 .hex => return error.HexObjectFormatUnimplemented,116 .hex => return error.HexObjectFormatUnimplemented,
101 .raw => return error.RawObjectFormatUnimplemented,117 .raw => return error.RawObjectFormatUnimplemented,
118 };
119
120 if (use_lld) {
121 file.intermediary_basename = sub_path;
102 }122 }
123
124 return file;
103 }125 }
104126
105 pub fn cast(base: *File, comptime T: type) ?*T {127 pub fn cast(base: *File, comptime T: type) ?*T {
...@@ -109,11 +131,11 @@ pub const File = struct {...@@ -109,11 +131,11 @@ pub const File = struct {
109 return @fieldParentPtr(T, "base", base);131 return @fieldParentPtr(T, "base", base);
110 }132 }
111133
112 pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {134 pub fn makeWritable(base: *File) !void {
113 switch (base.tag) {135 switch (base.tag) {
114 .coff, .elf, .macho => {136 .coff, .elf, .macho => {
115 if (base.file != null) return;137 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, .{
117 .truncate = false,139 .truncate = false,
118 .read = true,140 .read = true,
119 .mode = determineMode(base.options),141 .mode = determineMode(base.options),
...@@ -125,12 +147,16 @@ pub const File = struct {...@@ -125,12 +147,16 @@ pub const File = struct {
125147
126 pub fn makeExecutable(base: *File) !void {148 pub fn makeExecutable(base: *File) !void {
127 switch (base.tag) {149 switch (base.tag) {
128 .c => unreachable,150 .coff, .elf, .macho => if (base.file) |f| {
129 .wasm => {},151 if (base.intermediary_basename != null) {
130 else => if (base.file) |f| {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 }
131 f.close();156 f.close();
132 base.file = null;157 base.file = null;
133 },158 },
159 .c, .wasm => {},
134 }160 }
135 }161 }
136162
...@@ -167,7 +193,6 @@ pub const File = struct {...@@ -167,7 +193,6 @@ pub const File = struct {
167 }193 }
168194
169 pub fn deinit(base: *File) void {195 pub fn deinit(base: *File) void {
170 if (base.file) |f| f.close();
171 switch (base.tag) {196 switch (base.tag) {
172 .coff => @fieldParentPtr(Coff, "base", base).deinit(),197 .coff => @fieldParentPtr(Coff, "base", base).deinit(),
173 .elf => @fieldParentPtr(Elf, "base", base).deinit(),198 .elf => @fieldParentPtr(Elf, "base", base).deinit(),
...@@ -175,6 +200,8 @@ pub const File = struct {...@@ -175,6 +200,8 @@ pub const File = struct {
175 .c => @fieldParentPtr(C, "base", base).deinit(),200 .c => @fieldParentPtr(C, "base", base).deinit(),
176 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),201 .wasm => @fieldParentPtr(Wasm, "base", base).deinit(),
177 }202 }
203 if (base.file) |f| f.close();
204 if (base.intermediary_basename) |sub_path| base.allocator.free(sub_path);
178 }205 }
179206
180 pub fn destroy(base: *File) void {207 pub fn destroy(base: *File) void {
...@@ -292,7 +319,7 @@ pub fn determineMode(options: Options) fs.File.Mode {...@@ -292,7 +319,7 @@ pub fn determineMode(options: Options) fs.File.Mode {
292 // more leniently. As another data point, C's fopen seems to open files with the319 // more leniently. As another data point, C's fopen seems to open files with the
293 // 666 mode.320 // 666 mode.
294 const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;321 const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
295 switch (options.output_mode) {322 switch (options.effectiveOutputMode()) {
296 .Lib => return switch (options.link_mode) {323 .Lib => return switch (options.link_mode) {
297 .Dynamic => executable_mode,324 .Dynamic => executable_mode,
298 .Static => fs.File.default_mode,325 .Static => fs.File.default_mode,
src-self-hosted/link/C.zig+4-4
...@@ -22,13 +22,13 @@ need_stddef: bool = false,...@@ -22,13 +22,13 @@ need_stddef: bool = false,
22need_stdint: bool = false,22need_stdint: bool = false,
23error_msg: *Module.ErrorMsg = undefined,23error_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 {
26 assert(options.object_format == .c);26 assert(options.object_format == .c);
2727
28 if (options.use_llvm) return error.LLVM_HasNoCBackend;28 if (options.use_llvm) return error.LLVMHasNoCBackend;
29 if (options.use_lld) return error.LLD_HasNoCBackend;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) });
32 errdefer file.close();32 errdefer file.close();
3333
34 var c_file = try allocator.create(C);34 var c_file = try allocator.create(C);
src-self-hosted/link/Coff.zig+7-3
...@@ -19,7 +19,7 @@ const file_alignment = 512;...@@ -19,7 +19,7 @@ const file_alignment = 512;
19const image_base = 0x400_000;19const image_base = 0x400_000;
20const section_table_size = 2 * 40;20const section_table_size = 2 * 40;
21comptime {21comptime {
22 std.debug.assert(std.mem.isAligned(image_base, section_alignment));22 assert(std.mem.isAligned(image_base, section_alignment));
23}23}
2424
25pub const base_tag: link.File.Tag = .coff;25pub const base_tag: link.File.Tag = .coff;
...@@ -110,13 +110,17 @@ pub const TextBlock = struct {...@@ -110,13 +110,17 @@ pub const TextBlock = struct {
110110
111pub const SrcFn = void;111pub 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 {
114 assert(options.object_format == .coff);114 assert(options.object_format == .coff);
115115
116 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO116 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO
117 if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO117 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 });
120 errdefer file.close();124 errdefer file.close();
121125
122 var coff_file = try allocator.create(Coff);126 var coff_file = try allocator.create(Coff);
src-self-hosted/link/Elf.zig+11-44
...@@ -216,65 +216,28 @@ pub const SrcFn = struct {...@@ -216,65 +216,28 @@ pub const SrcFn = struct {
216 };216 };
217};217};
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 {
220 assert(options.object_format == .elf);220 assert(options.object_format == .elf);
221221
222 if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO222 if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO
223223
224 if (build_options.have_llvm and options.use_lld) {224 const file = try options.dir.createFile(sub_path, .{
225 std.debug.print("TODO open a temporary object file, not the final output file because we want to link with LLD\n", .{});225 .truncate = false,
226 }226 .read = true,
227227 .mode = link.determineMode(options),
228 const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) });228 });
229 errdefer file.close();229 errdefer file.close();
230230
231 var elf_file = try allocator.create(Elf);231 var elf_file = try allocator.create(Elf);
232 errdefer allocator.destroy(elf_file);232 errdefer allocator.destroy(elf_file);
233233
234 elf_file.* = openFile(allocator, file, options) catch |err| switch (err) {234 elf_file.* = try createFile(allocator, file, options);
235 error.IncrFailed => try createFile(allocator, file, options),
236 else => |e| return e,
237 };
238
239 return &elf_file.base;235 return &elf_file.base;
240}236}
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
270/// Truncates the existing file contents and overwrites the contents.238/// Truncates the existing file contents and overwrites the contents.
271/// Returns an error if `file` is not already open with +read +write +seek abilities.239/// Returns an error if `file` is not already open with +read +write +seek abilities.
272fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {240fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf {
273 switch (options.effectiveOutputMode()) {
274 .Exe => {},
275 .Obj => {},
276 .Lib => return error.TODOImplementWritingLibFiles,
277 }
278 var self: Elf = .{241 var self: Elf = .{
279 .base = .{242 .base = .{
280 .tag = .elf,243 .tag = .elf,
...@@ -753,6 +716,10 @@ pub fn flush(self: *Elf, module: *Module) !void {...@@ -753,6 +716,10 @@ pub fn flush(self: *Elf, module: *Module) !void {
753 }716 }
754 std.debug.print("TODO create an LLD command line and invoke it\n", .{});717 std.debug.print("TODO create an LLD command line and invoke it\n", .{});
755 } else {718 } else {
719 switch (self.base.options.effectiveOutputMode()) {
720 .Exe, .Obj => {},
721 .Lib => return error.TODOImplementWritingLibFiles,
722 }
756 return self.flushInner(module);723 return self.flushInner(module);
757 }724 }
758}725}
src-self-hosted/link/MachO.zig+6-2
...@@ -134,13 +134,17 @@ pub const SrcFn = struct {...@@ -134,13 +134,17 @@ pub const SrcFn = struct {
134 pub const empty = SrcFn{};134 pub const empty = SrcFn{};
135};135};
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 {
138 assert(options.object_format == .macho);138 assert(options.object_format == .macho);
139139
140 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO140 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO
141 if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO141 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 });
144 errdefer file.close();148 errdefer file.close();
145149
146 var macho_file = try allocator.create(MachO);150 var macho_file = try allocator.create(MachO);
src-self-hosted/link/Wasm.zig+2-2
...@@ -49,14 +49,14 @@ base: link.File,...@@ -49,14 +49,14 @@ base: link.File,
49/// TODO: can/should we access some data structure in Module directly?49/// TODO: can/should we access some data structure in Module directly?
50funcs: std.ArrayListUnmanaged(*Module.Decl) = .{},50funcs: 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 {
53 assert(options.object_format == .wasm);53 assert(options.object_format == .wasm);
5454
55 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO55 if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
56 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO56 if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
5757
58 // TODO: read the file and keep vaild parts instead of truncating58 // 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 });
60 errdefer file.close();60 errdefer file.close();
6161
62 const wasm = try allocator.create(Wasm);62 const wasm = try allocator.create(Wasm);
src-self-hosted/main.zig+1
...@@ -1000,6 +1000,7 @@ pub fn buildOutputType(...@@ -1000,6 +1000,7 @@ pub fn buildOutputType(
1000 .target = target_info.target,1000 .target = target_info.target,
1001 .output_mode = output_mode,1001 .output_mode = output_mode,
1002 .root_pkg = root_pkg,1002 .root_pkg = root_pkg,
1003 .bin_file_dir_path = null,
1003 .bin_file_dir = fs.cwd(),1004 .bin_file_dir = fs.cwd(),
1004 .bin_file_path = bin_path,1005 .bin_file_path = bin_path,
1005 .link_mode = link_mode,1006 .link_mode = link_mode,