Commit b5f73f8a7b
Changed files (21)
lib/std/Build/Step/Compile.zig
@@ -1834,47 +1834,16 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
lp.path = b.fmt("{}", .{output_dir});
}
- // -femit-bin[=path] (default) Output machine code
- if (compile.generated_bin) |bin| {
- bin.path = output_dir.joinString(b.allocator, compile.out_filename) catch @panic("OOM");
- }
-
- const sep = std.fs.path.sep_str;
-
- // output PDB if someone requested it
- if (compile.generated_pdb) |pdb| {
- pdb.path = b.fmt("{}" ++ sep ++ "{s}.pdb", .{ output_dir, compile.name });
- }
-
- // -femit-implib[=path] (default) Produce an import .lib when building a Windows DLL
- if (compile.generated_implib) |implib| {
- implib.path = b.fmt("{}" ++ sep ++ "{s}.lib", .{ output_dir, compile.name });
- }
-
- // -femit-h[=path] Generate a C header file (.h)
- if (compile.generated_h) |lp| {
- lp.path = b.fmt("{}" ++ sep ++ "{s}.h", .{ output_dir, compile.name });
- }
-
- // -femit-docs[=path] Create a docs/ dir with html documentation
- if (compile.generated_docs) |generated_docs| {
- generated_docs.path = output_dir.joinString(b.allocator, "docs") catch @panic("OOM");
- }
-
- // -femit-asm[=path] Output .s (assembly code)
- if (compile.generated_asm) |lp| {
- lp.path = b.fmt("{}" ++ sep ++ "{s}.s", .{ output_dir, compile.name });
- }
-
- // -femit-llvm-ir[=path] Produce a .ll file with optimized LLVM IR (requires LLVM extensions)
- if (compile.generated_llvm_ir) |lp| {
- lp.path = b.fmt("{}" ++ sep ++ "{s}.ll", .{ output_dir, compile.name });
- }
-
- // -femit-llvm-bc[=path] Produce an optimized LLVM module as a .bc file (requires LLVM extensions)
- if (compile.generated_llvm_bc) |lp| {
- lp.path = b.fmt("{}" ++ sep ++ "{s}.bc", .{ output_dir, compile.name });
- }
+ // zig fmt: off
+ if (compile.generated_bin) |lp| lp.path = compile.outputPath(output_dir, .bin);
+ if (compile.generated_pdb) |lp| lp.path = compile.outputPath(output_dir, .pdb);
+ if (compile.generated_implib) |lp| lp.path = compile.outputPath(output_dir, .implib);
+ if (compile.generated_h) |lp| lp.path = compile.outputPath(output_dir, .h);
+ if (compile.generated_docs) |lp| lp.path = compile.outputPath(output_dir, .docs);
+ if (compile.generated_asm) |lp| lp.path = compile.outputPath(output_dir, .@"asm");
+ if (compile.generated_llvm_ir) |lp| lp.path = compile.outputPath(output_dir, .llvm_ir);
+ if (compile.generated_llvm_bc) |lp| lp.path = compile.outputPath(output_dir, .llvm_bc);
+ // zig fmt: on
}
if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and
@@ -1888,6 +1857,21 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
);
}
}
+fn outputPath(c: *Compile, out_dir: std.Build.Cache.Path, ea: std.zig.EmitArtifact) []const u8 {
+ const arena = c.step.owner.graph.arena;
+ const name = ea.cacheName(arena, .{
+ .root_name = c.name,
+ .target = c.root_module.resolved_target.?.result,
+ .output_mode = switch (c.kind) {
+ .lib => .Lib,
+ .obj, .test_obj => .Obj,
+ .exe, .@"test" => .Exe,
+ },
+ .link_mode = c.linkage,
+ .version = c.version,
+ }) catch @panic("OOM");
+ return out_dir.joinString(arena, name) catch @panic("OOM");
+}
pub fn rebuildInFuzzMode(c: *Compile, progress_node: std.Progress.Node) !Path {
const gpa = c.step.owner.allocator;
lib/std/zig.zig
@@ -884,6 +884,35 @@ pub const SimpleComptimeReason = enum(u32) {
}
};
+/// Every kind of artifact which the compiler can emit.
+pub const EmitArtifact = enum {
+ bin,
+ @"asm",
+ implib,
+ llvm_ir,
+ llvm_bc,
+ docs,
+ pdb,
+ h,
+
+ /// If using `Server` to communicate with the compiler, it will place requested artifacts in
+ /// paths under the output directory, where those paths are named according to this function.
+ /// Returned string is allocated with `gpa` and owned by the caller.
+ pub fn cacheName(ea: EmitArtifact, gpa: Allocator, opts: BinNameOptions) Allocator.Error![]const u8 {
+ const suffix: []const u8 = switch (ea) {
+ .bin => return binNameAlloc(gpa, opts),
+ .@"asm" => ".s",
+ .implib => ".lib",
+ .llvm_ir => ".ll",
+ .llvm_bc => ".bc",
+ .docs => "-docs",
+ .pdb => ".pdb",
+ .h => ".h",
+ };
+ return std.fmt.allocPrint(gpa, "{s}{s}", .{ opts.root_name, suffix });
+ }
+};
+
test {
_ = Ast;
_ = AstRlAnnotate;
src/libs/freebsd.zig
@@ -1019,10 +1019,6 @@ fn buildSharedLib(
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
- const emit_bin = Compilation.EmitLoc{
- .directory = bin_directory,
- .basename = basename,
- };
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
@@ -1082,8 +1078,7 @@ fn buildSharedLib(
.root_mod = root_mod,
.root_name = lib.name,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
src/libs/glibc.zig
@@ -1185,10 +1185,6 @@ fn buildSharedLib(
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
- const emit_bin = Compilation.EmitLoc{
- .directory = bin_directory,
- .basename = basename,
- };
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
@@ -1248,8 +1244,7 @@ fn buildSharedLib(
.root_mod = root_mod,
.root_name = lib.name,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
src/libs/libcxx.zig
@@ -122,17 +122,6 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
const output_mode = .Lib;
const link_mode = .static;
const target = comp.root_mod.resolved_target.result;
- const basename = try std.zig.binNameAlloc(arena, .{
- .root_name = root_name,
- .target = target,
- .output_mode = output_mode,
- .link_mode = link_mode,
- });
-
- const emit_bin = Compilation.EmitLoc{
- .directory = null, // Put it in the cache directory.
- .basename = basename,
- };
const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" });
const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" });
@@ -271,8 +260,7 @@ pub fn buildLibCxx(comp: *Compilation, prog_node: std.Progress.Node) BuildError!
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.c_source_files = c_source_files.items,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
@@ -327,17 +315,6 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
const output_mode = .Lib;
const link_mode = .static;
const target = comp.root_mod.resolved_target.result;
- const basename = try std.zig.binNameAlloc(arena, .{
- .root_name = root_name,
- .target = target,
- .output_mode = output_mode,
- .link_mode = link_mode,
- });
-
- const emit_bin = Compilation.EmitLoc{
- .directory = null, // Put it in the cache directory.
- .basename = basename,
- };
const cxxabi_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxxabi", "include" });
const cxx_include_path = try comp.dirs.zig_lib.join(arena, &.{ "libcxx", "include" });
@@ -467,8 +444,7 @@ pub fn buildLibCxxAbi(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.c_source_files = c_source_files.items,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
src/libs/libtsan.zig
@@ -45,11 +45,6 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
.link_mode = link_mode,
});
- const emit_bin = Compilation.EmitLoc{
- .directory = null, // Put it in the cache directory.
- .basename = basename,
- };
-
const optimize_mode = comp.compilerRtOptMode();
const strip = comp.compilerRtStrip();
const unwind_tables: std.builtin.UnwindTables =
@@ -287,8 +282,7 @@ pub fn buildTsan(comp: *Compilation, prog_node: std.Progress.Node) BuildError!vo
.root_mod = root_mod,
.root_name = root_name,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.c_source_files = c_source_files.items,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
src/libs/libunwind.zig
@@ -31,7 +31,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
const unwind_tables: std.builtin.UnwindTables =
if (target.cpu.arch == .x86 and target.os.tag == .windows) .none else .@"async";
const config = Compilation.Config.resolve(.{
- .output_mode = .Lib,
+ .output_mode = output_mode,
.resolved_target = comp.root_mod.resolved_target,
.is_test = false,
.have_zcu = false,
@@ -85,17 +85,6 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
};
const root_name = "unwind";
- const link_mode = .static;
- const basename = try std.zig.binNameAlloc(arena, .{
- .root_name = root_name,
- .target = target,
- .output_mode = output_mode,
- .link_mode = link_mode,
- });
- const emit_bin = Compilation.EmitLoc{
- .directory = null, // Put it in the cache directory.
- .basename = basename,
- };
var c_source_files: [unwind_src_list.len]Compilation.CSourceFile = undefined;
for (unwind_src_list, 0..) |unwind_src, i| {
var cflags = std.ArrayList([]const u8).init(arena);
@@ -160,7 +149,7 @@ pub fn buildStaticLib(comp: *Compilation, prog_node: std.Progress.Node) BuildErr
.main_mod = null,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
+ .emit_bin = .yes_cache,
.function_sections = comp.function_sections,
.c_source_files = &c_source_files,
.verbose_cc = comp.verbose_cc,
src/libs/musl.zig
@@ -252,8 +252,7 @@ pub fn buildCrtFile(comp: *Compilation, in_crt_file: CrtFile, prog_node: std.Pro
.thread_pool = comp.thread_pool,
.root_name = "c",
.libc_installation = comp.libc_installation,
- .emit_bin = .{ .directory = null, .basename = "libc.so" },
- .emit_h = null,
+ .emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
src/libs/netbsd.zig
@@ -684,10 +684,6 @@ fn buildSharedLib(
defer tracy.end();
const basename = try std.fmt.allocPrint(arena, "lib{s}.so.{d}", .{ lib.name, lib.sover });
- const emit_bin = Compilation.EmitLoc{
- .directory = bin_directory,
- .basename = basename,
- };
const version: Version = .{ .major = lib.sover, .minor = 0, .patch = 0 };
const ld_basename = path.basename(comp.getTarget().standardDynamicLinkerPath().get().?);
const soname = if (mem.eql(u8, lib.name, "ld")) ld_basename else basename;
@@ -746,8 +742,7 @@ fn buildSharedLib(
.root_mod = root_mod,
.root_name = lib.name,
.libc_installation = comp.libc_installation,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
src/link/Coff.zig
@@ -224,21 +224,16 @@ pub fn createEmpty(
else => 0x1000,
};
- // If using LLVM to generate the object file for the zig compilation unit,
- // we need a place to put the object file so that it can be subsequently
- // handled.
- const zcu_object_sub_path = if (!use_llvm)
- null
- else
- try allocPrint(arena, "{s}.obj", .{emit.sub_path});
-
const coff = try arena.create(Coff);
coff.* = .{
.base = .{
.tag = .coff,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = zcu_object_sub_path,
+ .zcu_object_basename = if (use_llvm)
+ try std.fmt.allocPrint(arena, "{s}_zcu.obj", .{fs.path.stem(emit.sub_path)})
+ else
+ null,
.stack_size = options.stack_size orelse 16777216,
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
.print_gc_sections = options.print_gc_sections,
src/link/Elf.zig
@@ -249,14 +249,6 @@ pub fn createEmpty(
const is_dyn_lib = output_mode == .Lib and link_mode == .dynamic;
const default_sym_version: elf.Versym = if (is_dyn_lib or comp.config.rdynamic) .GLOBAL else .LOCAL;
- // If using LLVM to generate the object file for the zig compilation unit,
- // we need a place to put the object file so that it can be subsequently
- // handled.
- const zcu_object_sub_path = if (!use_llvm)
- null
- else
- try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
-
var rpath_table: std.StringArrayHashMapUnmanaged(void) = .empty;
try rpath_table.entries.resize(arena, options.rpath_list.len);
@memcpy(rpath_table.entries.items(.key), options.rpath_list);
@@ -268,7 +260,10 @@ pub fn createEmpty(
.tag = .elf,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = zcu_object_sub_path,
+ .zcu_object_basename = if (use_llvm)
+ try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)})
+ else
+ null,
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug and output_mode != .Obj),
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 16777216,
@@ -770,17 +765,13 @@ fn flushInner(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id) !void {
const gpa = comp.gpa;
const diags = &comp.link_diags;
- const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{
- .root_dir = self.base.emit.root_dir,
- .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|
- try fs.path.join(arena, &.{ dirname, path })
- else
- path,
+ const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: {
+ break :p try comp.resolveEmitPathFlush(arena, .temp, raw);
} else null;
if (self.zigObjectPtr()) |zig_object| try zig_object.flush(self, tid);
- if (module_obj_path) |path| openParseObjectReportingFailure(self, path);
+ if (zcu_obj_path) |path| openParseObjectReportingFailure(self, path);
switch (comp.config.output_mode) {
.Obj => return relocatable.flushObject(self, comp),
src/link/Goff.zig
@@ -41,7 +41,7 @@ pub fn createEmpty(
.tag = .goff,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = emit.sub_path,
+ .zcu_object_basename = emit.sub_path,
.gc_sections = options.gc_sections orelse false,
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 0,
src/link/Lld.zig
@@ -1,5 +1,4 @@
base: link.File,
-disable_caching: bool,
ofmt: union(enum) {
elf: Elf,
coff: Coff,
@@ -231,7 +230,7 @@ pub fn createEmpty(
.tag = .lld,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = try allocPrint(arena, "{s}.{s}", .{ emit.sub_path, obj_file_ext }),
+ .zcu_object_basename = try allocPrint(arena, "{s}_zcu.{s}", .{ fs.path.stem(emit.sub_path), obj_file_ext }),
.gc_sections = gc_sections,
.print_gc_sections = options.print_gc_sections,
.stack_size = stack_size,
@@ -239,7 +238,6 @@ pub fn createEmpty(
.file = null,
.build_id = options.build_id,
},
- .disable_caching = options.disable_lld_caching,
.ofmt = switch (target.ofmt) {
.coff => .{ .coff = try .init(comp, options) },
.elf => .{ .elf = try .init(comp, options) },
@@ -289,14 +287,11 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
const full_out_path_z = try arena.dupeZ(u8, full_out_path);
const opt_zcu = comp.zcu;
- // 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 zcu_obj_path: ?[]const u8 = if (opt_zcu != null) blk: {
- const dirname = fs.path.dirname(full_out_path_z) orelse ".";
- break :blk try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
+ const zcu_obj_path: ?Cache.Path = if (opt_zcu != null) p: {
+ break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
- log.debug("zcu_obj_path={s}", .{if (zcu_obj_path) |s| s else "(null)"});
+ log.debug("zcu_obj_path={?}", .{zcu_obj_path});
const compiler_rt_path: ?Cache.Path = if (comp.compiler_rt_strat == .obj)
comp.compiler_rt_obj.?.full_object_path
@@ -330,7 +325,7 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void {
for (comp.win32_resource_table.keys()) |key| {
object_files.appendAssumeCapacity(try arena.dupeZ(u8, key.status.success.res_path));
}
- if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try arena.dupeZ(u8, p));
+ if (zcu_obj_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
if (compiler_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
if (ubsan_rt_path) |p| object_files.appendAssumeCapacity(try p.toStringZ(arena));
@@ -368,14 +363,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
- // 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 (comp.zcu != null) p: {
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
- } else {
- break :p base.zcu_object_sub_path.?;
- }
+ const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: {
+ break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
const is_lib = comp.config.output_mode == .Lib;
@@ -402,8 +391,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
- if (module_obj_path) |p|
- break :blk Cache.Path.initCwd(p);
+ if (zcu_obj_path) |p|
+ break :blk p;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
@@ -513,9 +502,9 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
try argv.append(try allocPrint(arena, "-OUT:{s}", .{full_out_path}));
- if (comp.implib_emit) |emit| {
- const implib_out_path = try emit.root_dir.join(arena, &[_][]const u8{emit.sub_path});
- try argv.append(try allocPrint(arena, "-IMPLIB:{s}", .{implib_out_path}));
+ if (comp.emit_implib) |raw_emit_path| {
+ const path = try comp.resolveEmitPathFlush(arena, .temp, raw_emit_path);
+ try argv.append(try allocPrint(arena, "-IMPLIB:{}", .{path}));
}
if (comp.config.link_libc) {
@@ -556,8 +545,8 @@ fn coffLink(lld: *Lld, arena: Allocator) !void {
try argv.append(key.status.success.res_path);
}
- if (module_obj_path) |p| {
- try argv.append(p);
+ if (zcu_obj_path) |p| {
+ try argv.append(try p.toString(arena));
}
if (coff.module_definition_file) |def| {
@@ -808,14 +797,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
- // 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 (comp.zcu != null) p: {
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
- } else {
- break :p base.zcu_object_sub_path.?;
- }
+ const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: {
+ break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
const output_mode = comp.config.output_mode;
@@ -862,8 +845,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
- if (module_obj_path) |p|
- break :blk Cache.Path.initCwd(p);
+ if (zcu_obj_path) |p|
+ break :blk p;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
@@ -1151,8 +1134,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void {
try argv.append(try key.status.success.object_path.toString(arena));
}
- if (module_obj_path) |p| {
- try argv.append(p);
+ if (zcu_obj_path) |p| {
+ try argv.append(try p.toString(arena));
}
if (comp.tsan_lib) |lib| {
@@ -1387,14 +1370,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
const directory = base.emit.root_dir; // Just an alias to make it shorter to type.
const full_out_path = try directory.join(arena, &[_][]const u8{base.emit.sub_path});
- // 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 (comp.zcu != null) p: {
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :p try fs.path.join(arena, &.{ dirname, base.zcu_object_sub_path.? });
- } else {
- break :p base.zcu_object_sub_path.?;
- }
+ const zcu_obj_path: ?Cache.Path = if (comp.zcu != null) p: {
+ break :p try comp.resolveEmitPathFlush(arena, .temp, base.zcu_object_basename.?);
} else null;
const is_obj = comp.config.output_mode == .Obj;
@@ -1419,8 +1396,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
if (comp.c_object_table.count() != 0)
break :blk comp.c_object_table.keys()[0].status.success.object_path;
- if (module_obj_path) |p|
- break :blk Cache.Path.initCwd(p);
+ if (zcu_obj_path) |p|
+ break :blk p;
// TODO I think this is unreachable. Audit this situation when solving the above TODO
// regarding eliding redundant object -> object transformations.
@@ -1610,8 +1587,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
for (comp.c_object_table.keys()) |key| {
try argv.append(try key.status.success.object_path.toString(arena));
}
- if (module_obj_path) |p| {
- try argv.append(p);
+ if (zcu_obj_path) |p| {
+ try argv.append(try p.toString(arena));
}
if (compiler_rt_path) |p| {
src/link/MachO.zig
@@ -173,13 +173,6 @@ pub fn createEmpty(
const output_mode = comp.config.output_mode;
const link_mode = comp.config.link_mode;
- // If using LLVM to generate the object file for the zig compilation unit,
- // we need a place to put the object file so that it can be subsequently
- // handled.
- const zcu_object_sub_path = if (!use_llvm)
- null
- else
- try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
const allow_shlib_undefined = options.allow_shlib_undefined orelse false;
const self = try arena.create(MachO);
@@ -188,7 +181,10 @@ pub fn createEmpty(
.tag = .macho,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = zcu_object_sub_path,
+ .zcu_object_basename = if (use_llvm)
+ try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)})
+ else
+ null,
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug),
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 16777216,
@@ -351,21 +347,16 @@ pub fn flush(
const sub_prog_node = prog_node.start("MachO Flush", 0);
defer sub_prog_node.end();
- const directory = self.base.emit.root_dir;
- const module_obj_path: ?Path = if (self.base.zcu_object_sub_path) |path| .{
- .root_dir = directory,
- .sub_path = if (fs.path.dirname(self.base.emit.sub_path)) |dirname|
- try fs.path.join(arena, &.{ dirname, path })
- else
- path,
+ const zcu_obj_path: ?Path = if (self.base.zcu_object_basename) |raw| p: {
+ break :p try comp.resolveEmitPathFlush(arena, .temp, raw);
} else null;
// --verbose-link
if (comp.verbose_link) try self.dumpArgv(comp);
if (self.getZigObject()) |zo| try zo.flush(self, tid);
- if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, module_obj_path);
- if (self.base.isObject()) return relocatable.flushObject(self, comp, module_obj_path);
+ if (self.base.isStaticLib()) return relocatable.flushStaticLib(self, comp, zcu_obj_path);
+ if (self.base.isObject()) return relocatable.flushObject(self, comp, zcu_obj_path);
var positionals = std.ArrayList(link.Input).init(gpa);
defer positionals.deinit();
@@ -387,7 +378,7 @@ pub fn flush(
positionals.appendAssumeCapacity(try link.openObjectInput(diags, key.status.success.object_path));
}
- if (module_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
+ if (zcu_obj_path) |path| try positionals.append(try link.openObjectInput(diags, path));
if (comp.config.any_sanitize_thread) {
try positionals.append(try link.openObjectInput(diags, comp.tsan_lib.?.full_object_path));
@@ -636,12 +627,9 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
const directory = self.base.emit.root_dir;
const full_out_path = try directory.join(arena, &[_][]const u8{self.base.emit.sub_path});
- const module_obj_path: ?[]const u8 = if (self.base.zcu_object_sub_path) |path| blk: {
- if (fs.path.dirname(full_out_path)) |dirname| {
- break :blk try fs.path.join(arena, &.{ dirname, path });
- } else {
- break :blk path;
- }
+ const zcu_obj_path: ?[]const u8 = if (self.base.zcu_object_basename) |raw| p: {
+ const p = try comp.resolveEmitPathFlush(arena, .temp, raw);
+ break :p try p.toString(arena);
} else null;
var argv = std.ArrayList([]const u8).init(arena);
@@ -670,7 +658,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append(try key.status.success.object_path.toString(arena));
}
- if (module_obj_path) |p| {
+ if (zcu_obj_path) |p| {
try argv.append(p);
}
} else {
@@ -762,7 +750,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
try argv.append(try key.status.success.object_path.toString(arena));
}
- if (module_obj_path) |p| {
+ if (zcu_obj_path) |p| {
try argv.append(p);
}
src/link/Wasm.zig
@@ -2951,21 +2951,16 @@ pub fn createEmpty(
const output_mode = comp.config.output_mode;
const wasi_exec_model = comp.config.wasi_exec_model;
- // If using LLVM to generate the object file for the zig compilation unit,
- // we need a place to put the object file so that it can be subsequently
- // handled.
- const zcu_object_sub_path = if (!use_llvm)
- null
- else
- try std.fmt.allocPrint(arena, "{s}.o", .{emit.sub_path});
-
const wasm = try arena.create(Wasm);
wasm.* = .{
.base = .{
.tag = .wasm,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = zcu_object_sub_path,
+ .zcu_object_basename = if (use_llvm)
+ try std.fmt.allocPrint(arena, "{s}_zcu.o", .{fs.path.stem(emit.sub_path)})
+ else
+ null,
// Garbage collection is so crucial to WebAssembly that we design
// the linker around the assumption that it will be on in the vast
// majority of cases, and therefore express "no garbage collection"
@@ -3834,15 +3829,9 @@ pub fn flush(
if (comp.verbose_link) Compilation.dump_argv(wasm.dump_argv_list.items);
- if (wasm.base.zcu_object_sub_path) |path| {
- const module_obj_path: Path = .{
- .root_dir = wasm.base.emit.root_dir,
- .sub_path = if (fs.path.dirname(wasm.base.emit.sub_path)) |dirname|
- try fs.path.join(arena, &.{ dirname, path })
- else
- path,
- };
- openParseObjectReportingFailure(wasm, module_obj_path);
+ if (wasm.base.zcu_object_basename) |raw| {
+ const zcu_obj_path: Path = try comp.resolveEmitPathFlush(arena, .temp, raw);
+ openParseObjectReportingFailure(wasm, zcu_obj_path);
try prelink(wasm, prog_node);
}
src/link/Xcoff.zig
@@ -41,7 +41,7 @@ pub fn createEmpty(
.tag = .xcoff,
.comp = comp,
.emit = emit,
- .zcu_object_sub_path = emit.sub_path,
+ .zcu_object_basename = emit.sub_path,
.gc_sections = options.gc_sections orelse false,
.print_gc_sections = options.print_gc_sections,
.stack_size = options.stack_size orelse 0,
src/Zcu/PerThread.zig
@@ -2493,7 +2493,7 @@ fn newEmbedFile(
cache: {
const whole = switch (zcu.comp.cache_use) {
.whole => |whole| whole,
- .incremental => break :cache,
+ .incremental, .none => break :cache,
};
const man = whole.cache_manifest orelse break :cache;
const ip_str = opt_ip_str orelse break :cache; // this will be a compile error
@@ -3377,7 +3377,7 @@ pub fn populateTestFunctions(
}
// The linker thread is not running, so we actually need to dispatch this task directly.
- @import("../link.zig").doZcuTask(zcu.comp, @intFromEnum(pt.tid), .{ .link_nav = nav_index });
+ @import("../link.zig").linkTestFunctionsNav(pt, nav_index);
}
}
src/Compilation.zig
@@ -55,8 +55,7 @@ gpa: Allocator,
arena: Allocator,
/// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`.
zcu: ?*Zcu,
-/// Contains different state depending on whether the Compilation uses
-/// incremental or whole cache mode.
+/// Contains different state depending on the `CacheMode` used by this `Compilation`.
cache_use: CacheUse,
/// All compilations have a root module because this is where some important
/// settings are stored, such as target and optimization mode. This module
@@ -67,17 +66,13 @@ root_mod: *Package.Module,
config: Config,
/// The main output file.
-/// In whole cache mode, this is null except for during the body of the update
-/// function. In incremental cache mode, this is a long-lived object.
-/// In both cases, this is `null` when `-fno-emit-bin` is used.
+/// In `CacheMode.whole`, this is null except for during the body of `update`.
+/// In `CacheMode.none` and `CacheMode.incremental`, this is long-lived.
+/// Regardless of cache mode, this is `null` when `-fno-emit-bin` is used.
bin_file: ?*link.File,
/// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin)
sysroot: ?[]const u8,
-/// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used.
-implib_emit: ?Cache.Path,
-/// This is non-null when `-femit-docs` is provided.
-docs_emit: ?Cache.Path,
root_name: [:0]const u8,
compiler_rt_strat: RtStrat,
ubsan_rt_strat: RtStrat,
@@ -259,10 +254,6 @@ mutex: if (builtin.single_threaded) struct {
test_filters: []const []const u8,
test_name_prefix: ?[]const u8,
-emit_asm: ?EmitLoc,
-emit_llvm_ir: ?EmitLoc,
-emit_llvm_bc: ?EmitLoc,
-
link_task_wait_group: WaitGroup = .{},
work_queue_progress_node: std.Progress.Node = .none,
@@ -274,6 +265,31 @@ file_system_inputs: ?*std.ArrayListUnmanaged(u8),
/// This digest will be known after update() is called.
digest: ?[Cache.bin_digest_len]u8 = null,
+/// Non-`null` iff we are emitting a binary.
+/// Does not change for the lifetime of this `Compilation`.
+/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache.
+emit_bin: ?[]const u8,
+/// Non-`null` iff we are emitting assembly.
+/// Does not change for the lifetime of this `Compilation`.
+/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache.
+emit_asm: ?[]const u8,
+/// Non-`null` iff we are emitting an implib.
+/// Does not change for the lifetime of this `Compilation`.
+/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache.
+emit_implib: ?[]const u8,
+/// Non-`null` iff we are emitting LLVM IR.
+/// Does not change for the lifetime of this `Compilation`.
+/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache.
+emit_llvm_ir: ?[]const u8,
+/// Non-`null` iff we are emitting LLVM bitcode.
+/// Does not change for the lifetime of this `Compilation`.
+/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache.
+emit_llvm_bc: ?[]const u8,
+/// Non-`null` iff we are emitting documentation.
+/// Does not change for the lifetime of this `Compilation`.
+/// Cwd-relative if `cache_use == .none`. Otherwise, relative to our subdirectory in the cache.
+emit_docs: ?[]const u8,
+
const QueuedJobs = struct {
compiler_rt_lib: bool = false,
compiler_rt_obj: bool = false,
@@ -774,13 +790,6 @@ pub const CrtFile = struct {
lock: Cache.Lock,
full_object_path: Cache.Path,
- pub fn isObject(cf: CrtFile) bool {
- return switch (classifyFileExt(cf.full_object_path.sub_path)) {
- .object => true,
- else => false,
- };
- }
-
pub fn deinit(self: *CrtFile, gpa: Allocator) void {
self.lock.release();
gpa.free(self.full_object_path.sub_path);
@@ -1321,14 +1330,6 @@ pub const MiscError = struct {
}
};
-pub const EmitLoc = struct {
- /// If this is `null` it means the file will be output to the cache directory.
- /// When provided, both the open file handle and the path name must outlive the `Compilation`.
- directory: ?Cache.Directory,
- /// This may not have sub-directories in it.
- basename: []const u8,
-};
-
pub const cache_helpers = struct {
pub fn addModule(hh: *Cache.HashHelper, mod: *const Package.Module) void {
addResolvedTarget(hh, mod.resolved_target);
@@ -1368,15 +1369,6 @@ pub const cache_helpers = struct {
hh.add(resolved_target.is_explicit_dynamic_linker);
}
- pub fn addEmitLoc(hh: *Cache.HashHelper, emit_loc: EmitLoc) void {
- hh.addBytes(emit_loc.basename);
- }
-
- pub fn addOptionalEmitLoc(hh: *Cache.HashHelper, optional_emit_loc: ?EmitLoc) void {
- hh.add(optional_emit_loc != null);
- addEmitLoc(hh, optional_emit_loc orelse return);
- }
-
pub fn addOptionalDebugFormat(hh: *Cache.HashHelper, x: ?Config.DebugFormat) void {
hh.add(x != null);
addDebugFormat(hh, x orelse return);
@@ -1423,7 +1415,38 @@ pub const ClangPreprocessorMode = enum {
pub const Framework = link.File.MachO.Framework;
pub const SystemLib = link.SystemLib;
-pub const CacheMode = enum { incremental, whole };
+pub const CacheMode = enum {
+ /// The results of this compilation are not cached. The compilation is always performed, and the
+ /// results are emitted directly to their output locations. Temporary files will be placed in a
+ /// temporary directory in the cache, but deleted after the compilation is done.
+ ///
+ /// This mode is typically used for direct CLI invocations like `zig build-exe`, because such
+ /// processes are typically low-level usages which would not make efficient use of the cache.
+ none,
+ /// The compilation is cached based only on the options given when creating the `Compilation`.
+ /// In particular, Zig source file contents are not included in the cache manifest. This mode
+ /// allows incremental compilation, because the old cached compilation state can be restored
+ /// and the old binary patched up with the changes. All files, including temporary files, are
+ /// stored in the cache directory like '<cache>/o/<hash>/'. Temporary files are not deleted.
+ ///
+ /// At the time of writing, incremental compilation is only supported with the `-fincremental`
+ /// command line flag, so this mode is rarely used. However, it is required in order to use
+ /// incremental compilation.
+ incremental,
+ /// The compilation is cached based on the `Compilation` options and every input, including Zig
+ /// source files, linker inputs, and `@embedFile` targets. If any of them change, we will see a
+ /// cache miss, and the entire compilation will be re-run. On a cache miss, we initially write
+ /// all output files to a directory under '<cache>/tmp/', because we don't know the final
+ /// manifest digest until the update is almost done. Once we can compute the final digest, this
+ /// directory is moved to '<cache>/o/<hash>/'. Temporary files are not deleted.
+ ///
+ /// At the time of writing, this is the most commonly used cache mode: it is used by the build
+ /// system (and any other parent using `--listen`) unless incremental compilation is enabled.
+ /// Once incremental compilation is more mature, it will be replaced by `incremental` in many
+ /// cases, but still has use cases, such as for release binaries, particularly globally cached
+ /// artifacts like compiler_rt.
+ whole,
+};
pub const ParentWholeCache = struct {
manifest: *Cache.Manifest,
@@ -1432,22 +1455,33 @@ pub const ParentWholeCache = struct {
};
const CacheUse = union(CacheMode) {
+ none: *None,
incremental: *Incremental,
whole: *Whole,
+ const None = struct {
+ /// User-requested artifacts are written directly to their output path in this cache mode.
+ /// However, if we need to emit any temporary files, they are placed in this directory.
+ /// We will recursively delete this directory at the end of this update. This field is
+ /// non-`null` only inside `update`.
+ tmp_artifact_directory: ?Cache.Directory,
+ };
+
+ const Incremental = struct {
+ /// All output files, including artifacts and incremental compilation metadata, are placed
+ /// in this directory, which is some 'o/<hash>' in a cache directory.
+ artifact_directory: Cache.Directory,
+ };
+
const Whole = struct {
- /// This is a pointer to a local variable inside `update()`.
- cache_manifest: ?*Cache.Manifest = null,
- cache_manifest_mutex: std.Thread.Mutex = .{},
- /// null means -fno-emit-bin.
- /// This is mutable memory allocated into the Compilation-lifetime arena (`arena`)
- /// of exactly the correct size for "o/[digest]/[basename]".
- /// The basename is of the outputted binary file in case we don't know the directory yet.
- bin_sub_path: ?[]u8,
- /// Same as `bin_sub_path` but for implibs.
- implib_sub_path: ?[]u8,
- docs_sub_path: ?[]u8,
+ /// Since we don't open the output file until `update`, we must save these options for then.
lf_open_opts: link.File.OpenOptions,
+ /// This is a pointer to a local variable inside `update`.
+ cache_manifest: ?*Cache.Manifest,
+ cache_manifest_mutex: std.Thread.Mutex,
+ /// This is non-`null` for most of the body of `update`. It is the temporary directory which
+ /// we initially emit our artifacts to. After the main part of the update is done, it will
+ /// be closed and moved to its final location, and this field set to `null`.
tmp_artifact_directory: ?Cache.Directory,
/// Prevents other processes from clobbering files in the output directory.
lock: ?Cache.Lock,
@@ -1466,17 +1500,16 @@ const CacheUse = union(CacheMode) {
}
};
- const Incremental = struct {
- /// Where build artifacts and incremental compilation metadata serialization go.
- artifact_directory: Cache.Directory,
- };
-
fn deinit(cu: CacheUse) void {
switch (cu) {
+ .none => |none| {
+ assert(none.tmp_artifact_directory == null);
+ },
.incremental => |incremental| {
incremental.artifact_directory.handle.close();
},
.whole => |whole| {
+ assert(whole.tmp_artifact_directory == null);
whole.releaseLock();
},
}
@@ -1503,28 +1536,14 @@ pub const CreateOptions = struct {
std_mod: ?*Package.Module = null,
root_name: []const u8,
sysroot: ?[]const u8 = null,
- /// `null` means to not emit a binary file.
- emit_bin: ?EmitLoc,
- /// `null` means to not emit a C header file.
- emit_h: ?EmitLoc = null,
- /// `null` means to not emit assembly.
- emit_asm: ?EmitLoc = null,
- /// `null` means to not emit LLVM IR.
- emit_llvm_ir: ?EmitLoc = null,
- /// `null` means to not emit LLVM module bitcode.
- emit_llvm_bc: ?EmitLoc = null,
- /// `null` means to not emit docs.
- emit_docs: ?EmitLoc = null,
- /// `null` means to not emit an import lib.
- emit_implib: ?EmitLoc = null,
- /// Normally when using LLD to link, Zig uses a file named "lld.id" in the
- /// same directory as the output binary which contains the hash of the link
- /// operation, allowing Zig to skip linking when the hash would be unchanged.
- /// In the case that the output binary is being emitted into a directory which
- /// is externally modified - essentially anything other than zig-cache - then
- /// this flag would be set to disable this machinery to avoid false positives.
- disable_lld_caching: bool = false,
- cache_mode: CacheMode = .incremental,
+ cache_mode: CacheMode,
+ emit_h: Emit = .no,
+ emit_bin: Emit,
+ emit_asm: Emit = .no,
+ emit_implib: Emit = .no,
+ emit_llvm_ir: Emit = .no,
+ emit_llvm_bc: Emit = .no,
+ emit_docs: Emit = .no,
/// This field is intended to be removed.
/// The ELF implementation no longer uses this data, however the MachO and COFF
/// implementations still do.
@@ -1662,6 +1681,38 @@ pub const CreateOptions = struct {
parent_whole_cache: ?ParentWholeCache = null,
pub const Entry = link.File.OpenOptions.Entry;
+
+ /// Which fields are valid depends on the `cache_mode` given.
+ pub const Emit = union(enum) {
+ /// Do not emit this file. Always valid.
+ no,
+ /// Emit this file into its default name in the cache directory.
+ /// Requires `cache_mode` to not be `.none`.
+ yes_cache,
+ /// Emit this file to the given path (absolute or cwd-relative).
+ /// Requires `cache_mode` to be `.none`.
+ yes_path: []const u8,
+
+ fn resolve(emit: Emit, arena: Allocator, opts: *const CreateOptions, ea: std.zig.EmitArtifact) Allocator.Error!?[]const u8 {
+ switch (emit) {
+ .no => return null,
+ .yes_cache => {
+ assert(opts.cache_mode != .none);
+ return try ea.cacheName(arena, .{
+ .root_name = opts.root_name,
+ .target = opts.root_mod.resolved_target.result,
+ .output_mode = opts.config.output_mode,
+ .link_mode = opts.config.link_mode,
+ .version = opts.version,
+ });
+ },
+ .yes_path => |path| {
+ assert(opts.cache_mode == .none);
+ return try arena.dupe(u8, path);
+ },
+ }
+ }
+ };
};
fn addModuleTableToCacheHash(
@@ -1869,13 +1920,18 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
cache.hash.add(options.config.link_libunwind);
cache.hash.add(output_mode);
cache_helpers.addDebugFormat(&cache.hash, options.config.debug_format);
- cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_bin);
- cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_implib);
- cache_helpers.addOptionalEmitLoc(&cache.hash, options.emit_docs);
cache.hash.addBytes(options.root_name);
cache.hash.add(options.config.wasi_exec_model);
cache.hash.add(options.config.san_cov_trace_pc_guard);
cache.hash.add(options.debug_compiler_runtime_libs);
+ // The actual emit paths don't matter. They're only user-specified if we aren't using the
+ // cache! However, it does matter whether the files are emitted at all.
+ cache.hash.add(options.emit_bin != .no);
+ cache.hash.add(options.emit_asm != .no);
+ cache.hash.add(options.emit_implib != .no);
+ cache.hash.add(options.emit_llvm_ir != .no);
+ cache.hash.add(options.emit_llvm_bc != .no);
+ cache.hash.add(options.emit_docs != .no);
// TODO audit this and make sure everything is in it
const main_mod = options.main_mod orelse options.root_mod;
@@ -1925,7 +1981,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
try zcu.init(options.thread_pool.getIdCount());
break :blk zcu;
} else blk: {
- if (options.emit_h != null) return error.NoZigModuleForCHeader;
+ if (options.emit_h != .no) return error.NoZigModuleForCHeader;
break :blk null;
};
errdefer if (opt_zcu) |zcu| zcu.deinit();
@@ -1938,18 +1994,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.arena = arena,
.zcu = opt_zcu,
.cache_use = undefined, // populated below
- .bin_file = null, // populated below
- .implib_emit = null, // handled below
- .docs_emit = null, // handled below
+ .bin_file = null, // populated below if necessary
.root_mod = options.root_mod,
.config = options.config,
.dirs = options.dirs,
- .emit_asm = options.emit_asm,
- .emit_llvm_ir = options.emit_llvm_ir,
- .emit_llvm_bc = options.emit_llvm_bc,
.work_queues = @splat(.init(gpa)),
- .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa),
- .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{},
+ .c_object_work_queue = .init(gpa),
+ .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) .init(gpa) else .{},
.c_source_files = options.c_source_files,
.rc_source_files = options.rc_source_files,
.cache_parent = cache,
@@ -2002,6 +2053,12 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.file_system_inputs = options.file_system_inputs,
.parent_whole_cache = options.parent_whole_cache,
.link_diags = .init(gpa),
+ .emit_bin = try options.emit_bin.resolve(arena, &options, .bin),
+ .emit_asm = try options.emit_asm.resolve(arena, &options, .@"asm"),
+ .emit_implib = try options.emit_implib.resolve(arena, &options, .implib),
+ .emit_llvm_ir = try options.emit_llvm_ir.resolve(arena, &options, .llvm_ir),
+ .emit_llvm_bc = try options.emit_llvm_bc.resolve(arena, &options, .llvm_bc),
+ .emit_docs = try options.emit_docs.resolve(arena, &options, .docs),
};
// Prevent some footguns by making the "any" fields of config reflect
@@ -2068,7 +2125,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
.soname = options.soname,
.compatibility_version = options.compatibility_version,
.build_id = build_id,
- .disable_lld_caching = options.disable_lld_caching or options.cache_mode == .whole,
.subsystem = options.subsystem,
.hash_style = options.hash_style,
.enable_link_snapshots = options.enable_link_snapshots,
@@ -2087,6 +2143,17 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
};
switch (options.cache_mode) {
+ .none => {
+ const none = try arena.create(CacheUse.None);
+ none.* = .{ .tmp_artifact_directory = null };
+ comp.cache_use = .{ .none = none };
+ if (comp.emit_bin) |path| {
+ comp.bin_file = try link.File.open(arena, comp, .{
+ .root_dir = .cwd(),
+ .sub_path = path,
+ }, lf_open_opts);
+ }
+ },
.incremental => {
// Options that are specific to zig source files, that cannot be
// modified between incremental updates.
@@ -2100,7 +2167,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
hash.addListOfBytes(options.test_filters);
hash.addOptionalBytes(options.test_name_prefix);
hash.add(options.skip_linker_dependencies);
- hash.add(options.emit_h != null);
+ hash.add(options.emit_h != .no);
hash.add(error_limit);
// Here we put the root source file path name, but *not* with addFile.
@@ -2135,49 +2202,26 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
};
comp.cache_use = .{ .incremental = incremental };
- if (options.emit_bin) |emit_bin| {
+ if (comp.emit_bin) |cache_rel_path| {
const emit: Cache.Path = .{
- .root_dir = emit_bin.directory orelse artifact_directory,
- .sub_path = emit_bin.basename,
+ .root_dir = artifact_directory,
+ .sub_path = cache_rel_path,
};
comp.bin_file = try link.File.open(arena, comp, emit, lf_open_opts);
}
-
- if (options.emit_implib) |emit_implib| {
- comp.implib_emit = .{
- .root_dir = emit_implib.directory orelse artifact_directory,
- .sub_path = emit_implib.basename,
- };
- }
-
- if (options.emit_docs) |emit_docs| {
- comp.docs_emit = .{
- .root_dir = emit_docs.directory orelse artifact_directory,
- .sub_path = emit_docs.basename,
- };
- }
},
.whole => {
- // For whole cache mode, we don't know where to put outputs from
- // the linker until the final cache hash, which is available after
- // the compilation is complete.
+ // For whole cache mode, we don't know where to put outputs from the linker until
+ // the final cache hash, which is available after the compilation is complete.
//
- // Therefore, bin_file is left null until the beginning of update(),
- // where it may find a cache hit, or use a temporary directory to
- // hold output artifacts.
+ // Therefore, `comp.bin_file` is left `null` (already done) until `update`, where
+ // it may find a cache hit, or else will use a temporary directory to hold output
+ // artifacts.
const whole = try arena.create(CacheUse.Whole);
whole.* = .{
- // This is kept here so that link.File.open can be called later.
.lf_open_opts = lf_open_opts,
- // This is so that when doing `CacheMode.whole`, the mechanism in update()
- // can use it for communicating the result directory via `bin_file.emit`.
- // This is used to distinguish between -fno-emit-bin and -femit-bin
- // for `CacheMode.whole`.
- // This memory will be overwritten with the real digest in update() but
- // the basename will be preserved.
- .bin_sub_path = try prepareWholeEmitSubPath(arena, options.emit_bin),
- .implib_sub_path = try prepareWholeEmitSubPath(arena, options.emit_implib),
- .docs_sub_path = try prepareWholeEmitSubPath(arena, options.emit_docs),
+ .cache_manifest = null,
+ .cache_manifest_mutex = .{},
.tmp_artifact_directory = null,
.lock = null,
};
@@ -2245,12 +2289,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
}
}
- const have_bin_emit = switch (comp.cache_use) {
- .whole => |whole| whole.bin_sub_path != null,
- .incremental => comp.bin_file != null,
- };
-
- if (have_bin_emit and target.ofmt != .c) {
+ if (comp.emit_bin != null and target.ofmt != .c) {
if (!comp.skip_linker_dependencies) {
// If we need to build libc for the target, add work items for it.
// We go through the work queue so that building can be done in parallel.
@@ -2544,8 +2583,23 @@ pub fn hotCodeSwap(
try lf.makeExecutable();
}
-fn cleanupAfterUpdate(comp: *Compilation) void {
+fn cleanupAfterUpdate(comp: *Compilation, tmp_dir_rand_int: u64) void {
switch (comp.cache_use) {
+ .none => |none| {
+ if (none.tmp_artifact_directory) |*tmp_dir| {
+ tmp_dir.handle.close();
+ none.tmp_artifact_directory = null;
+ const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
+ comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| {
+ log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{
+ comp.dirs.local_cache.path orelse ".",
+ std.fs.path.sep,
+ tmp_dir_sub_path,
+ @errorName(err),
+ });
+ };
+ }
+ },
.incremental => return,
.whole => |whole| {
if (whole.cache_manifest) |man| {
@@ -2556,10 +2610,18 @@ fn cleanupAfterUpdate(comp: *Compilation) void {
lf.destroy();
comp.bin_file = null;
}
- if (whole.tmp_artifact_directory) |*directory| {
- directory.handle.close();
- if (directory.path) |p| comp.gpa.free(p);
+ if (whole.tmp_artifact_directory) |*tmp_dir| {
+ tmp_dir.handle.close();
whole.tmp_artifact_directory = null;
+ const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
+ comp.dirs.local_cache.handle.deleteTree(tmp_dir_sub_path) catch |err| {
+ log.warn("failed to delete temporary directory '{s}{c}{s}': {s}", .{
+ comp.dirs.local_cache.path orelse ".",
+ std.fs.path.sep,
+ tmp_dir_sub_path,
+ @errorName(err),
+ });
+ };
}
},
}
@@ -2579,14 +2641,27 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
comp.clearMiscFailures();
comp.last_update_was_cache_hit = false;
- var man: Cache.Manifest = undefined;
- defer cleanupAfterUpdate(comp);
-
var tmp_dir_rand_int: u64 = undefined;
+ var man: Cache.Manifest = undefined;
+ defer cleanupAfterUpdate(comp, tmp_dir_rand_int);
// If using the whole caching strategy, we check for *everything* up front, including
// C source files.
+ log.debug("Compilation.update for {s}, CacheMode.{s}", .{ comp.root_name, @tagName(comp.cache_use) });
switch (comp.cache_use) {
+ .none => |none| {
+ assert(none.tmp_artifact_directory == null);
+ none.tmp_artifact_directory = d: {
+ tmp_dir_rand_int = std.crypto.random.int(u64);
+ const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
+ const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
+ break :d .{
+ .path = path,
+ .handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}),
+ };
+ };
+ },
+ .incremental => {},
.whole => |whole| {
assert(comp.bin_file == null);
// We are about to obtain this lock, so here we give other processes a chance first.
@@ -2633,10 +2708,8 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
comp.last_update_was_cache_hit = true;
log.debug("CacheMode.whole cache hit for {s}", .{comp.root_name});
const bin_digest = man.finalBin();
- const hex_digest = Cache.binToHex(bin_digest);
comp.digest = bin_digest;
- comp.wholeCacheModeSetBinFilePath(whole, &hex_digest);
assert(whole.lock == null);
whole.lock = man.toOwnedLock();
@@ -2645,52 +2718,23 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name});
// Compile the artifacts to a temporary directory.
- const tmp_artifact_directory: Cache.Directory = d: {
- const s = std.fs.path.sep_str;
+ whole.tmp_artifact_directory = d: {
tmp_dir_rand_int = std.crypto.random.int(u64);
- const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);
-
- const path = try comp.dirs.local_cache.join(gpa, &.{tmp_dir_sub_path});
- errdefer gpa.free(path);
-
- const handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{});
- errdefer handle.close();
-
+ const tmp_dir_sub_path = "tmp" ++ std.fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int);
+ const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path});
break :d .{
.path = path,
- .handle = handle,
+ .handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}),
};
};
- whole.tmp_artifact_directory = tmp_artifact_directory;
-
- // Now that the directory is known, it is time to create the Emit
- // objects and call link.File.open.
-
- if (whole.implib_sub_path) |sub_path| {
- comp.implib_emit = .{
- .root_dir = tmp_artifact_directory,
- .sub_path = std.fs.path.basename(sub_path),
- };
- }
-
- if (whole.docs_sub_path) |sub_path| {
- comp.docs_emit = .{
- .root_dir = tmp_artifact_directory,
- .sub_path = std.fs.path.basename(sub_path),
- };
- }
-
- if (whole.bin_sub_path) |sub_path| {
+ if (comp.emit_bin) |sub_path| {
const emit: Cache.Path = .{
- .root_dir = tmp_artifact_directory,
- .sub_path = std.fs.path.basename(sub_path),
+ .root_dir = whole.tmp_artifact_directory.?,
+ .sub_path = sub_path,
};
comp.bin_file = try link.File.createEmpty(arena, comp, emit, whole.lf_open_opts);
}
},
- .incremental => {
- log.debug("Compilation.update for {s}, CacheMode.incremental", .{comp.root_name});
- },
}
// From this point we add a preliminary set of file system inputs that
@@ -2789,11 +2833,18 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
return;
}
- // Flush below handles -femit-bin but there is still -femit-llvm-ir,
- // -femit-llvm-bc, and -femit-asm, in the case of C objects.
- comp.emitOthers();
+ if (comp.zcu == null and comp.config.output_mode == .Obj and comp.c_object_table.count() == 1) {
+ // This is `zig build-obj foo.c`. We can emit asm and LLVM IR/bitcode.
+ const c_obj_path = comp.c_object_table.keys()[0].status.success.object_path;
+ if (comp.emit_asm) |path| try comp.emitFromCObject(arena, c_obj_path, ".s", path);
+ if (comp.emit_llvm_ir) |path| try comp.emitFromCObject(arena, c_obj_path, ".ll", path);
+ if (comp.emit_llvm_bc) |path| try comp.emitFromCObject(arena, c_obj_path, ".bc", path);
+ }
switch (comp.cache_use) {
+ .none, .incremental => {
+ try flush(comp, arena, .main, main_progress_node);
+ },
.whole => |whole| {
if (comp.file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
if (comp.parent_whole_cache) |pwc| {
@@ -2805,18 +2856,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
const bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest);
- // Rename the temporary directory into place.
- // Close tmp dir and link.File to avoid open handle during rename.
- if (whole.tmp_artifact_directory) |*tmp_directory| {
- tmp_directory.handle.close();
- if (tmp_directory.path) |p| gpa.free(p);
- whole.tmp_artifact_directory = null;
- } else unreachable;
-
- const s = std.fs.path.sep_str;
- const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);
- const o_sub_path = "o" ++ s ++ hex_digest;
-
// Work around windows `AccessDenied` if any files within this
// directory are open by closing and reopening the file handles.
const need_writable_dance: enum { no, lf_only, lf_and_debug } = w: {
@@ -2841,6 +2880,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
break :w .no;
};
+ // Rename the temporary directory into place.
+ // Close tmp dir and link.File to avoid open handle during rename.
+ whole.tmp_artifact_directory.?.handle.close();
+ whole.tmp_artifact_directory = null;
+ const s = std.fs.path.sep_str;
+ const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int);
+ const o_sub_path = "o" ++ s ++ hex_digest;
renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| {
return comp.setMiscFailure(
.rename_results,
@@ -2853,7 +2899,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
);
};
comp.digest = bin_digest;
- comp.wholeCacheModeSetBinFilePath(whole, &hex_digest);
// The linker flush functions need to know the final output path
// for debug info purposes because executable debug info contains
@@ -2861,10 +2906,9 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
if (comp.bin_file) |lf| {
lf.emit = .{
.root_dir = comp.dirs.local_cache,
- .sub_path = whole.bin_sub_path.?,
+ .sub_path = try std.fs.path.join(arena, &.{ o_sub_path, comp.emit_bin.? }),
};
- // Has to be after the `wholeCacheModeSetBinFilePath` above.
switch (need_writable_dance) {
.no => {},
.lf_only => try lf.makeWritable(),
@@ -2875,10 +2919,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
}
}
- try flush(comp, arena, .{
- .root_dir = comp.dirs.local_cache,
- .sub_path = o_sub_path,
- }, .main, main_progress_node);
+ try flush(comp, arena, .main, main_progress_node);
// Calling `flush` may have produced errors, in which case the
// cache manifest must not be written.
@@ -2897,11 +2938,6 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void {
assert(whole.lock == null);
whole.lock = man.toOwnedLock();
},
- .incremental => |incremental| {
- try flush(comp, arena, .{
- .root_dir = incremental.artifact_directory,
- }, .main, main_progress_node);
- },
}
}
@@ -2931,10 +2967,47 @@ pub fn appendFileSystemInput(comp: *Compilation, path: Compilation.Path) Allocat
fsi.appendSliceAssumeCapacity(path.sub_path);
}
+fn resolveEmitPath(comp: *Compilation, path: []const u8) Cache.Path {
+ return .{
+ .root_dir = switch (comp.cache_use) {
+ .none => .cwd(),
+ .incremental => |i| i.artifact_directory,
+ .whole => |w| w.tmp_artifact_directory.?,
+ },
+ .sub_path = path,
+ };
+}
+/// Like `resolveEmitPath`, but for calling during `flush`. The returned `Cache.Path` may reference
+/// memory from `arena`, and may reference `path` itself.
+/// If `kind == .temp`, then the returned path will be in a temporary or cache directory. This is
+/// useful for intermediate files, such as the ZCU object file emitted by the LLVM backend.
+pub fn resolveEmitPathFlush(
+ comp: *Compilation,
+ arena: Allocator,
+ kind: enum { temp, artifact },
+ path: []const u8,
+) Allocator.Error!Cache.Path {
+ switch (comp.cache_use) {
+ .none => |none| return .{
+ .root_dir = switch (kind) {
+ .temp => none.tmp_artifact_directory.?,
+ .artifact => .cwd(),
+ },
+ .sub_path = path,
+ },
+ .incremental, .whole => return .{
+ .root_dir = comp.dirs.local_cache,
+ .sub_path = try fs.path.join(arena, &.{
+ "o",
+ &Cache.binToHex(comp.digest.?),
+ path,
+ }),
+ },
+ }
+}
fn flush(
comp: *Compilation,
arena: Allocator,
- default_artifact_directory: Cache.Path,
tid: Zcu.PerThread.Id,
prog_node: std.Progress.Node,
) !void {
@@ -2942,19 +3015,32 @@ fn flush(
if (zcu.llvm_object) |llvm_object| {
// Emit the ZCU object from LLVM now; it's required to flush the output file.
// If there's an output file, it wants to decide where the LLVM object goes!
- const zcu_obj_emit_loc: ?EmitLoc = if (comp.bin_file) |lf| .{
- .directory = null,
- .basename = lf.zcu_object_sub_path.?,
- } else null;
const sub_prog_node = prog_node.start("LLVM Emit Object", 0);
defer sub_prog_node.end();
try llvm_object.emit(.{
.pre_ir_path = comp.verbose_llvm_ir,
.pre_bc_path = comp.verbose_llvm_bc,
- .bin_path = try resolveEmitLoc(arena, default_artifact_directory, zcu_obj_emit_loc),
- .asm_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_asm),
- .post_ir_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_ir),
- .post_bc_path = try resolveEmitLoc(arena, default_artifact_directory, comp.emit_llvm_bc),
+
+ .bin_path = p: {
+ const lf = comp.bin_file orelse break :p null;
+ const p = try comp.resolveEmitPathFlush(arena, .temp, lf.zcu_object_basename.?);
+ break :p try p.toStringZ(arena);
+ },
+ .asm_path = p: {
+ const raw = comp.emit_asm orelse break :p null;
+ const p = try comp.resolveEmitPathFlush(arena, .artifact, raw);
+ break :p try p.toStringZ(arena);
+ },
+ .post_ir_path = p: {
+ const raw = comp.emit_llvm_ir orelse break :p null;
+ const p = try comp.resolveEmitPathFlush(arena, .artifact, raw);
+ break :p try p.toStringZ(arena);
+ },
+ .post_bc_path = p: {
+ const raw = comp.emit_llvm_bc orelse break :p null;
+ const p = try comp.resolveEmitPathFlush(arena, .artifact, raw);
+ break :p try p.toStringZ(arena);
+ },
.is_debug = comp.root_mod.optimize_mode == .Debug,
.is_small = comp.root_mod.optimize_mode == .ReleaseSmall,
@@ -3025,45 +3111,6 @@ fn renameTmpIntoCache(
}
}
-/// Communicate the output binary location to parent Compilations.
-fn wholeCacheModeSetBinFilePath(
- comp: *Compilation,
- whole: *CacheUse.Whole,
- digest: *const [Cache.hex_digest_len]u8,
-) void {
- const digest_start = 2; // "o/[digest]/[basename]"
-
- if (whole.bin_sub_path) |sub_path| {
- @memcpy(sub_path[digest_start..][0..digest.len], digest);
- }
-
- if (whole.implib_sub_path) |sub_path| {
- @memcpy(sub_path[digest_start..][0..digest.len], digest);
-
- comp.implib_emit = .{
- .root_dir = comp.dirs.local_cache,
- .sub_path = sub_path,
- };
- }
-
- if (whole.docs_sub_path) |sub_path| {
- @memcpy(sub_path[digest_start..][0..digest.len], digest);
-
- comp.docs_emit = .{
- .root_dir = comp.dirs.local_cache,
- .sub_path = sub_path,
- };
- }
-}
-
-fn prepareWholeEmitSubPath(arena: Allocator, opt_emit: ?EmitLoc) error{OutOfMemory}!?[]u8 {
- const emit = opt_emit orelse return null;
- if (emit.directory != null) return null;
- const s = std.fs.path.sep_str;
- const format = "o" ++ s ++ ("x" ** Cache.hex_digest_len) ++ s ++ "{s}";
- return try std.fmt.allocPrint(arena, format, .{emit.basename});
-}
-
/// This is only observed at compile-time and used to emit a compile error
/// to remind the programmer to update multiple related pieces of code that
/// are in different locations. Bump this number when adding or deleting
@@ -3084,7 +3131,7 @@ fn addNonIncrementalStuffToCacheManifest(
man.hash.addListOfBytes(comp.test_filters);
man.hash.addOptionalBytes(comp.test_name_prefix);
man.hash.add(comp.skip_linker_dependencies);
- //man.hash.add(zcu.emit_h != null);
+ //man.hash.add(zcu.emit_h != .no);
man.hash.add(zcu.error_limit);
} else {
cache_helpers.addModule(&man.hash, comp.root_mod);
@@ -3130,10 +3177,6 @@ fn addNonIncrementalStuffToCacheManifest(
man.hash.addListOfBytes(comp.framework_dirs);
man.hash.addListOfBytes(comp.windows_libs.keys());
- cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm);
- cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir);
- cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc);
-
man.hash.addListOfBytes(comp.global_cc_argv);
const opts = comp.cache_use.whole.lf_open_opts;
@@ -3211,54 +3254,39 @@ fn addNonIncrementalStuffToCacheManifest(
man.hash.addOptional(opts.minor_subsystem_version);
}
-fn emitOthers(comp: *Compilation) void {
- if (comp.config.output_mode != .Obj or comp.zcu != null or
- comp.c_object_table.count() == 0)
- {
- return;
- }
- const obj_path = comp.c_object_table.keys()[0].status.success.object_path;
- const ext = std.fs.path.extension(obj_path.sub_path);
- const dirname = obj_path.sub_path[0 .. obj_path.sub_path.len - ext.len];
- // This obj path always ends with the object file extension, but if we change the
- // extension to .ll, .bc, or .s, then it will be the path to those things.
- const outs = [_]struct {
- emit: ?EmitLoc,
- ext: []const u8,
- }{
- .{ .emit = comp.emit_asm, .ext = ".s" },
- .{ .emit = comp.emit_llvm_ir, .ext = ".ll" },
- .{ .emit = comp.emit_llvm_bc, .ext = ".bc" },
+fn emitFromCObject(
+ comp: *Compilation,
+ arena: Allocator,
+ c_obj_path: Cache.Path,
+ new_ext: []const u8,
+ unresolved_emit_path: []const u8,
+) Allocator.Error!void {
+ // The dirname and stem (i.e. everything but the extension), of the sub path of the C object.
+ // We'll append `new_ext` to it to get the path to the right thing (asm, LLVM IR, etc).
+ const c_obj_dir_and_stem: []const u8 = p: {
+ const p = c_obj_path.sub_path;
+ const ext_len = fs.path.extension(p).len;
+ break :p p[0 .. p.len - ext_len];
};
- for (outs) |out| {
- if (out.emit) |loc| {
- if (loc.directory) |directory| {
- const src_path = std.fmt.allocPrint(comp.gpa, "{s}{s}", .{
- dirname, out.ext,
- }) catch |err| {
- log.err("unable to copy {s}{s}: {s}", .{ dirname, out.ext, @errorName(err) });
- continue;
- };
- defer comp.gpa.free(src_path);
- obj_path.root_dir.handle.copyFile(src_path, directory.handle, loc.basename, .{}) catch |err| {
- log.err("unable to copy {s}: {s}", .{ src_path, @errorName(err) });
- };
- }
- }
- }
-}
+ const src_path: Cache.Path = .{
+ .root_dir = c_obj_path.root_dir,
+ .sub_path = try std.fmt.allocPrint(arena, "{s}{s}", .{
+ c_obj_dir_and_stem,
+ new_ext,
+ }),
+ };
+ const emit_path = comp.resolveEmitPath(unresolved_emit_path);
-fn resolveEmitLoc(
- arena: Allocator,
- default_artifact_directory: Cache.Path,
- opt_loc: ?EmitLoc,
-) Allocator.Error!?[*:0]const u8 {
- const loc = opt_loc orelse return null;
- const slice = if (loc.directory) |directory|
- try directory.joinZ(arena, &.{loc.basename})
- else
- try default_artifact_directory.joinStringZ(arena, loc.basename);
- return slice.ptr;
+ src_path.root_dir.handle.copyFile(
+ src_path.sub_path,
+ emit_path.root_dir.handle,
+ emit_path.sub_path,
+ .{},
+ ) catch |err| log.err("unable to copy '{}' to '{}': {s}", .{
+ src_path,
+ emit_path,
+ @errorName(err),
+ });
}
/// Having the file open for writing is problematic as far as executing the
@@ -4179,7 +4207,7 @@ fn performAllTheWorkInner(
comp.link_task_queue.start(comp);
- if (comp.docs_emit != null) {
+ if (comp.emit_docs != null) {
dev.check(.docs_emit);
comp.thread_pool.spawnWg(&work_queue_wait_group, workerDocsCopy, .{comp});
work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node });
@@ -4457,7 +4485,7 @@ fn performAllTheWorkInner(
};
}
},
- .incremental => {},
+ .none, .incremental => {},
}
if (any_fatal_files or
@@ -4721,12 +4749,12 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void {
const zcu = comp.zcu orelse
return comp.lockAndSetMiscFailure(.docs_copy, "no Zig code to document", .{});
- const emit = comp.docs_emit.?;
- var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| {
+ const docs_path = comp.resolveEmitPath(comp.emit_docs.?);
+ var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
- "unable to create output directory '{}{s}': {s}",
- .{ emit.root_dir, emit.sub_path, @errorName(err) },
+ "unable to create output directory '{}': {s}",
+ .{ docs_path, @errorName(err) },
);
};
defer out_dir.close();
@@ -4745,8 +4773,8 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void {
var tar_file = out_dir.createFile("sources.tar", .{}) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
- "unable to create '{}{s}/sources.tar': {s}",
- .{ emit.root_dir, emit.sub_path, @errorName(err) },
+ "unable to create '{}/sources.tar': {s}",
+ .{ docs_path, @errorName(err) },
);
};
defer tar_file.close();
@@ -4896,11 +4924,6 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye
.parent = root_mod,
});
try root_mod.deps.put(arena, "Walk", walk_mod);
- const bin_basename = try std.zig.binNameAlloc(arena, .{
- .root_name = root_name,
- .target = resolved_target.result,
- .output_mode = output_mode,
- });
const sub_compilation = try Compilation.create(gpa, arena, .{
.dirs = dirs,
@@ -4912,10 +4935,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
- .emit_bin = .{
- .directory = null, // Put it in the cache directory.
- .basename = bin_basename,
- },
+ .emit_bin = .yes_cache,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
@@ -4930,27 +4950,31 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye
try comp.updateSubCompilation(sub_compilation, .docs_wasm, prog_node);
- const emit = comp.docs_emit.?;
- var out_dir = emit.root_dir.handle.makeOpenPath(emit.sub_path, .{}) catch |err| {
+ var crt_file = try sub_compilation.toCrtFile();
+ defer crt_file.deinit(gpa);
+
+ const docs_bin_file = crt_file.full_object_path;
+ assert(docs_bin_file.sub_path.len > 0); // emitted binary is not a directory
+
+ const docs_path = comp.resolveEmitPath(comp.emit_docs.?);
+ var out_dir = docs_path.root_dir.handle.makeOpenPath(docs_path.sub_path, .{}) catch |err| {
return comp.lockAndSetMiscFailure(
.docs_copy,
- "unable to create output directory '{}{s}': {s}",
- .{ emit.root_dir, emit.sub_path, @errorName(err) },
+ "unable to create output directory '{}': {s}",
+ .{ docs_path, @errorName(err) },
);
};
defer out_dir.close();
- sub_compilation.dirs.local_cache.handle.copyFile(
- sub_compilation.cache_use.whole.bin_sub_path.?,
+ crt_file.full_object_path.root_dir.handle.copyFile(
+ crt_file.full_object_path.sub_path,
out_dir,
"main.wasm",
.{},
) catch |err| {
- return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}{s}' to '{}{s}': {s}", .{
- sub_compilation.dirs.local_cache,
- sub_compilation.cache_use.whole.bin_sub_path.?,
- emit.root_dir,
- emit.sub_path,
+ return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}' to '{}': {s}", .{
+ crt_file.full_object_path,
+ docs_path,
@errorName(err),
});
};
@@ -5212,7 +5236,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module
defer whole.cache_manifest_mutex.unlock();
try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename);
},
- .incremental => {},
+ .incremental, .none => {},
}
const bin_digest = man.finalBin();
@@ -5557,9 +5581,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
defer man.deinit();
man.hash.add(comp.clang_preprocessor_mode);
- cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm);
- cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir);
- cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_bc);
+ man.hash.addOptionalBytes(comp.emit_asm);
+ man.hash.addOptionalBytes(comp.emit_llvm_ir);
+ man.hash.addOptionalBytes(comp.emit_llvm_bc);
try cache_helpers.hashCSource(&man, c_object.src);
@@ -5793,7 +5817,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr
try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename);
}
},
- .incremental => {},
+ .incremental, .none => {},
}
}
@@ -6037,7 +6061,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32
defer whole.cache_manifest_mutex.unlock();
try whole_cache_manifest.addFilePost(dep_file_path);
},
- .incremental => {},
+ .incremental, .none => {},
}
}
}
@@ -7209,12 +7233,6 @@ fn buildOutputFromZig(
.cc_argv = &.{},
.parent = null,
});
- const target = comp.getTarget();
- const bin_basename = try std.zig.binNameAlloc(arena, .{
- .root_name = root_name,
- .target = target,
- .output_mode = output_mode,
- });
const parent_whole_cache: ?ParentWholeCache = switch (comp.cache_use) {
.whole => |whole| .{
@@ -7227,7 +7245,7 @@ fn buildOutputFromZig(
3, // global cache is the same
},
},
- .incremental => null,
+ .incremental, .none => null,
};
const sub_compilation = try Compilation.create(gpa, arena, .{
@@ -7240,13 +7258,9 @@ fn buildOutputFromZig(
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
- .emit_bin = .{
- .directory = null, // Put it in the cache directory.
- .basename = bin_basename,
- },
+ .emit_bin = .yes_cache,
.function_sections = true,
.data_sections = true,
- .emit_h = null,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
.verbose_air = comp.verbose_air,
@@ -7366,13 +7380,9 @@ pub fn build_crt_file(
.root_name = root_name,
.thread_pool = comp.thread_pool,
.libc_installation = comp.libc_installation,
- .emit_bin = .{
- .directory = null, // Put it in the cache directory.
- .basename = basename,
- },
+ .emit_bin = .yes_cache,
.function_sections = options.function_sections orelse false,
.data_sections = options.data_sections orelse false,
- .emit_h = null,
.c_source_files = c_source_files,
.verbose_cc = comp.verbose_cc,
.verbose_link = comp.verbose_link,
@@ -7444,7 +7454,11 @@ pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile {
return .{
.full_object_path = .{
.root_dir = comp.dirs.local_cache,
- .sub_path = try comp.gpa.dupe(u8, comp.cache_use.whole.bin_sub_path.?),
+ .sub_path = try std.fs.path.join(comp.gpa, &.{
+ "o",
+ &Cache.binToHex(comp.digest.?),
+ comp.emit_bin.?,
+ }),
},
.lock = comp.cache_use.whole.moveLock(),
};
src/link.zig
@@ -384,9 +384,11 @@ pub const File = struct {
emit: Path,
file: ?fs.File,
- /// 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.
- zcu_object_sub_path: ?[]const u8 = null,
+ /// When using the LLVM backend, the emitted object is written to a file with this name. This
+ /// object file then becomes a normal link input to LLD or a self-hosted linker.
+ ///
+ /// To convert this to an actual path, see `Compilation.resolveEmitPath` (with `kind == .temp`).
+ zcu_object_basename: ?[]const u8 = null,
gc_sections: bool,
print_gc_sections: bool,
build_id: std.zig.BuildId,
@@ -433,7 +435,6 @@ pub const File = struct {
export_symbol_names: []const []const u8,
global_base: ?u64,
build_id: std.zig.BuildId,
- disable_lld_caching: bool,
hash_style: Lld.Elf.HashStyle,
sort_section: ?Lld.Elf.SortSection,
major_subsystem_version: ?u16,
@@ -1083,7 +1084,7 @@ pub const File = struct {
// In this case, an object file is created by the LLVM backend, so
// there is no prelink phase. The Zig code is linked as a standard
// object along with the others.
- if (base.zcu_object_sub_path != null) return;
+ if (base.zcu_object_basename != null) return;
switch (base.tag) {
inline .wasm => |tag| {
@@ -1496,6 +1497,31 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {
},
}
}
+/// After the main pipeline is done, but before flush, the compilation may need to link one final
+/// `Nav` into the binary: the `builtin.test_functions` value. Since the link thread isn't running
+/// by then, we expose this function which can be called directly.
+pub fn linkTestFunctionsNav(pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) void {
+ const zcu = pt.zcu;
+ const comp = zcu.comp;
+ const diags = &comp.link_diags;
+ if (zcu.llvm_object) |llvm_object| {
+ llvm_object.updateNav(pt, nav_index) catch |err| switch (err) {
+ error.OutOfMemory => diags.setAllocFailure(),
+ };
+ } else if (comp.bin_file) |lf| {
+ lf.updateNav(pt, nav_index) catch |err| switch (err) {
+ error.OutOfMemory => diags.setAllocFailure(),
+ error.CodegenFail => zcu.assertCodegenFailed(nav_index),
+ error.Overflow, error.RelocationNotByteAligned => {
+ switch (zcu.codegenFail(nav_index, "unable to codegen: {s}", .{@errorName(err)})) {
+ error.CodegenFail => return,
+ error.OutOfMemory => return diags.setAllocFailure(),
+ }
+ // Not a retryable failure.
+ },
+ };
+ }
+}
/// Provided by the CLI, processed into `LinkInput` instances at the start of
/// the compilation pipeline.
src/main.zig
@@ -699,55 +699,21 @@ const Emit = union(enum) {
yes_default_path,
yes: []const u8,
- const Resolved = struct {
- data: ?Compilation.EmitLoc,
- dir: ?fs.Dir,
-
- fn deinit(self: *Resolved) void {
- if (self.dir) |*dir| {
- dir.close();
- }
- }
- };
-
- fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: bool) !Resolved {
- var resolved: Resolved = .{ .data = null, .dir = null };
- errdefer resolved.deinit();
-
- switch (emit) {
- .no => {},
- .yes_default_path => {
- resolved.data = Compilation.EmitLoc{
- .directory = if (output_to_cache) null else .{
- .path = null,
- .handle = fs.cwd(),
- },
- .basename = default_basename,
- };
- },
- .yes => |full_path| {
- const basename = fs.path.basename(full_path);
- if (fs.path.dirname(full_path)) |dirname| {
- const handle = try fs.cwd().openDir(dirname, .{});
- resolved = .{
- .dir = handle,
- .data = Compilation.EmitLoc{
- .basename = basename,
- .directory = .{
- .path = dirname,
- .handle = handle,
- },
- },
- };
- } else {
- resolved.data = Compilation.EmitLoc{
- .basename = basename,
- .directory = .{ .path = null, .handle = fs.cwd() },
- };
+ const OutputToCacheReason = enum { listen, @"zig run", @"zig test" };
+ fn resolve(emit: Emit, default_basename: []const u8, output_to_cache: ?OutputToCacheReason) Compilation.CreateOptions.Emit {
+ return switch (emit) {
+ .no => .no,
+ .yes_default_path => if (output_to_cache != null) .yes_cache else .{ .yes_path = default_basename },
+ .yes => |path| if (output_to_cache) |reason| {
+ switch (reason) {
+ .listen => fatal("--listen incompatible with explicit output path '{s}'", .{path}),
+ .@"zig run", .@"zig test" => fatal(
+ "'{s}' with explicit output path '{s}' requires explicit '-femit-bin=path' or '-fno-emit-bin'",
+ .{ @tagName(reason), path },
+ ),
}
- },
- }
- return resolved;
+ } else .{ .yes_path = path },
+ };
}
};
@@ -2830,7 +2796,7 @@ fn buildOutputType(
.link => {
create_module.opts.output_mode = if (is_shared_lib) .Lib else .Exe;
if (emit_bin != .no) {
- emit_bin = if (out_path) |p| .{ .yes = p } else EmitBin.yes_a_out;
+ emit_bin = if (out_path) |p| .{ .yes = p } else .yes_a_out;
}
if (emit_llvm) {
fatal("-emit-llvm cannot be used when linking", .{});
@@ -3208,7 +3174,17 @@ fn buildOutputType(
var cleanup_emit_bin_dir: ?fs.Dir = null;
defer if (cleanup_emit_bin_dir) |*dir| dir.close();
- const output_to_cache = listen != .none;
+ // For `zig run` and `zig test`, we don't want to put the binary in the cwd by default. So, if
+ // the binary is requested with no explicit path (as is the default), we emit to the cache.
+ const output_to_cache: ?Emit.OutputToCacheReason = switch (listen) {
+ .stdio, .ip4 => .listen,
+ .none => if (arg_mode == .run and emit_bin == .yes_default_path)
+ .@"zig run"
+ else if (arg_mode == .zig_test and emit_bin == .yes_default_path)
+ .@"zig test"
+ else
+ null,
+ };
const optional_version = if (have_version) version else null;
const root_name = if (provided_name) |n| n else main_mod.fully_qualified_name;
@@ -3225,150 +3201,48 @@ fn buildOutputType(
},
};
- const a_out_basename = switch (target.ofmt) {
- .coff => "a.exe",
- else => "a.out",
- };
-
- const emit_bin_loc: ?Compilation.EmitLoc = switch (emit_bin) {
- .no => null,
- .yes_default_path => Compilation.EmitLoc{
- .directory = blk: {
- switch (arg_mode) {
- .run, .zig_test => break :blk null,
- .build, .cc, .cpp, .translate_c, .zig_test_obj => {
- if (output_to_cache) {
- break :blk null;
- } else {
- break :blk .{ .path = null, .handle = fs.cwd() };
- }
- },
- }
- },
- .basename = if (clang_preprocessor_mode == .pch)
- try std.fmt.allocPrint(arena, "{s}.pch", .{root_name})
- else
- try std.zig.binNameAlloc(arena, .{
+ const emit_bin_resolved: Compilation.CreateOptions.Emit = switch (emit_bin) {
+ .no => .no,
+ .yes_default_path => emit: {
+ if (output_to_cache != null) break :emit .yes_cache;
+ const name = switch (clang_preprocessor_mode) {
+ .pch => try std.fmt.allocPrint(arena, "{s}.pch", .{root_name}),
+ else => try std.zig.binNameAlloc(arena, .{
.root_name = root_name,
.target = target,
.output_mode = create_module.resolved_options.output_mode,
.link_mode = create_module.resolved_options.link_mode,
.version = optional_version,
}),
+ };
+ break :emit .{ .yes_path = name };
},
- .yes => |full_path| b: {
- const basename = fs.path.basename(full_path);
- if (fs.path.dirname(full_path)) |dirname| {
- const handle = fs.cwd().openDir(dirname, .{}) catch |err| {
- fatal("unable to open output directory '{s}': {s}", .{ dirname, @errorName(err) });
- };
- cleanup_emit_bin_dir = handle;
- break :b Compilation.EmitLoc{
- .basename = basename,
- .directory = .{
- .path = dirname,
- .handle = handle,
- },
- };
- } else {
- break :b Compilation.EmitLoc{
- .basename = basename,
- .directory = .{ .path = null, .handle = fs.cwd() },
- };
- }
- },
- .yes_a_out => Compilation.EmitLoc{
- .directory = .{ .path = null, .handle = fs.cwd() },
- .basename = a_out_basename,
+ .yes => |path| if (output_to_cache != null) {
+ assert(output_to_cache == .listen); // there was an explicit bin path
+ fatal("--listen incompatible with explicit output path '{s}'", .{path});
+ } else .{ .yes_path = path },
+ .yes_a_out => emit: {
+ assert(output_to_cache == null);
+ break :emit .{ .yes_path = switch (target.ofmt) {
+ .coff => "a.exe",
+ else => "a.out",
+ } };
},
};
const default_h_basename = try std.fmt.allocPrint(arena, "{s}.h", .{root_name});
- var emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache) catch |err| {
- switch (emit_h) {
- .yes => |p| {
- fatal("unable to open directory from argument '-femit-h', '{s}': {s}", .{
- p, @errorName(err),
- });
- },
- .yes_default_path => {
- fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
- default_h_basename, @errorName(err),
- });
- },
- .no => unreachable,
- }
- };
- defer emit_h_resolved.deinit();
+ const emit_h_resolved = emit_h.resolve(default_h_basename, output_to_cache);
const default_asm_basename = try std.fmt.allocPrint(arena, "{s}.s", .{root_name});
- var emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache) catch |err| {
- switch (emit_asm) {
- .yes => |p| {
- fatal("unable to open directory from argument '-femit-asm', '{s}': {s}", .{
- p, @errorName(err),
- });
- },
- .yes_default_path => {
- fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
- default_asm_basename, @errorName(err),
- });
- },
- .no => unreachable,
- }
- };
- defer emit_asm_resolved.deinit();
+ const emit_asm_resolved = emit_asm.resolve(default_asm_basename, output_to_cache);
const default_llvm_ir_basename = try std.fmt.allocPrint(arena, "{s}.ll", .{root_name});
- var emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache) catch |err| {
- switch (emit_llvm_ir) {
- .yes => |p| {
- fatal("unable to open directory from argument '-femit-llvm-ir', '{s}': {s}", .{
- p, @errorName(err),
- });
- },
- .yes_default_path => {
- fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
- default_llvm_ir_basename, @errorName(err),
- });
- },
- .no => unreachable,
- }
- };
- defer emit_llvm_ir_resolved.deinit();
+ const emit_llvm_ir_resolved = emit_llvm_ir.resolve(default_llvm_ir_basename, output_to_cache);
const default_llvm_bc_basename = try std.fmt.allocPrint(arena, "{s}.bc", .{root_name});
- var emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache) catch |err| {
- switch (emit_llvm_bc) {
- .yes => |p| {
- fatal("unable to open directory from argument '-femit-llvm-bc', '{s}': {s}", .{
- p, @errorName(err),
- });
- },
- .yes_default_path => {
- fatal("unable to open directory from arguments '--name' or '-fsoname', '{s}': {s}", .{
- default_llvm_bc_basename, @errorName(err),
- });
- },
- .no => unreachable,
- }
- };
- defer emit_llvm_bc_resolved.deinit();
+ const emit_llvm_bc_resolved = emit_llvm_bc.resolve(default_llvm_bc_basename, output_to_cache);
- var emit_docs_resolved = emit_docs.resolve("docs", output_to_cache) catch |err| {
- switch (emit_docs) {
- .yes => |p| {
- fatal("unable to open directory from argument '-femit-docs', '{s}': {s}", .{
- p, @errorName(err),
- });
- },
- .yes_default_path => {
- fatal("unable to open directory 'docs': {s}", .{@errorName(err)});
- },
- .no => unreachable,
- }
- };
- defer emit_docs_resolved.deinit();
+ const emit_docs_resolved = emit_docs.resolve("docs", output_to_cache);
const is_exe_or_dyn_lib = switch (create_module.resolved_options.output_mode) {
.Obj => false,
@@ -3378,7 +3252,7 @@ fn buildOutputType(
// Note that cmake when targeting Windows will try to execute
// zig cc to make an executable and output an implib too.
const implib_eligible = is_exe_or_dyn_lib and
- emit_bin_loc != null and target.os.tag == .windows;
+ emit_bin_resolved != .no and target.os.tag == .windows;
if (!implib_eligible) {
if (!emit_implib_arg_provided) {
emit_implib = .no;
@@ -3387,22 +3261,18 @@ fn buildOutputType(
}
}
const default_implib_basename = try std.fmt.allocPrint(arena, "{s}.lib", .{root_name});
- var emit_implib_resolved = switch (emit_implib) {
- .no => Emit.Resolved{ .data = null, .dir = null },
- .yes => |p| emit_implib.resolve(default_implib_basename, output_to_cache) catch |err| {
- fatal("unable to open directory from argument '-femit-implib', '{s}': {s}", .{
- p, @errorName(err),
+ const emit_implib_resolved: Compilation.CreateOptions.Emit = switch (emit_implib) {
+ .no => .no,
+ .yes => emit_implib.resolve(default_implib_basename, output_to_cache),
+ .yes_default_path => emit: {
+ if (output_to_cache != null) break :emit .yes_cache;
+ const p = try fs.path.join(arena, &.{
+ fs.path.dirname(emit_bin_resolved.yes_path) orelse ".",
+ default_implib_basename,
});
- },
- .yes_default_path => Emit.Resolved{
- .data = Compilation.EmitLoc{
- .directory = emit_bin_loc.?.directory,
- .basename = default_implib_basename,
- },
- .dir = null,
+ break :emit .{ .yes_path = p };
},
};
- defer emit_implib_resolved.deinit();
var thread_pool: ThreadPool = undefined;
try thread_pool.init(.{
@@ -3456,7 +3326,7 @@ fn buildOutputType(
src.src_path = try dirs.local_cache.join(arena, &.{sub_path});
}
- if (build_options.have_llvm and emit_asm != .no) {
+ if (build_options.have_llvm and emit_asm_resolved != .no) {
// LLVM has no way to set this non-globally.
const argv = [_][*:0]const u8{ "zig (LLVM option parsing)", "--x86-asm-syntax=intel" };
@import("codegen/llvm/bindings.zig").ParseCommandLineOptions(argv.len, &argv);
@@ -3472,23 +3342,11 @@ fn buildOutputType(
fatal("--debug-incremental requires -fincremental", .{});
}
- const disable_lld_caching = !output_to_cache;
-
const cache_mode: Compilation.CacheMode = b: {
+ // Once incremental compilation is the default, we'll want some smarter logic here,
+ // considering things like the backend in use and whether there's a ZCU.
+ if (output_to_cache == null) break :b .none;
if (incremental) break :b .incremental;
- if (disable_lld_caching) break :b .incremental;
- if (!create_module.resolved_options.have_zcu) break :b .whole;
-
- // TODO: once we support incremental compilation for the LLVM backend
- // via saving the LLVM module into a bitcode file and restoring it,
- // along with compiler state, this clause can be removed so that
- // incremental cache mode is used for LLVM backend too.
- if (create_module.resolved_options.use_llvm) break :b .whole;
-
- // Eventually, this default should be `.incremental`. However, since incremental
- // compilation is currently an opt-in feature, it makes a strictly worse default cache mode
- // than `.whole`.
- // https://github.com/ziglang/zig/issues/21165
break :b .whole;
};
@@ -3510,13 +3368,13 @@ fn buildOutputType(
.main_mod = main_mod,
.root_mod = root_mod,
.std_mod = std_mod,
- .emit_bin = emit_bin_loc,
- .emit_h = emit_h_resolved.data,
- .emit_asm = emit_asm_resolved.data,
- .emit_llvm_ir = emit_llvm_ir_resolved.data,
- .emit_llvm_bc = emit_llvm_bc_resolved.data,
- .emit_docs = emit_docs_resolved.data,
- .emit_implib = emit_implib_resolved.data,
+ .emit_bin = emit_bin_resolved,
+ .emit_h = emit_h_resolved,
+ .emit_asm = emit_asm_resolved,
+ .emit_llvm_ir = emit_llvm_ir_resolved,
+ .emit_llvm_bc = emit_llvm_bc_resolved,
+ .emit_docs = emit_docs_resolved,
+ .emit_implib = emit_implib_resolved,
.lib_directories = create_module.lib_directories.items,
.rpath_list = create_module.rpath_list.items,
.symbol_wrap_set = symbol_wrap_set,
@@ -3599,7 +3457,6 @@ fn buildOutputType(
.test_filters = test_filters.items,
.test_name_prefix = test_name_prefix,
.test_runner_path = test_runner_path,
- .disable_lld_caching = disable_lld_caching,
.cache_mode = cache_mode,
.subsystem = subsystem,
.debug_compile_errors = debug_compile_errors,
@@ -3744,13 +3601,8 @@ fn buildOutputType(
}) {
dev.checkAny(&.{ .run_command, .test_command });
- if (test_exec_args.items.len == 0 and target.ofmt == .c) default_exec_args: {
+ if (test_exec_args.items.len == 0 and target.ofmt == .c and emit_bin_resolved != .no) {
// Default to using `zig run` to execute the produced .c code from `zig test`.
- const c_code_loc = emit_bin_loc orelse break :default_exec_args;
- const c_code_directory = c_code_loc.directory orelse comp.bin_file.?.emit.root_dir;
- const c_code_path = try fs.path.join(arena, &[_][]const u8{
- c_code_directory.path orelse ".", c_code_loc.basename,
- });
try test_exec_args.appendSlice(arena, &.{ self_exe_path, "run" });
if (dirs.zig_lib.path) |p| {
try test_exec_args.appendSlice(arena, &.{ "-I", p });
@@ -3775,7 +3627,7 @@ fn buildOutputType(
if (create_module.dynamic_linker) |dl| {
try test_exec_args.appendSlice(arena, &.{ "--dynamic-linker", dl });
}
- try test_exec_args.append(arena, c_code_path);
+ try test_exec_args.append(arena, null); // placeholder for the path of the emitted C source file
}
try runOrTest(
@@ -4354,12 +4206,22 @@ fn runOrTest(
runtime_args_start: ?usize,
link_libc: bool,
) !void {
- const lf = comp.bin_file orelse return;
- // A naive `directory.join` here will indeed get the correct path to the binary,
- // however, in the case of cwd, we actually want `./foo` so that the path can be executed.
- const exe_path = try fs.path.join(arena, &[_][]const u8{
- lf.emit.root_dir.path orelse ".", lf.emit.sub_path,
- });
+ const raw_emit_bin = comp.emit_bin orelse return;
+ const exe_path = switch (comp.cache_use) {
+ .none => p: {
+ if (fs.path.isAbsolute(raw_emit_bin)) break :p raw_emit_bin;
+ // Use `fs.path.join` to make a file in the cwd is still executed properly.
+ break :p try fs.path.join(arena, &.{
+ ".",
+ raw_emit_bin,
+ });
+ },
+ .whole, .incremental => try comp.dirs.local_cache.join(arena, &.{
+ "o",
+ &Cache.binToHex(comp.digest.?),
+ raw_emit_bin,
+ }),
+ };
var argv = std.ArrayList([]const u8).init(gpa);
defer argv.deinit();
@@ -5087,16 +4949,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
};
};
- const exe_basename = try std.zig.binNameAlloc(arena, .{
- .root_name = "build",
- .target = resolved_target.result,
- .output_mode = .Exe,
- });
- const emit_bin: Compilation.EmitLoc = .{
- .directory = null, // Use the local zig-cache.
- .basename = exe_basename,
- };
-
process.raiseFileDescriptorLimit();
const cwd_path = try introspect.getResolvedCwd(arena);
@@ -5357,8 +5209,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
.config = config,
.root_mod = root_mod,
.main_mod = build_mod,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.self_exe_path = self_exe_path,
.thread_pool = &thread_pool,
.verbose_cc = verbose_cc,
@@ -5386,8 +5237,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
// Since incremental compilation isn't done yet, we use cache_mode = whole
// above, and thus the output file is already closed.
//try comp.makeBinFileExecutable();
- child_argv.items[argv_index_exe] =
- try dirs.local_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
+ child_argv.items[argv_index_exe] = try dirs.local_cache.join(arena, &.{
+ "o",
+ &Cache.binToHex(comp.digest.?),
+ comp.emit_bin.?,
+ });
}
if (process.can_spawn) {
@@ -5504,16 +5358,6 @@ fn jitCmd(
.is_explicit_dynamic_linker = false,
};
- const exe_basename = try std.zig.binNameAlloc(arena, .{
- .root_name = options.cmd_name,
- .target = resolved_target.result,
- .output_mode = .Exe,
- });
- const emit_bin: Compilation.EmitLoc = .{
- .directory = null, // Use the global zig-cache.
- .basename = exe_basename,
- };
-
const self_exe_path = fs.selfExePathAlloc(arena) catch |err| {
fatal("unable to find self exe path: {s}", .{@errorName(err)});
};
@@ -5605,8 +5449,7 @@ fn jitCmd(
.config = config,
.root_mod = root_mod,
.main_mod = root_mod,
- .emit_bin = emit_bin,
- .emit_h = null,
+ .emit_bin = .yes_cache,
.self_exe_path = self_exe_path,
.thread_pool = &thread_pool,
.cache_mode = .whole,
@@ -5637,7 +5480,11 @@ fn jitCmd(
};
}
- const exe_path = try dirs.global_cache.join(arena, &.{comp.cache_use.whole.bin_sub_path.?});
+ const exe_path = try dirs.global_cache.join(arena, &.{
+ "o",
+ &Cache.binToHex(comp.digest.?),
+ comp.emit_bin.?,
+ });
child_argv.appendAssumeCapacity(exe_path);
}
tools/incr-check.zig
@@ -314,7 +314,7 @@ const Eval = struct {
const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len];
const result_dir = ".local-cache" ++ std.fs.path.sep_str ++ "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*);
- const bin_name = try std.zig.binNameAlloc(arena, .{
+ const bin_name = try std.zig.EmitArtifact.bin.cacheName(arena, .{
.root_name = "root", // corresponds to the module name "root"
.target = eval.target.resolved,
.output_mode = .Exe,