From b37955f2739dc20f65cc8d352cab98dc07f44b5f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 9 Sep 2020 10:54:40 -0700 Subject: [PATCH] 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. --- src-self-hosted/Module.zig | 13 ++++---- src-self-hosted/link.zig | 55 +++++++++++++++++++++++++--------- src-self-hosted/link/C.zig | 8 ++--- src-self-hosted/link/Coff.zig | 10 +++++-- src-self-hosted/link/Elf.zig | 55 +++++++--------------------------- src-self-hosted/link/MachO.zig | 8 +++-- src-self-hosted/link/Wasm.zig | 4 +-- src-self-hosted/main.zig | 1 + 8 files changed, 78 insertions(+), 76 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 10a26a4aa8c18af704bd8292b085bd0edafd1a11..441ce15dc031f53e3742ec1c01611c0029057a37 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -35,8 +35,6 @@ root_pkg: ?*Package, /// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`. root_scope: *Scope, bin_file: *link.File, -bin_file_dir: std.fs.Dir, -bin_file_path: []const u8, /// It's rare for a decl to be exported, so we save memory by having a sparse map of /// Decl pointers to details about them being exported. /// 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 { root_pkg: ?*Package, output_mode: std.builtin.OutputMode, rand: *std.rand.Random, + bin_file_dir_path: ?[]const u8 = null, bin_file_dir: ?std.fs.Dir = null, bin_file_path: []const u8, emit_h: ?[]const u8 = null, @@ -1018,8 +1017,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { break :blk false; }; - const bin_file_dir = options.bin_file_dir orelse std.fs.cwd(); - const bin_file = try link.File.openPath(gpa, bin_file_dir, options.bin_file_path, .{ + const bin_file = try link.File.openPath(gpa, .{ + .dir = options.bin_file_dir orelse std.fs.cwd(), + .dir_path = options.bin_file_dir_path, + .sub_path = options.bin_file_path, .root_name = root_name, .root_pkg = options.root_pkg, .target = options.target, @@ -1165,8 +1166,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Module { .root_name = root_name, .root_pkg = options.root_pkg, .root_scope = root_scope, - .bin_file_dir = bin_file_dir, - .bin_file_path = options.bin_file_path, .bin_file = bin_file, .work_queue = std.fifo.LinearFifo(WorkItem, .Dynamic).init(gpa), .keep_source_files_loaded = options.keep_source_files_loaded, @@ -1350,7 +1349,7 @@ pub fn makeBinFileExecutable(self: *Module) !void { } pub fn makeBinFileWritable(self: *Module) !void { - return self.bin_file.makeWritable(self.bin_file_dir, self.bin_file_path); + return self.bin_file.makeWritable(); } pub fn totalErrorCount(self: *Module) usize { diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 3b07899aec7a246ab5340f0cfd48ba150ef5fb7b..22c8a6c8e36930e9d5c45ba85520111909fe5e06 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -10,6 +10,12 @@ const build_options = @import("build_options"); pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version; pub const Options = struct { + dir: fs.Dir, + /// Redundant with dir. Needed when linking with LLD because we have to pass paths rather + /// than file descriptors. `null` means cwd. OK to pass `null` when `use_lld` is `false`. + dir_path: ?[]const u8, + /// Path to the output file, relative to dir. + sub_path: []const u8, target: std.Target, output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, @@ -56,6 +62,9 @@ pub const File = struct { options: Options, file: ?fs.File, allocator: *Allocator, + /// When linking with LLD, this linker code will output an object file only at + /// this location, and then this path can be placed on the LLD linker line. + intermediary_basename: ?[]const u8 = null, pub const LinkBlock = union { elf: Elf.TextBlock, @@ -90,16 +99,29 @@ pub const File = struct { /// incremental linking fails, falls back to truncating the file and /// rewriting it. A malicious file is detected as incremental link failure /// and does not cause Illegal Behavior. This operation is not atomic. - pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: Options) !*File { - switch (options.object_format) { - .coff, .pe => return Coff.openPath(allocator, dir, sub_path, options), - .elf => return Elf.openPath(allocator, dir, sub_path, options), - .macho => return MachO.openPath(allocator, dir, sub_path, options), - .wasm => return Wasm.openPath(allocator, dir, sub_path, options), - .c => return C.openPath(allocator, dir, sub_path, options), + pub fn openPath(allocator: *Allocator, options: Options) !*File { + const use_lld = build_options.have_llvm and options.use_lld; // comptime known false when !have_llvm + const sub_path = if (use_lld) blk: { + // Open a temporary object file, not the final output file because we want to link with LLD. + break :blk try std.fmt.allocPrint(allocator, "{}{}", .{ options.sub_path, options.target.oFileExt() }); + } else options.sub_path; + errdefer if (use_lld) allocator.free(sub_path); + + const file: *File = switch (options.object_format) { + .coff, .pe => try Coff.openPath(allocator, sub_path, options), + .elf => try Elf.openPath(allocator, sub_path, options), + .macho => try MachO.openPath(allocator, sub_path, options), + .wasm => try Wasm.openPath(allocator, sub_path, options), + .c => try C.openPath(allocator, sub_path, options), .hex => return error.HexObjectFormatUnimplemented, .raw => return error.RawObjectFormatUnimplemented, + }; + + if (use_lld) { + file.intermediary_basename = sub_path; } + + return file; } pub fn cast(base: *File, comptime T: type) ?*T { @@ -109,11 +131,11 @@ pub const File = struct { return @fieldParentPtr(T, "base", base); } - pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void { + pub fn makeWritable(base: *File) !void { switch (base.tag) { .coff, .elf, .macho => { if (base.file != null) return; - base.file = try dir.createFile(sub_path, .{ + base.file = try base.options.dir.createFile(base.options.sub_path, .{ .truncate = false, .read = true, .mode = determineMode(base.options), @@ -125,12 +147,16 @@ pub const File = struct { pub fn makeExecutable(base: *File) !void { switch (base.tag) { - .c => unreachable, - .wasm => {}, - else => if (base.file) |f| { + .coff, .elf, .macho => if (base.file) |f| { + if (base.intermediary_basename != null) { + // The file we have open is not the final file that we want to + // make executable, so we don't have to close it. + return; + } f.close(); base.file = null; }, + .c, .wasm => {}, } } @@ -167,7 +193,6 @@ pub const File = struct { } pub fn deinit(base: *File) void { - if (base.file) |f| f.close(); switch (base.tag) { .coff => @fieldParentPtr(Coff, "base", base).deinit(), .elf => @fieldParentPtr(Elf, "base", base).deinit(), @@ -175,6 +200,8 @@ pub const File = struct { .c => @fieldParentPtr(C, "base", base).deinit(), .wasm => @fieldParentPtr(Wasm, "base", base).deinit(), } + if (base.file) |f| f.close(); + if (base.intermediary_basename) |sub_path| base.allocator.free(sub_path); } pub fn destroy(base: *File) void { @@ -292,7 +319,7 @@ pub fn determineMode(options: Options) fs.File.Mode { // more leniently. As another data point, C's fopen seems to open files with the // 666 mode. const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777; - switch (options.output_mode) { + switch (options.effectiveOutputMode()) { .Lib => return switch (options.link_mode) { .Dynamic => executable_mode, .Static => fs.File.default_mode, diff --git a/src-self-hosted/link/C.zig b/src-self-hosted/link/C.zig index ae0516dae0e08a12f4eca9a3147ba63843a4f969..8ec6f91959861d967c905e5981a6daf1dabd7dca 100644 --- a/src-self-hosted/link/C.zig +++ b/src-self-hosted/link/C.zig @@ -22,13 +22,13 @@ need_stddef: bool = false, need_stdint: bool = false, error_msg: *Module.ErrorMsg = undefined, -pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File { +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*File { assert(options.object_format == .c); - if (options.use_llvm) return error.LLVM_HasNoCBackend; - if (options.use_lld) return error.LLD_HasNoCBackend; + if (options.use_llvm) return error.LLVMHasNoCBackend; + if (options.use_lld) return error.LLDHasNoCBackend; - const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); + const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); errdefer file.close(); var c_file = try allocator.create(C); diff --git a/src-self-hosted/link/Coff.zig b/src-self-hosted/link/Coff.zig index 4cab7a75fd0a0eb760a8f51067a5dcb9192a3bf6..950e3537d9a57562f5e12d3e04eaf26ddb0fa862 100644 --- a/src-self-hosted/link/Coff.zig +++ b/src-self-hosted/link/Coff.zig @@ -19,7 +19,7 @@ const file_alignment = 512; const image_base = 0x400_000; const section_table_size = 2 * 40; comptime { - std.debug.assert(std.mem.isAligned(image_base, section_alignment)); + assert(std.mem.isAligned(image_base, section_alignment)); } pub const base_tag: link.File.Tag = .coff; @@ -110,13 +110,17 @@ pub const TextBlock = struct { pub const SrcFn = void; -pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File { +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*link.File { assert(options.object_format == .coff); if (options.use_llvm) return error.LLVM_BackendIsTODO_ForCoff; // TODO if (options.use_lld) return error.LLD_LinkingIsTODO_ForCoff; // TODO - const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) }); + const file = try options.dir.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = link.determineMode(options), + }); errdefer file.close(); var coff_file = try allocator.create(Coff); diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 4bb2a4e940f1193625a36406b56b56b6ca2bbd89..22ed0446c45b25ec541fea4e0e826e24e0659d0c 100644 --- a/src-self-hosted/link/Elf.zig +++ b/src-self-hosted/link/Elf.zig @@ -216,65 +216,28 @@ pub const SrcFn = struct { }; }; -pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File { +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*File { assert(options.object_format == .elf); if (options.use_llvm) return error.LLVMBackendUnimplementedForELF; // TODO - if (build_options.have_llvm and options.use_lld) { - std.debug.print("TODO open a temporary object file, not the final output file because we want to link with LLD\n", .{}); - } - - const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) }); + const file = try options.dir.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = link.determineMode(options), + }); errdefer file.close(); var elf_file = try allocator.create(Elf); errdefer allocator.destroy(elf_file); - elf_file.* = openFile(allocator, file, options) catch |err| switch (err) { - error.IncrFailed => try createFile(allocator, file, options), - else => |e| return e, - }; - + elf_file.* = try createFile(allocator, file, options); return &elf_file.base; } -/// Returns error.IncrFailed if incremental update could not be performed. -fn openFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf { - switch (options.effectiveOutputMode()) { - .Exe => {}, - .Obj => {}, - .Lib => return error.IncrFailed, - } - var self: Elf = .{ - .base = .{ - .file = file, - .tag = .elf, - .options = options, - .allocator = allocator, - }, - .ptr_width = switch (options.target.cpu.arch.ptrBitWidth()) { - 0 ... 32 => .p32, - 33 ... 64 => .p64, - else => return error.UnsupportedELFArchitecture, - }, - }; - errdefer self.deinit(); - - // TODO implement reading the elf file - return error.IncrFailed; - //try self.populateMissingMetadata(); - //return self; -} - /// Truncates the existing file contents and overwrites the contents. /// Returns an error if `file` is not already open with +read +write +seek abilities. fn createFile(allocator: *Allocator, file: fs.File, options: link.Options) !Elf { - switch (options.effectiveOutputMode()) { - .Exe => {}, - .Obj => {}, - .Lib => return error.TODOImplementWritingLibFiles, - } var self: Elf = .{ .base = .{ .tag = .elf, @@ -753,6 +716,10 @@ pub fn flush(self: *Elf, module: *Module) !void { } std.debug.print("TODO create an LLD command line and invoke it\n", .{}); } else { + switch (self.base.options.effectiveOutputMode()) { + .Exe, .Obj => {}, + .Lib => return error.TODOImplementWritingLibFiles, + } return self.flushInner(module); } } diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index 95f0d26f287b05c6a3aff6731e7e90dbee51fd8e..3ffa606d048a35d18ed9b7cf9bd801881bddbefc 100644 --- a/src-self-hosted/link/MachO.zig +++ b/src-self-hosted/link/MachO.zig @@ -134,13 +134,17 @@ pub const SrcFn = struct { pub const empty = SrcFn{}; }; -pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*File { +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*File { assert(options.object_format == .macho); if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO - const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = link.determineMode(options) }); + const file = try options.dir.createFile(sub_path, .{ + .truncate = false, + .read = true, + .mode = link.determineMode(options), + }); errdefer file.close(); var macho_file = try allocator.create(MachO); diff --git a/src-self-hosted/link/Wasm.zig b/src-self-hosted/link/Wasm.zig index cc5cb427cd76b71d9c59816926cb760adc626064..46bafaaf105938432744c21d3bbcac4964b59eb7 100644 --- a/src-self-hosted/link/Wasm.zig +++ b/src-self-hosted/link/Wasm.zig @@ -49,14 +49,14 @@ base: link.File, /// TODO: can/should we access some data structure in Module directly? funcs: std.ArrayListUnmanaged(*Module.Decl) = .{}, -pub fn openPath(allocator: *Allocator, dir: fs.Dir, sub_path: []const u8, options: link.Options) !*link.File { +pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*link.File { assert(options.object_format == .wasm); if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO // TODO: read the file and keep vaild parts instead of truncating - const file = try dir.createFile(sub_path, .{ .truncate = true, .read = true }); + const file = try options.dir.createFile(sub_path, .{ .truncate = true, .read = true }); errdefer file.close(); const wasm = try allocator.create(Wasm); diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index 84bb6e9351286af9cb3cc28a92a8836244a06009..81a3126c7f15a499bf6af26a02d21e9eb999b27e 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -1000,6 +1000,7 @@ pub fn buildOutputType( .target = target_info.target, .output_mode = output_mode, .root_pkg = root_pkg, + .bin_file_dir_path = null, .bin_file_dir = fs.cwd(), .bin_file_path = bin_path, .link_mode = link_mode, -- 2.54.0