Commit b37955f273

Andrew Kelley <andrew@ziglang.org>
2020-09-09 19:54:40
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.
1 parent 193ad41
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);
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);
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);
     }
 }
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);
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);
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,
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,
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 {