diff --git a/src-self-hosted/Compilation.zig b/src-self-hosted/Compilation.zig index 606eefa4ee..49747cecb2 100644 --- a/src-self-hosted/Compilation.zig +++ b/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(); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index a7393cc913..4d3fd5085d 100644 --- a/src-self-hosted/link.zig +++ b/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 { diff --git a/src-self-hosted/link/C.zig b/src-self-hosted/link/C.zig index 97990f34fb..d5d1249244 100644 --- a/src-self-hosted/link/C.zig +++ b/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(); diff --git a/src-self-hosted/link/Coff.zig b/src-self-hosted/link/Coff.zig index b927ca2586..31726a5712 100644 --- a/src-self-hosted/link/Coff.zig +++ b/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(); diff --git a/src-self-hosted/link/Elf.zig b/src-self-hosted/link/Elf.zig index 49cf02d7c4..8a75796bc3 100644 --- a/src-self-hosted/link/Elf.zig +++ b/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(); diff --git a/src-self-hosted/link/MachO.zig b/src-self-hosted/link/MachO.zig index ff3fa2810d..3b70a0d710 100644 --- a/src-self-hosted/link/MachO.zig +++ b/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(); diff --git a/src-self-hosted/link/Wasm.zig b/src-self-hosted/link/Wasm.zig index b4bb2c3195..1160e471fe 100644 --- a/src-self-hosted/link/Wasm.zig +++ b/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(); diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig index d247e87c28..64a6d4e8b5 100644 --- a/src-self-hosted/llvm.zig +++ b/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, +}; diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig index b09c363d8a..8ea880e898 100644 --- a/src-self-hosted/target.zig +++ b/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, + }; +}