Skip to content

Commit 0b4c95e

Browse files
committed
linker: fail the compilation if there were linker errors
There was no check for linker errors after flushing, which meant that if the link failed the build would continue and try to copy the non-existant exe, and also write the manifest as if it had succeeded. Also adds parsing of lld output, which is surfaced at the end of the compilation with the other errors instead of via stderr
1 parent a93fa29 commit 0b4c95e

File tree

4 files changed

+94
-14
lines changed

4 files changed

+94
-14
lines changed

src/Compilation.zig

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ whole_cache_manifest: ?*Cache.Manifest = null,
5151
whole_cache_manifest_mutex: std.Thread.Mutex = .{},
5252

5353
link_error_flags: link.File.ErrorFlags = .{},
54+
lld_errors: std.ArrayListUnmanaged(LldError) = .{},
5455

5556
work_queue: std.fifo.LinearFifo(Job, .Dynamic),
5657
anon_work_queue: std.fifo.LinearFifo(Job, .Dynamic),
@@ -335,6 +336,21 @@ pub const MiscError = struct {
335336
}
336337
};
337338

339+
pub const LldError = struct {
340+
/// Allocated with gpa.
341+
msg: []const u8,
342+
context_lines: []const []const u8 = &.{},
343+
344+
pub fn deinit(self: *LldError, gpa: Allocator) void {
345+
for (self.context_lines) |line| {
346+
gpa.free(line);
347+
}
348+
349+
gpa.free(self.context_lines);
350+
gpa.free(self.msg);
351+
}
352+
};
353+
338354
/// To support incremental compilation, errors are stored in various places
339355
/// so that they can be created and destroyed appropriately. This structure
340356
/// is used to collect all the errors from the various places into one
@@ -2154,6 +2170,11 @@ pub fn destroy(self: *Compilation) void {
21542170
}
21552171
self.failed_c_objects.deinit(gpa);
21562172

2173+
for (self.lld_errors.items) |*lld_error| {
2174+
lld_error.deinit(gpa);
2175+
}
2176+
self.lld_errors.deinit(gpa);
2177+
21572178
self.clearMiscFailures();
21582179

21592180
self.cache_parent.manifest_dir.close();
@@ -2453,6 +2474,10 @@ pub fn update(comp: *Compilation) !void {
24532474
try comp.flush(main_progress_node);
24542475
}
24552476

2477+
if (comp.totalErrorCount() != 0) {
2478+
return;
2479+
}
2480+
24562481
// Failure here only means an unnecessary cache miss.
24572482
man.writeManifest() catch |err| {
24582483
log.warn("failed to write cache manifest: {s}", .{@errorName(err)});
@@ -2482,7 +2507,7 @@ fn flush(comp: *Compilation, prog_node: *std.Progress.Node) !void {
24822507
// This is needed before reading the error flags.
24832508
comp.bin_file.flush(comp, prog_node) catch |err| switch (err) {
24842509
error.FlushFailure => {}, // error reported through link_error_flags
2485-
error.LLDReportedFailure => {}, // error reported through log.err
2510+
error.LLDReportedFailure => {}, // error reported via lockAndParseLldStderr
24862511
else => |e| return e,
24872512
};
24882513
comp.link_error_flags = comp.bin_file.errorFlags();
@@ -2715,7 +2740,7 @@ pub fn makeBinFileWritable(self: *Compilation) !void {
27152740
/// This function is temporally single-threaded.
27162741
pub fn totalErrorCount(self: *Compilation) usize {
27172742
var total: usize = self.failed_c_objects.count() + self.misc_failures.count() +
2718-
@boolToInt(self.alloc_failure_occurred);
2743+
@boolToInt(self.alloc_failure_occurred) + self.lld_errors.items.len;
27192744

27202745
if (self.bin_file.options.module) |module| {
27212746
total += module.failed_exports.count();
@@ -2803,6 +2828,21 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
28032828
});
28042829
}
28052830
}
2831+
for (self.lld_errors.items) |lld_error| {
2832+
const notes = try arena_allocator.alloc(AllErrors.Message, lld_error.context_lines.len);
2833+
for (lld_error.context_lines) |context_line, i| {
2834+
notes[i] = .{ .plain = .{
2835+
.msg = try arena_allocator.dupe(u8, context_line),
2836+
} };
2837+
}
2838+
2839+
try errors.append(.{
2840+
.plain = .{
2841+
.msg = try arena_allocator.dupe(u8, lld_error.msg),
2842+
.notes = notes,
2843+
},
2844+
});
2845+
}
28062846
for (self.misc_failures.values()) |*value| {
28072847
try AllErrors.addPlainWithChildren(&arena, &errors, value.msg, value.children);
28082848
}
@@ -4977,6 +5017,49 @@ pub fn lockAndSetMiscFailure(
49775017
return setMiscFailure(comp, tag, format, args);
49785018
}
49795019

5020+
fn parseLddStderr(comp: *Compilation, comptime prefix: []const u8, stderr: []const u8) Allocator.Error!void {
5021+
var context_lines = std.ArrayList([]const u8).init(comp.gpa);
5022+
defer context_lines.deinit();
5023+
5024+
var current_err: ?*LldError = null;
5025+
var lines = mem.split(u8, stderr, std.cstr.line_sep);
5026+
while (lines.next()) |line| {
5027+
if (mem.startsWith(u8, line, prefix ++ ":")) {
5028+
if (current_err) |err| {
5029+
err.context_lines = context_lines.toOwnedSlice();
5030+
}
5031+
5032+
const duped_msg = try comp.gpa.dupe(u8, line);
5033+
errdefer comp.gpa.free(duped_msg);
5034+
5035+
current_err = try comp.lld_errors.addOne(comp.gpa);
5036+
current_err.?.* = .{ .msg = duped_msg };
5037+
} else if (current_err != null) {
5038+
const context_prefix = ">>> ";
5039+
var trimmed = mem.trimRight(u8, line, &std.ascii.whitespace);
5040+
if (mem.startsWith(u8, trimmed, context_prefix)) {
5041+
trimmed = trimmed[context_prefix.len..];
5042+
}
5043+
5044+
if (trimmed.len > 0) {
5045+
const duped_line = try comp.gpa.dupe(u8, trimmed);
5046+
try context_lines.append(duped_line);
5047+
}
5048+
}
5049+
}
5050+
5051+
if (current_err) |err| {
5052+
err.context_lines = context_lines.toOwnedSlice();
5053+
}
5054+
}
5055+
5056+
pub fn lockAndParseLldStderr(comp: *Compilation, comptime prefix: []const u8, stderr: []const u8) void {
5057+
comp.mutex.lock();
5058+
defer comp.mutex.unlock();
5059+
5060+
comp.parseLddStderr(prefix, stderr) catch comp.setAllocFailure();
5061+
}
5062+
49805063
pub fn dump_argv(argv: []const []const u8) void {
49815064
for (argv[0 .. argv.len - 1]) |arg| {
49825065
std.debug.print("{s} ", .{arg});

src/link/Coff/lld.zig

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,8 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
175175
// We will invoke ourselves as a child process to gain access to LLD.
176176
// This is necessary because LLD does not behave properly as a library -
177177
// it calls exit() and does not reset all global data between invocations.
178-
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "lld-link" });
178+
const linker_command = "lld-link";
179+
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
179180

180181
try argv.append("-ERRORLIMIT:0");
181182
try argv.append("-NOLOGO");
@@ -556,9 +557,7 @@ pub fn linkWithLLD(self: *Coff, comp: *Compilation, prog_node: *std.Progress.Nod
556557
switch (term) {
557558
.Exited => |code| {
558559
if (code != 0) {
559-
// TODO parse this output and surface with the Compilation API rather than
560-
// directly outputting to stderr here.
561-
std.debug.print("{s}", .{stderr});
560+
comp.lockAndParseLldStderr(linker_command, stderr);
562561
return error.LLDReportedFailure;
563562
}
564563
},

src/link/Elf.zig

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1422,7 +1422,8 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
14221422
// We will invoke ourselves as a child process to gain access to LLD.
14231423
// This is necessary because LLD does not behave properly as a library -
14241424
// it calls exit() and does not reset all global data between invocations.
1425-
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "ld.lld" });
1425+
const linker_command = "ld.lld";
1426+
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
14261427
if (is_obj) {
14271428
try argv.append("-r");
14281429
}
@@ -1841,9 +1842,7 @@ fn linkWithLLD(self: *Elf, comp: *Compilation, prog_node: *std.Progress.Node) !v
18411842
switch (term) {
18421843
.Exited => |code| {
18431844
if (code != 0) {
1844-
// TODO parse this output and surface with the Compilation API rather than
1845-
// directly outputting to stderr here.
1846-
std.debug.print("{s}", .{stderr});
1845+
comp.lockAndParseLldStderr(linker_command, stderr);
18471846
return error.LLDReportedFailure;
18481847
}
18491848
},

src/link/Wasm.zig

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3125,7 +3125,8 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
31253125
// We will invoke ourselves as a child process to gain access to LLD.
31263126
// This is necessary because LLD does not behave properly as a library -
31273127
// it calls exit() and does not reset all global data between invocations.
3128-
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, "wasm-ld" });
3128+
const linker_command = "wasm-ld";
3129+
try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command });
31293130
try argv.append("--error-limit=0");
31303131

31313132
if (wasm.base.options.lto) {
@@ -3357,9 +3358,7 @@ fn linkWithLLD(wasm: *Wasm, comp: *Compilation, prog_node: *std.Progress.Node) !
33573358
switch (term) {
33583359
.Exited => |code| {
33593360
if (code != 0) {
3360-
// TODO parse this output and surface with the Compilation API rather than
3361-
// directly outputting to stderr here.
3362-
std.debug.print("{s}", .{stderr});
3361+
comp.lockAndParseLldStderr(linker_command, stderr);
33633362
return error.LLDReportedFailure;
33643363
}
33653364
},

0 commit comments

Comments
 (0)