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...@@ -4254,14 +4254,10 @@ fn appendCompileLogLines(log_text: *std.ArrayListUnmanaged(u8), zcu: *Zcu, loggi
4254 }4254 }
4255}4255}
42564256
4257fn anyErrors(comp: *Compilation) bool {4257pub fn anyErrors(comp: *Compilation) bool {
4258 return (totalErrorCount(comp) catch return true) != 0;4258 var errors = comp.getAllErrorsAlloc() catch return true;
4259}
4260
4261fn totalErrorCount(comp: *Compilation) !u32 {
4262 var errors = try comp.getAllErrorsAlloc();
4263 defer errors.deinit(comp.gpa);4259 defer errors.deinit(comp.gpa);
4264 return errors.errorMessageCount();4260 return errors.errorMessageCount() > 0;
4265}4261}
42664262
4267pub const ErrorNoteHashContext = struct {4263pub const ErrorNoteHashContext = struct {
src/Zcu/PerThread.zig+12-5
...@@ -3149,18 +3149,23 @@ pub fn processExports(pt: Zcu.PerThread) !void {...@@ -3149,18 +3149,23 @@ pub fn processExports(pt: Zcu.PerThread) !void {
3149 }3149 }
3150 }3150 }
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
3152 // Map symbol names to `Export` for name collision detection.3157 // Map symbol names to `Export` for name collision detection.
3153 var symbol_exports: SymbolExports = .{};3158 var symbol_exports: SymbolExports = .{};
3154 defer symbol_exports.deinit(gpa);3159 defer symbol_exports.deinit(gpa);
31553160
3156 for (nav_exports.keys(), nav_exports.values()) |exported_nav, exports_list| {3161 for (nav_exports.keys(), nav_exports.values()) |exported_nav, exports_list| {
3157 const exported: Zcu.Exported = .{ .nav = exported_nav };3162 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);
3159 }3164 }
31603165
3161 for (uav_exports.keys(), uav_exports.values()) |exported_uav, exports_list| {3166 for (uav_exports.keys(), uav_exports.values()) |exported_uav, exports_list| {
3162 const exported: Zcu.Exported = .{ .uav = exported_uav };3167 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);
3164 }3169 }
3165}3170}
31663171
...@@ -3171,6 +3176,7 @@ fn processExportsInner(...@@ -3171,6 +3176,7 @@ fn processExportsInner(
3171 symbol_exports: *SymbolExports,3176 symbol_exports: *SymbolExports,
3172 exported: Zcu.Exported,3177 exported: Zcu.Exported,
3173 export_indices: []const Zcu.Export.Index,3178 export_indices: []const Zcu.Export.Index,
3179 skip_linker_work: bool,
3174) error{OutOfMemory}!void {3180) error{OutOfMemory}!void {
3175 const zcu = pt.zcu;3181 const zcu = pt.zcu;
3176 const gpa = zcu.gpa;3182 const gpa = zcu.gpa;
...@@ -3216,13 +3222,14 @@ fn processExportsInner(...@@ -3216,13 +3222,14 @@ fn processExportsInner(
3216 }3222 }
3217 break :failed false;3223 break :failed false;
3218 }) {3224 }) {
3219 // This `Decl` is failed, so was never sent to codegen.3225 // This `Nav` is failed, so was never sent to codegen. There should be a compile error.
3220 // TODO: we should probably tell the backend to delete any old exports of this `Decl`?3226 assert(skip_linker_work);
3221 return;
3222 },3227 },
3223 .uav => {},3228 .uav => {},
3224 }3229 }
32253230
3231 if (skip_linker_work) return;
3232
3226 if (zcu.llvm_object) |llvm_object| {3233 if (zcu.llvm_object) |llvm_object| {
3227 try zcu.handleUpdateExports(export_indices, llvm_object.updateExports(pt, exported, export_indices));3234 try zcu.handleUpdateExports(export_indices, llvm_object.updateExports(pt, exported, export_indices));
3228 } else if (zcu.comp.bin_file) |lf| {3235 } 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'