authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-08-15 11:18:45+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-08-15 20:00:30+01:00
log8adabaa4ed2321882e2ef04ebb2d9b621805aac2
treea1ff7a83fbfcd976d17b1f19cd1e848c08da0793
parentce2c9399dd44943079dde5c00af4fead01d3d9ef

Zcu: don't tell linkers about exports if there are compile errors

In the best case, this is redundant work, because we aren't actually going to emit a working binary this update. In the worst case, it causes bugs because the linker may not have *seen* the thing being exported due to the compile errors. Resolves: #24417

3 files changed, 28 insertions(+), 12 deletions(-)

src/Compilation.zig+3-7
......@@ -4254,14 +4254,10 @@ fn appendCompileLogLines(log_text: *std.ArrayListUnmanaged(u8), zcu: *Zcu, loggi
42544254 }
42554255}
42564256
4257fn anyErrors(comp: *Compilation) bool {
4258 return (totalErrorCount(comp) catch return true) != 0;
4259}
4260
4261fn totalErrorCount(comp: *Compilation) !u32 {
4262 var errors = try comp.getAllErrorsAlloc();
4257pub fn anyErrors(comp: *Compilation) bool {
4258 var errors = comp.getAllErrorsAlloc() catch return true;
42634259 defer errors.deinit(comp.gpa);
4264 return errors.errorMessageCount();
4260 return errors.errorMessageCount() > 0;
42654261}
42664262
42674263pub const ErrorNoteHashContext = struct {
src/Zcu/PerThread.zig+12-5
......@@ -3149,18 +3149,23 @@ pub fn processExports(pt: Zcu.PerThread) !void {
31493149 }
31503150 }
31513151
3152 // If there are compile errors, we won't call `updateExports`. Not only would it be redundant
3153 // work, but the linker may not have seen an exported `Nav` due to a compile error, so linker
3154 // implementations would have to handle that case. This early return avoids that.
3155 const skip_linker_work = zcu.comp.anyErrors();
3156
31523157 // Map symbol names to `Export` for name collision detection.
31533158 var symbol_exports: SymbolExports = .{};
31543159 defer symbol_exports.deinit(gpa);
31553160
31563161 for (nav_exports.keys(), nav_exports.values()) |exported_nav, exports_list| {
31573162 const exported: Zcu.Exported = .{ .nav = exported_nav };
3158 try pt.processExportsInner(&symbol_exports, exported, exports_list.items);
3163 try pt.processExportsInner(&symbol_exports, exported, exports_list.items, skip_linker_work);
31593164 }
31603165
31613166 for (uav_exports.keys(), uav_exports.values()) |exported_uav, exports_list| {
31623167 const exported: Zcu.Exported = .{ .uav = exported_uav };
3163 try pt.processExportsInner(&symbol_exports, exported, exports_list.items);
3168 try pt.processExportsInner(&symbol_exports, exported, exports_list.items, skip_linker_work);
31643169 }
31653170}
31663171
......@@ -3171,6 +3176,7 @@ fn processExportsInner(
31713176 symbol_exports: *SymbolExports,
31723177 exported: Zcu.Exported,
31733178 export_indices: []const Zcu.Export.Index,
3179 skip_linker_work: bool,
31743180) error{OutOfMemory}!void {
31753181 const zcu = pt.zcu;
31763182 const gpa = zcu.gpa;
......@@ -3216,13 +3222,14 @@ fn processExportsInner(
32163222 }
32173223 break :failed false;
32183224 }) {
3219 // This `Decl` is failed, so was never sent to codegen.
3220 // TODO: we should probably tell the backend to delete any old exports of this `Decl`?
3221 return;
3225 // This `Nav` is failed, so was never sent to codegen. There should be a compile error.
3226 assert(skip_linker_work);
32223227 },
32233228 .uav => {},
32243229 }
32253230
3231 if (skip_linker_work) return;
3232
32263233 if (zcu.llvm_object) |llvm_object| {
32273234 try zcu.handleUpdateExports(export_indices, llvm_object.updateExports(pt, exported, export_indices));
32283235 } else if (zcu.comp.bin_file) |lf| {
test/cases/compile_errors/exported_function_uses_invalid_type.zig created+13
......@@ -0,0 +1,13 @@
1export fn foo() void {
2 const S = struct { x: u32 = "bad default" };
3 const s: S = undefined;
4 _ = s;
5}
6
7// This test case explicitly runs on the LLVM backend as well as self-hosted, as
8// the original bug leading to this test occurred only with the LLVM backend.
9
10// error
11// backend=stage2,llvm
12//
13// :2:33: error: expected type 'u32', found '*const [11:0]u8'