Commit 26798018b7

Andrew Kelley <andrew@ziglang.org>
2020-09-15 00:25:30
stage2: implement writing archive files
1 parent 40cb712
src-self-hosted/link/C.zig
@@ -74,6 +74,10 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
 }
 
 pub fn flush(self: *C, comp: *Compilation) !void {
+    return self.flushModule(comp);
+}
+
+pub fn flushModule(self: *C, comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
src-self-hosted/link/Coff.zig
@@ -11,6 +11,7 @@ const Module = @import("../Module.zig");
 const Compilation = @import("../Compilation.zig");
 const codegen = @import("../codegen.zig");
 const link = @import("../link.zig");
+const build_options = @import("build_options");
 
 const allocation_padding = 4 / 3;
 const minimum_text_block_size = 64 * allocation_padding;
@@ -724,6 +725,14 @@ pub fn updateDeclExports(self: *Coff, module: *Module, decl: *const Module.Decl,
 }
 
 pub fn flush(self: *Coff, comp: *Compilation) !void {
+    if (build_options.have_llvm and self.base.options.use_lld) {
+        return error.CoffLinkingWithLLDUnimplemented;
+    } else {
+        return self.flushModule(comp);
+    }
+}
+
+pub fn flushModule(self: *Coff, comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
src-self-hosted/link/Elf.zig
@@ -719,12 +719,11 @@ pub fn flush(self: *Elf, comp: *Compilation) !void {
             .Exe, .Obj => {},
             .Lib => return error.TODOImplementWritingLibFiles,
         }
-        return self.flushInner(comp);
+        return self.flushModule(comp);
     }
 }
 
-/// Commit pending changes and write headers.
-fn flushInner(self: *Elf, comp: *Compilation) !void {
+pub fn flushModule(self: *Elf, comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
@@ -1221,7 +1220,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
     // If there is no Zig code to compile, then we should skip flushing the output file because it
     // will not be part of the linker line anyway.
     const module_obj_path: ?[]const u8 = if (self.base.options.module) |module| blk: {
-        try self.flushInner(comp);
+        try self.flushModule(comp);
 
         const obj_basename = self.base.intermediary_basename.?;
         const full_obj_path = if (directory.path) |dir_path|
@@ -1239,7 +1238,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void {
     // After a successful link, we store the id in the metadata of a symlink named "id.txt" in
     // the artifact directory. So, now, we check if this symlink exists, and if it matches
     // our digest. If so, we can skip linking. Otherwise, we proceed with invoking LLD.
-    const id_symlink_basename = "id.txt";
+    const id_symlink_basename = "lld.id";
 
     // We are about to obtain this lock, so here we give other processes a chance first.
     self.base.releaseLock();
src-self-hosted/link/MachO.zig
@@ -9,9 +9,10 @@ const macho = std.macho;
 const codegen = @import("../codegen.zig");
 const math = std.math;
 const mem = std.mem;
+
 const trace = @import("../tracy.zig").trace;
 const Type = @import("../type.zig").Type;
-
+const build_options = @import("build_options");
 const Module = @import("../Module.zig");
 const Compilation = @import("../Compilation.zig");
 const link = @import("../link.zig");
@@ -178,6 +179,14 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*MachO {
 }
 
 pub fn flush(self: *MachO, comp: *Compilation) !void {
+    if (build_options.have_llvm and self.base.options.use_lld) {
+        return error.MachOLLDLinkingUnimplemented;
+    } else {
+        return self.flushModule(comp);
+    }
+}
+
+pub fn flushModule(self: *MachO, comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
src-self-hosted/link/Wasm.zig
@@ -11,6 +11,7 @@ const Compilation = @import("../Compilation.zig");
 const codegen = @import("../codegen/wasm.zig");
 const link = @import("../link.zig");
 const trace = @import("../tracy.zig").trace;
+const build_options = @import("build_options");
 
 /// Various magic numbers defined by the wasm spec
 const spec = struct {
@@ -135,6 +136,14 @@ pub fn freeDecl(self: *Wasm, decl: *Module.Decl) void {
 }
 
 pub fn flush(self: *Wasm, comp: *Compilation) !void {
+    if (build_options.have_llvm and self.base.options.use_lld) {
+        return error.WasmLinkingWithLLDUnimplemented;
+    } else {
+        return self.flushModule(comp);
+    }
+}
+
+pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
     const tracy = trace(@src());
     defer tracy.end();
 
src-self-hosted/Compilation.zig
@@ -535,6 +535,8 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
             var hash = cache.hash;
             if (options.c_source_files.len >= 1) {
                 hash.addBytes(options.c_source_files[0].src_path);
+            } else if (options.link_objects.len >= 1) {
+                hash.addBytes(options.link_objects[0]);
             }
 
             const digest = hash.final();
src-self-hosted/link.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const mem = std.mem;
 const Allocator = std.mem.Allocator;
 const Compilation = @import("Compilation.zig");
 const Module = @import("Module.zig");
@@ -9,6 +10,7 @@ const Type = @import("type.zig").Type;
 const Cache = @import("Cache.zig");
 const build_options = @import("build_options");
 const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
+const log = std.log.scoped(.link);
 
 pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
 
@@ -279,9 +281,11 @@ pub const File = struct {
         }
     }
 
+    /// Commit pending changes and write headers. Takes into account final output mode
+    /// and `use_lld`, not only `effectiveOutputMode`.
     pub fn flush(base: *File, comp: *Compilation) !void {
         const use_lld = build_options.have_llvm and base.options.use_lld;
-        if (base.options.output_mode == .Lib and base.options.link_mode == .Static and
+        if (use_lld and base.options.output_mode == .Lib and base.options.link_mode == .Static and
             !base.options.target.isWasm())
         {
             return base.linkAsArchive(comp);
@@ -295,6 +299,18 @@ pub const File = struct {
         }
     }
 
+    /// Commit pending changes and write headers. Works based on `effectiveOutputMode`
+    /// rather than final output mode.
+    pub fn flushModule(base: *File, comp: *Compilation) !void {
+        switch (base.tag) {
+            .coff => return @fieldParentPtr(Coff, "base", base).flushModule(comp),
+            .elf => return @fieldParentPtr(Elf, "base", base).flushModule(comp),
+            .macho => return @fieldParentPtr(MachO, "base", base).flushModule(comp),
+            .c => return @fieldParentPtr(C, "base", base).flushModule(comp),
+            .wasm => return @fieldParentPtr(Wasm, "base", base).flushModule(comp),
+        }
+    }
+
     pub fn freeDecl(base: *File, decl: *Module.Decl) void {
         switch (base.tag) {
             .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
@@ -343,9 +359,108 @@ pub const File = struct {
     }
 
     fn linkAsArchive(base: *File, comp: *Compilation) !void {
-        // TODO follow pattern from ELF linkWithLLD
-        // ZigLLVMWriteArchive
-        return error.TODOMakeArchive;
+        const tracy = trace(@src());
+        defer tracy.end();
+
+        var arena_allocator = std.heap.ArenaAllocator.init(base.allocator);
+        defer arena_allocator.deinit();
+        const arena = &arena_allocator.allocator;
+
+        const directory = base.options.directory; // Just an alias to make it shorter to type.
+
+        // If there is no Zig code to compile, then we should skip flushing the output file because it
+        // will not be part of the linker line anyway.
+        const module_obj_path: ?[]const u8 = if (base.options.module) |module| blk: {
+            try base.flushModule(comp);
+
+            const obj_basename = base.intermediary_basename.?;
+            const full_obj_path = if (directory.path) |dir_path|
+                try std.fs.path.join(arena, &[_][]const u8{ dir_path, obj_basename })
+            else
+                obj_basename;
+            break :blk full_obj_path;
+        } else null;
+
+        // This function follows the same pattern as link.Elf.linkWithLLD so if you want some
+        // insight as to what's going on here you can read that function body which is more
+        // well-commented.
+
+        const id_symlink_basename = "llvm-ar.id";
+
+        base.releaseLock();
+
+        var ch = comp.cache_parent.obtain();
+        defer ch.deinit();
+
+        try ch.addListOfFiles(base.options.objects);
+        for (comp.c_object_table.items()) |entry| {
+            _ = try ch.addFile(entry.key.status.success.object_path, null);
+        }
+        try ch.addOptionalFile(module_obj_path);
+
+        // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
+        _ = try ch.hit();
+        const digest = ch.final();
+
+        var prev_digest_buf: [digest.len]u8 = undefined;
+        const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| b: {
+            log.debug("archive new_digest={} readlink error: {}", .{ digest, @errorName(err) });
+            break :b prev_digest_buf[0..0];
+        };
+        if (mem.eql(u8, prev_digest, &digest)) {
+            log.debug("archive digest={} match - skipping invocation", .{digest});
+            base.lock = ch.toOwnedLock();
+            return;
+        }
+
+        // We are about to change the output file to be different, so we invalidate the build hash now.
+        directory.handle.deleteFile(id_symlink_basename) catch |err| switch (err) {
+            error.FileNotFound => {},
+            else => |e| return e,
+        };
+
+        var object_files = std.ArrayList([*:0]const u8).init(base.allocator);
+        defer object_files.deinit();
+
+        try object_files.ensureCapacity(base.options.objects.len + comp.c_object_table.items().len + 1);
+        for (base.options.objects) |obj_path| {
+            object_files.appendAssumeCapacity(try arena.dupeZ(u8, obj_path));
+        }
+        for (comp.c_object_table.items()) |entry| {
+            object_files.appendAssumeCapacity(try arena.dupeZ(u8, entry.key.status.success.object_path));
+        }
+        if (module_obj_path) |p| {
+            object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
+        }
+
+        const full_out_path = if (directory.path) |dir_path|
+            try std.fs.path.join(arena, &[_][]const u8{ dir_path, base.options.sub_path })
+        else
+            base.options.sub_path;
+        const full_out_path_z = try arena.dupeZ(u8, full_out_path);
+
+        if (base.options.debug_link) {
+            std.debug.print("ar rcs {}", .{full_out_path_z});
+            for (object_files.items) |arg| {
+                std.debug.print(" {}", .{arg});
+            }
+            std.debug.print("\n", .{});
+        }
+
+        const llvm = @import("llvm.zig");
+        const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag);
+        const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type);
+        if (bad) return error.UnableToWriteArchive;
+
+        directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| {
+            std.log.warn("failed to save archive hash digest symlink: {}", .{@errorName(err)});
+        };
+
+        ch.writeManifest() catch |err| {
+            std.log.warn("failed to write cache manifest when archiving: {}", .{@errorName(err)});
+        };
+
+        base.lock = ch.toOwnedLock();
     }
 
     pub const Tag = enum {
src-self-hosted/llvm.zig
@@ -2,7 +2,7 @@
 //! to bootstrap if it does not depend on translate-c.
 
 pub const Link = ZigLLDLink;
-pub extern fn ZigLLDLink(
+extern fn ZigLLDLink(
     oformat: ObjectFormatType,
     args: [*:null]const ?[*:0]const u8,
     arg_count: usize,
@@ -25,3 +25,50 @@ extern fn LLVMGetHostCPUName() ?[*:0]u8;
 
 pub const GetNativeFeatures = ZigLLVMGetNativeFeatures;
 extern fn ZigLLVMGetNativeFeatures() ?[*:0]u8;
+
+pub const WriteArchive = ZigLLVMWriteArchive;
+extern fn ZigLLVMWriteArchive(
+    archive_name: [*:0]const u8,
+    file_names_ptr: [*]const [*:0]const u8,
+    file_names_len: usize,
+    os_type: OSType,
+) bool;
+
+pub const OSType = extern enum(c_int) {
+    UnknownOS = 0,
+    Ananas = 1,
+    CloudABI = 2,
+    Darwin = 3,
+    DragonFly = 4,
+    FreeBSD = 5,
+    Fuchsia = 6,
+    IOS = 7,
+    KFreeBSD = 8,
+    Linux = 9,
+    Lv2 = 10,
+    MacOSX = 11,
+    NetBSD = 12,
+    OpenBSD = 13,
+    Solaris = 14,
+    Win32 = 15,
+    Haiku = 16,
+    Minix = 17,
+    RTEMS = 18,
+    NaCl = 19,
+    CNK = 20,
+    AIX = 21,
+    CUDA = 22,
+    NVCL = 23,
+    AMDHSA = 24,
+    PS4 = 25,
+    ELFIAMCU = 26,
+    TvOS = 27,
+    WatchOS = 28,
+    Mesa3D = 29,
+    Contiki = 30,
+    AMDPAL = 31,
+    HermitCore = 32,
+    Hurd = 33,
+    WASI = 34,
+    Emscripten = 35,
+};
src-self-hosted/target.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const llvm = @import("llvm.zig");
 
 pub const ArchOsAbi = struct {
     arch: std.Target.Cpu.Arch,
@@ -168,3 +169,43 @@ pub fn supportsStackProbing(target: std.Target) bool {
     return target.os.tag != .windows and target.os.tag != .uefi and
         (target.cpu.arch == .i386 or target.cpu.arch == .x86_64);
 }
+
+pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType {
+    return switch (os_tag) {
+        .freestanding, .other => .UnknownOS,
+        .windows, .uefi => .Win32,
+        .ananas => .Ananas,
+        .cloudabi => .CloudABI,
+        .dragonfly => .DragonFly,
+        .freebsd => .FreeBSD,
+        .fuchsia => .Fuchsia,
+        .ios => .IOS,
+        .kfreebsd => .KFreeBSD,
+        .linux => .Linux,
+        .lv2 => .Lv2,
+        .macosx => .MacOSX,
+        .netbsd => .NetBSD,
+        .openbsd => .OpenBSD,
+        .solaris => .Solaris,
+        .haiku => .Haiku,
+        .minix => .Minix,
+        .rtems => .RTEMS,
+        .nacl => .NaCl,
+        .cnk => .CNK,
+        .aix => .AIX,
+        .cuda => .CUDA,
+        .nvcl => .NVCL,
+        .amdhsa => .AMDHSA,
+        .ps4 => .PS4,
+        .elfiamcu => .ELFIAMCU,
+        .tvos => .TvOS,
+        .watchos => .WatchOS,
+        .mesa3d => .Mesa3D,
+        .contiki => .Contiki,
+        .amdpal => .AMDPAL,
+        .hermit => .HermitCore,
+        .hurd => .Hurd,
+        .wasi => .WASI,
+        .emscripten => .Emscripten,
+    };
+}