authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-09-30 18:05:31-07:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-10-01 16:16:54+02:00
log7f8f2b16a0a523a1473b172e9f8a585087e879fc
treeeb637e775d45379adc6157f75db5e05ffc30b1d4
parent4f07ceaf8e6ba13bbd062bc8d8f9a64a6ac50713

Compilation: fall back to response file when invoking `zig clang`


2 files changed, 192 insertions(+), 160 deletions(-)

src/Compilation.zig+179-62
...@@ -5676,23 +5676,18 @@ fn updateCObject(...@@ -5676,23 +5676,18 @@ fn updateCObject(
5676 else => log.warn("failed to delete {q}: {t}", .{ dep_file_path, err }),5676 else => log.warn("failed to delete {q}: {t}", .{ dep_file_path, err }),
5677 };5677 };
5678 if (std.process.can_spawn) {5678 if (std.process.can_spawn) {
5679 var diags: EvalZigLlvmProcessDiagnostics = undefined;
5680 const result = comp.evalZigLlvmProcess(arena, &diags, argv.items) catch |err| switch (err) {
5681 else => |e| return e,
5682 error.EvalZigLlvmFail => return comp.failCObj(
5683 c_object,
5684 "failed to evaluate zig clang '{s}': {f}",
5685 .{ argv.items[0], diags },
5686 ),
5687 };
5688
5679 if (comp.clang_passthrough_mode) {5689 if (comp.clang_passthrough_mode) {
5680 var child = std.process.spawn(io, .{5690 switch (result.term) {
5681 .argv = argv.items,
5682 .stdin = .inherit,
5683 .stdout = .inherit,
5684 .stderr = .inherit,
5685 }) catch |err| {
5686 return comp.failCObj(c_object, "failed to spawn zig clang (passthrough mode) {s}: {t}", .{
5687 argv.items[0], err,
5688 });
5689 };
5690 const term = child.wait(io) catch |err| {
5691 return comp.failCObj(c_object, "failed to wait zig clang (passthrough mode) {s}: {t}", .{
5692 argv.items[0], err,
5693 });
5694 };
5695 switch (term) {
5696 .exited => |code| {5691 .exited => |code| {
5697 if (code != 0) {5692 if (code != 0) {
5698 std.process.exit(code);5693 std.process.exit(code);
...@@ -5705,67 +5700,27 @@ fn updateCObject(...@@ -5705,67 +5700,27 @@ fn updateCObject(
5705 else => std.process.abort(),5700 else => std.process.abort(),
5706 }5701 }
5707 } else {5702 } else {
5708 var child = std.process.spawn(io, .{5703 switch (result.term) {
5709 .argv = argv.items,
5710 .stdin = .ignore,
5711 .stdout = .ignore,
5712 .stderr = .pipe,
5713 }) catch |err| switch (err) {
5714 error.Canceled,
5715 error.OutOfMemory,
5716 => |e| return e,
5717 else => |e| return comp.failCObjRetryable(
5718 c_object,
5719 "failed to spawn zig clang '{s}': {t}",
5720 .{ argv.items[0], e },
5721 ),
5722 };
5723
5724 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
5725 const stderr = stderr_reader.interface.allocRemaining(arena, .limited(std.math.maxInt(u32))) catch |err| switch (err) {
5726 error.OutOfMemory => |e| return e,
5727 error.StreamTooLong => return comp.failCObjRetryable(
5728 c_object,
5729 "failed to read stderr of zig clang '{s}': exceeded maximum size",
5730 .{argv.items[0]},
5731 ),
5732 error.ReadFailed => return comp.failCObjRetryable(
5733 c_object,
5734 "failed to read stderr of zig clang '{s}': {t}",
5735 .{ argv.items[0], stderr_reader.err.? },
5736 ),
5737 };
5738
5739 const term = child.wait(io) catch |err| switch (err) {
5740 error.Canceled => |e| return e,
5741 else => |e| return comp.failCObj(
5742 c_object,
5743 "failed to spawn zig clang '{s}': {t}",
5744 .{ argv.items[0], e },
5745 ),
5746 };
5747
5748 switch (term) {
5749 .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| {5704 .exited => |code| if (code != 0) if (out_diag_path) |diag_file_path| {
5750 const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| {5705 const bundle = CObject.Diag.Bundle.parse(gpa, io, diag_file_path) catch |err| {
5751 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, stderr });5706 log.err("{}: failed to parse clang diagnostics: {s}", .{ err, result.stderr });
5752 return comp.failCObj(c_object, "clang exited with code {d}", .{code});5707 return comp.failCObj(c_object, "clang exited with code {d}", .{code});
5753 };5708 };
5754 return comp.failCObjWithOwnedDiagBundle(c_object, bundle);5709 return comp.failCObjWithOwnedDiagBundle(c_object, bundle);
5755 } else {5710 } else {
5756 log.err("clang failed with stderr: {s}", .{stderr});5711 log.err("clang failed with stderr: {s}", .{result.stderr});
5757 return comp.failCObj(c_object, "clang exited with code {d}", .{code});5712 return comp.failCObj(c_object, "clang exited with code {d}", .{code});
5758 },5713 },
5759 .signal => |sig| {5714 .signal => |sig| {
5760 log.err("clang failed with stderr: {s}", .{stderr});5715 log.err("clang failed with stderr: {s}", .{result.stderr});
5761 return comp.failCObj(c_object, "clang terminated with signal {t}", .{sig});5716 return comp.failCObj(c_object, "clang terminated with signal {t}", .{sig});
5762 },5717 },
5763 .stopped => |sig| {5718 .stopped => |sig| {
5764 log.err("clang failed with stderr: {s}", .{stderr});5719 log.err("clang failed with stderr: {s}", .{result.stderr});
5765 return comp.failCObj(c_object, "clang stopped with signal {t}", .{sig});5720 return comp.failCObj(c_object, "clang stopped with signal {t}", .{sig});
5766 },5721 },
5767 .unknown => {5722 .unknown => {
5768 log.err("clang terminated with stderr: {s}", .{stderr});5723 log.err("clang terminated with stderr: {s}", .{result.stderr});
5769 return comp.failCObj(c_object, "clang terminated unexpectedly", .{});5724 return comp.failCObj(c_object, "clang terminated unexpectedly", .{});
5770 },5725 },
5771 }5726 }
...@@ -7763,3 +7718,165 @@ pub fn compilerRtOptMode(comp: Compilation) std.lang.Optimize {...@@ -7763,3 +7718,165 @@ pub fn compilerRtOptMode(comp: Compilation) std.lang.Optimize {
7763pub fn compilerRtStrip(comp: Compilation) bool {7718pub fn compilerRtStrip(comp: Compilation) bool {
7764 return comp.root_mod.strip;7719 return comp.root_mod.strip;
7765}7720}
7721
7722pub const EvalZigLlvmProcessDiagnostics = union(enum) {
7723 spawn_err: std.process.SpawnError,
7724 read_stderr_err: Io.File.Reader.Error,
7725 write_rsp_file_err: struct {
7726 path: []const u8,
7727 err: (Io.File.OpenError || Io.File.Writer.Error),
7728 },
7729 pub fn format(diags: EvalZigLlvmProcessDiagnostics, w: *Writer) Writer.Error!void {
7730 switch (diags) {
7731 .spawn_err => |err| try w.print(
7732 "failed to spawn: {t}",
7733 .{err},
7734 ),
7735 .read_stderr_err => |err| try w.print(
7736 "failed to read stderr: {t}",
7737 .{err},
7738 ),
7739 .write_rsp_file_err => |write_rsp_file| try w.print(
7740 "failed to write '{s}': {t}",
7741 .{ write_rsp_file.path, write_rsp_file.err },
7742 ),
7743 }
7744 }
7745};
7746pub const EvalZigLlvmProcessResult = struct {
7747 term: std.process.Child.Term,
7748 stderr: []const u8,
7749};
7750
7751/// Assumes that `opts.argv` is a Zig LLVM wrapper subcommand (e.g. `zig clang ...`), and attempts
7752/// to spawn it; first trying to spawn it directly, but upon `error.NameTooLong`, falling back to
7753/// writing a response file and passing it to the command (e.g. `zig clang @path/to/args.rsp`).
7754pub fn evalZigLlvmProcess(
7755 comp: *const Compilation,
7756 arena: Allocator,
7757 diags: *EvalZigLlvmProcessDiagnostics,
7758 argv: []const []const u8,
7759) error{ EvalZigLlvmFail, OutOfMemory, Canceled }!EvalZigLlvmProcessResult {
7760 const io = comp.io;
7761
7762 if (std.process.spawn(io, .{
7763 .argv = argv,
7764 .stdin = if (comp.clang_passthrough_mode) .inherit else .ignore,
7765 .stdout = if (comp.clang_passthrough_mode) .inherit else .ignore,
7766 .stderr = if (comp.clang_passthrough_mode) .inherit else .pipe,
7767 })) |child| {
7768 return comp.finishEvalZigLlvmProcess(arena, child, diags);
7769 } else |err| switch (err) {
7770 error.Canceled => |e| return e,
7771 error.NameTooLong => {}, // fallback logic below
7772 else => |e| {
7773 diags.* = .{ .spawn_err = e };
7774 return error.EvalZigLlvmFail;
7775 },
7776 }
7777
7778 // Main command line was too long; try a response file.
7779 const rand_int: u64 = r: {
7780 var x: u64 = undefined;
7781 io.random(@ptrCast(&x));
7782 break :r x;
7783 };
7784 const rsp_cache_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int) ++ ".rsp";
7785
7786 const rsp_file = comp.dirs.local_cache.handle.createFile(io, rsp_cache_sub_path, .{}) catch |err| switch (err) {
7787 error.Canceled => |e| return e,
7788 else => |e| {
7789 diags.* = .{ .write_rsp_file_err = .{
7790 .path = try arena.dupe(u8, rsp_cache_sub_path),
7791 .err = e,
7792 } };
7793 return error.EvalZigLlvmFail;
7794 },
7795 };
7796 defer comp.dirs.local_cache.handle.deleteFile(io, rsp_cache_sub_path) catch |err|
7797 log.warn("failed to delete response file {s}: {t}", .{ rsp_cache_sub_path, err });
7798
7799 {
7800 defer rsp_file.close(io);
7801 var buf: [1024]u8 = undefined;
7802 var rsp_fw = rsp_file.writer(io, &buf);
7803 writeResponseFile(argv[2..], &rsp_fw.interface) catch |err| switch (err) {
7804 error.WriteFailed => switch (rsp_fw.err.?) {
7805 error.Canceled => |e| return e,
7806 else => |e| {
7807 diags.* = .{ .write_rsp_file_err = .{
7808 .path = try arena.dupe(u8, rsp_cache_sub_path),
7809 .err = e,
7810 } };
7811 return error.EvalZigLlvmFail;
7812 },
7813 },
7814 };
7815 }
7816
7817 const rsp_arg = try arena.print("@{s}{c}{s}", .{
7818 comp.dirs.local_cache.path orelse ".",
7819 fs.path.sep,
7820 rsp_cache_sub_path,
7821 });
7822
7823 if (std.process.spawn(io, .{
7824 .argv = &.{ argv[0], argv[1], rsp_arg },
7825 .stdin = if (comp.clang_passthrough_mode) .inherit else .ignore,
7826 .stdout = if (comp.clang_passthrough_mode) .inherit else .ignore,
7827 .stderr = if (comp.clang_passthrough_mode) .inherit else .pipe,
7828 })) |child| {
7829 return comp.finishEvalZigLlvmProcess(arena, child, diags);
7830 } else |err| switch (err) {
7831 error.Canceled => |e| return e,
7832 else => |e| {
7833 diags.* = .{ .spawn_err = e };
7834 return error.EvalZigLlvmFail;
7835 },
7836 }
7837}
7838fn writeResponseFile(args: []const []const u8, w: *Writer) Writer.Error!void {
7839 for (args) |arg| {
7840 try w.writeByte('"');
7841 for (arg) |c| {
7842 switch (c) {
7843 '\"', '\\' => try w.writeByte('\\'),
7844 else => {},
7845 }
7846 try w.writeByte(c);
7847 }
7848 try w.writeAll("\"\n");
7849 }
7850 try w.flush();
7851}
7852fn finishEvalZigLlvmProcess(
7853 comp: *const Compilation,
7854 arena: Allocator,
7855 child_arg: std.process.Child,
7856 diags: *EvalZigLlvmProcessDiagnostics,
7857) error{ EvalZigLlvmFail, OutOfMemory, Canceled }!EvalZigLlvmProcessResult {
7858 const io = comp.io;
7859 var child = child_arg;
7860 defer child.kill(io);
7861 if (comp.clang_passthrough_mode) {
7862 const term = child.wait(io) catch |err| {
7863 diags.* = .{ .spawn_err = err };
7864 return error.EvalZigLlvmFail;
7865 };
7866 return .{ .stderr = &.{}, .term = term };
7867 }
7868 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
7869 const stderr = stderr_reader.interface.allocRemaining(arena, .unlimited) catch |err| switch (err) {
7870 error.StreamTooLong => unreachable, // unlimited
7871 error.OutOfMemory => |e| return e,
7872 error.ReadFailed => {
7873 diags.* = .{ .read_stderr_err = stderr_reader.err.? };
7874 return error.EvalZigLlvmFail;
7875 },
7876 };
7877 const term = child.wait(io) catch |err| {
7878 diags.* = .{ .spawn_err = err };
7879 return error.EvalZigLlvmFail;
7880 };
7881 return .{ .stderr = stderr, .term = term };
7882}
src/link/Lld.zig+13-98
...@@ -1641,7 +1641,6 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {...@@ -1641,7 +1641,6 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void {
16411641
1642fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {1642fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !void {
1643 const io = comp.io;1643 const io = comp.io;
1644 const gpa = comp.gpa;
16451644
1646 if (comp.verbose_link) {1645 if (comp.verbose_link) {
1647 // Skip over our own name so that the LLD linker name is the first argv item.1646 // Skip over our own name so that the LLD linker name is the first argv item.
...@@ -1658,120 +1657,36 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi...@@ -1658,120 +1657,36 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi
1658 return error.AlreadyReported;1657 return error.AlreadyReported;
1659 }1658 }
16601659
1661 var stderr: []u8 = &.{};1660 var diags: Compilation.EvalZigLlvmProcessDiagnostics = undefined;
1662 defer gpa.free(stderr);1661 const result = comp.evalZigLlvmProcess(arena, &diags, argv) catch |err| switch (err) {
16631662 else => |e| return e,
1664 // TODO rework this awkward logic to call child.kill() in the failure case1663 error.EvalZigLlvmFail => {
1665 const term = (if (comp.clang_passthrough_mode) term: {1664 log.err("failed to evaluate LLD '{s}': {f}", .{ argv[0], diags });
1666 var child = std.process.spawn(io, .{1665 return error.UnableToSpawnSelf;
1667 .argv = argv,1666 },
1668 .stdin = .inherit,
1669 .stdout = .inherit,
1670 .stderr = .inherit,
1671 }) catch |err| break :term err;
1672
1673 break :term child.wait(io);
1674 } else term: {
1675 var child = std.process.spawn(io, .{
1676 .argv = argv,
1677 .stdin = .ignore,
1678 .stdout = .ignore,
1679 .stderr = .pipe,
1680 }) catch |err| break :term err;
1681
1682 var stderr_reader = child.stderr.?.readerStreaming(io, &.{});
1683 stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) {
1684 error.StreamTooLong => unreachable, // unlimited
1685 error.OutOfMemory => |e| return e,
1686 error.ReadFailed => return stderr_reader.err.?,
1687 };
1688 break :term child.wait(io);
1689 }) catch |first_err| term: {
1690 const err = switch (first_err) {
1691 error.NameTooLong => err: {
1692 const s = fs.path.sep_str;
1693 const rand_int = r: {
1694 var x: u64 = undefined;
1695 io.random(@ptrCast(&x));
1696 break :r x;
1697 };
1698 const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp";
1699
1700 const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{});
1701 defer comp.dirs.local_cache.handle.deleteFile(io, rsp_path) catch |err|
1702 log.warn("failed to delete response file {s}: {t}", .{ rsp_path, err });
1703 {
1704 defer rsp_file.close(io);
1705 var rsp_file_buffer: [1024]u8 = undefined;
1706 var rsp_file_writer = rsp_file.writer(io, &rsp_file_buffer);
1707 const rsp_writer = &rsp_file_writer.interface;
1708 for (argv[2..]) |arg| {
1709 try rsp_writer.writeByte('"');
1710 for (arg) |c| {
1711 switch (c) {
1712 '\"', '\\' => try rsp_writer.writeByte('\\'),
1713 else => {},
1714 }
1715 try rsp_writer.writeByte(c);
1716 }
1717 try rsp_writer.writeByte('"');
1718 try rsp_writer.writeByte('\n');
1719 }
1720 try rsp_writer.flush();
1721 }
1722
1723 var rsp_child = std.process.spawn(io, .{
1724 .argv = &.{
1725 argv[0],
1726 argv[1],
1727 try arena.print("@{s}", .{
1728 try comp.dirs.local_cache.join(arena, &.{rsp_path}),
1729 }),
1730 },
1731 .stdin = if (comp.clang_passthrough_mode) .inherit else .ignore,
1732 .stdout = if (comp.clang_passthrough_mode) .inherit else .ignore,
1733 .stderr = if (comp.clang_passthrough_mode) .inherit else .pipe,
1734 }) catch |err| break :err err;
1735 if (comp.clang_passthrough_mode) {
1736 break :term rsp_child.wait(io) catch |err| break :err err;
1737 } else {
1738 var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{});
1739 stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) {
1740 error.StreamTooLong => unreachable, // unlimited
1741 error.OutOfMemory => |e| return e,
1742 error.ReadFailed => return stderr_reader.err.?,
1743 };
1744 break :term rsp_child.wait(io) catch |err| break :err err;
1745 }
1746 },
1747 else => first_err,
1748 };
1749 log.err("unable to spawn LLD {s}: {t}", .{ argv[0], err });
1750 return error.UnableToSpawnSelf;
1751 };1667 };
17521668
1753 const diags = &comp.link_diags;1669 switch (result.term) {
1754 switch (term) {
1755 .exited => |code| if (code != 0) {1670 .exited => |code| if (code != 0) {
1756 if (comp.clang_passthrough_mode) std.process.exit(code);1671 if (comp.clang_passthrough_mode) std.process.exit(code);
1757 diags.lockAndParseLldStderr(argv[1], stderr);1672 comp.link_diags.lockAndParseLldStderr(argv[1], result.stderr);
1758 return error.AlreadyReported;1673 return error.AlreadyReported;
1759 },1674 },
1760 .signal => |sig| {1675 .signal => |sig| {
1761 if (comp.clang_passthrough_mode) std.process.abort();1676 if (comp.clang_passthrough_mode) std.process.abort();
1762 return diags.fail("{s} terminated with signal {t} and stderr:\n{s}", .{ argv[0], sig, stderr });1677 return comp.link_diags.fail("{s} terminated with signal {t} and stderr:\n{s}", .{ argv[0], sig, result.stderr });
1763 },1678 },
1764 .stopped => |sig| {1679 .stopped => |sig| {
1765 if (comp.clang_passthrough_mode) std.process.abort();1680 if (comp.clang_passthrough_mode) std.process.abort();
1766 return diags.fail("{s} stopped with signal {t} and stderr:\n{s}", .{ argv[0], sig, stderr });1681 return comp.link_diags.fail("{s} stopped with signal {t} and stderr:\n{s}", .{ argv[0], sig, result.stderr });
1767 },1682 },
1768 .unknown => |code| {1683 .unknown => |code| {
1769 if (comp.clang_passthrough_mode) std.process.abort();1684 if (comp.clang_passthrough_mode) std.process.abort();
1770 return diags.fail("{s} terminated for unknown reason with code {d} and stderr:\n{s}", .{ argv[0], code, stderr });1685 return comp.link_diags.fail("{s} terminated for unknown reason with code {d} and stderr:\n{s}", .{ argv[0], code, result.stderr });
1771 },1686 },
1772 }1687 }
17731688
1774 if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr});1689 if (result.stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{result.stderr});
1775}1690}
17761691
1777const builtin = @import("builtin");1692const builtin = @import("builtin");