diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 10a26a4aa8..441ce15dc0 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 3b07899aec..22c8a6c8e3 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 ae0516dae0..8ec6f91959 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 4cab7a75fd..950e3537d9 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 4bb2a4e940..22ed0446c4 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 95f0d26f28..3ffa606d04 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 cc5cb427cd..46bafaaf10 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 84bb6e9351..81a3126c7f 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,