authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-04-20 02:01:46+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-04-20 18:11:53+01:00
log6561a98a61bb54c9d6c868f788da3eaa6f48d2c3
tree429a38f476c8c0ecc1bf211df83b5596f8824ba6
parentf01833e03eea786a05635cfbe142f581e9281b51
signaturelock-open Commit is signed but in an unrecognized format.

incremental: correctly handle dead exporters

Resolves: #23604

2 files changed, 190 insertions(+), 3 deletions(-)

src/Zcu/PerThread.zig+29-3
......@@ -2837,6 +2837,11 @@ pub fn processExports(pt: Zcu.PerThread) !void {
28372837 const zcu = pt.zcu;
28382838 const gpa = zcu.gpa;
28392839
2840 if (zcu.single_exports.count() == 0 and zcu.multi_exports.count() == 0) {
2841 // We can avoid a call to `resolveReferences` in this case.
2842 return;
2843 }
2844
28402845 // First, construct a mapping of every exported value and Nav to the indices of all its different exports.
28412846 var nav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;
28422847 var uav_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, std.ArrayListUnmanaged(Zcu.Export.Index)) = .empty;
......@@ -2857,8 +2862,18 @@ pub fn processExports(pt: Zcu.PerThread) !void {
28572862 // So, this ensureTotalCapacity serves as a reasonable (albeit very approximate) optimization.
28582863 try nav_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count());
28592864
2860 for (zcu.single_exports.values()) |export_idx| {
2865 const unit_references = try zcu.resolveReferences();
2866
2867 for (zcu.single_exports.keys(), zcu.single_exports.values()) |exporter, export_idx| {
28612868 const exp = export_idx.ptr(zcu);
2869 if (!unit_references.contains(exporter)) {
2870 // This export might already have been sent to the linker on a previous update, in which case we need to delete it.
2871 // The linker export API should be modified to eliminate this call. #23616
2872 if (zcu.comp.bin_file) |lf| {
2873 lf.deleteExport(exp.exported, exp.opts.name);
2874 }
2875 continue;
2876 }
28622877 const value_ptr, const found_existing = switch (exp.exported) {
28632878 .nav => |nav| gop: {
28642879 const gop = try nav_exports.getOrPut(gpa, nav);
......@@ -2873,8 +2888,19 @@ pub fn processExports(pt: Zcu.PerThread) !void {
28732888 try value_ptr.append(gpa, export_idx);
28742889 }
28752890
2876 for (zcu.multi_exports.values()) |info| {
2877 for (zcu.all_exports.items[info.index..][0..info.len], info.index..) |exp, export_idx| {
2891 for (zcu.multi_exports.keys(), zcu.multi_exports.values()) |exporter, info| {
2892 const exports = zcu.all_exports.items[info.index..][0..info.len];
2893 if (!unit_references.contains(exporter)) {
2894 // This export might already have been sent to the linker on a previous update, in which case we need to delete it.
2895 // The linker export API should be modified to eliminate this loop. #23616
2896 if (zcu.comp.bin_file) |lf| {
2897 for (exports) |exp| {
2898 lf.deleteExport(exp.exported, exp.opts.name);
2899 }
2900 }
2901 continue;
2902 }
2903 for (exports, info.index..) |exp, export_idx| {
28782904 const value_ptr, const found_existing = switch (exp.exported) {
28792905 .nav => |nav| gop: {
28802906 const gop = try nav_exports.getOrPut(gpa, nav);
test/incremental/change_exports created+161
......@@ -0,0 +1,161 @@
1#target=x86_64-linux-selfhosted
2#target=x86_64-linux-cbe
3#target=x86_64-windows-cbe
4
5#update=initial version
6#file=main.zig
7export fn foo() void {}
8const bar: u32 = 123;
9const other: u32 = 456;
10comptime {
11 @export(&bar, .{ .name = "bar" });
12}
13pub fn main() !void {
14 const S = struct {
15 extern fn foo() void;
16 extern const bar: u32;
17 };
18 S.foo();
19 try std.io.getStdOut().writer().print("{}\n", .{S.bar});
20}
21const std = @import("std");
22#expect_stdout="123\n"
23
24#update=add conflict
25#file=main.zig
26export fn foo() void {}
27const bar: u32 = 123;
28const other: u32 = 456;
29comptime {
30 @export(&bar, .{ .name = "bar" });
31 @export(&other, .{ .name = "foo" });
32}
33pub fn main() !void {
34 const S = struct {
35 extern fn foo() void;
36 extern const bar: u32;
37 extern const other: u32;
38 };
39 S.foo();
40 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
41}
42const std = @import("std");
43#expect_error=main.zig:6:5: error: exported symbol collision: foo
44#expect_error=main.zig:1:1: note: other symbol here
45
46#update=resolve conflict
47#file=main.zig
48export fn foo() void {}
49const bar: u32 = 123;
50const other: u32 = 456;
51comptime {
52 @export(&bar, .{ .name = "bar" });
53 @export(&other, .{ .name = "other" });
54}
55pub fn main() !void {
56 const S = struct {
57 extern fn foo() void;
58 extern const bar: u32;
59 extern const other: u32;
60 };
61 S.foo();
62 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
63}
64const std = @import("std");
65#expect_stdout="123 456\n"
66
67#update=put exports in decl
68#file=main.zig
69export fn foo() void {}
70const bar: u32 = 123;
71const other: u32 = 456;
72const does_exports = {
73 @export(&bar, .{ .name = "bar" });
74 @export(&other, .{ .name = "other" });
75};
76comptime {
77 _ = does_exports;
78}
79pub fn main() !void {
80 const S = struct {
81 extern fn foo() void;
82 extern const bar: u32;
83 extern const other: u32;
84 };
85 S.foo();
86 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
87}
88const std = @import("std");
89#expect_stdout="123 456\n"
90
91#update=remove reference to exporting decl
92#file=main.zig
93export fn foo() void {}
94const bar: u32 = 123;
95const other: u32 = 456;
96const does_exports = {
97 @export(&bar, .{ .name = "bar" });
98 @export(&other, .{ .name = "other" });
99};
100comptime {
101 //_ = does_exports;
102}
103pub fn main() !void {
104 const S = struct {
105 extern fn foo() void;
106 };
107 S.foo();
108}
109const std = @import("std");
110#expect_stdout=""
111
112#update=mark consts as export
113#file=main.zig
114export fn foo() void {}
115export const bar: u32 = 123;
116export const other: u32 = 456;
117const does_exports = {
118 @export(&bar, .{ .name = "bar" });
119 @export(&other, .{ .name = "other" });
120};
121comptime {
122 //_ = does_exports;
123}
124pub fn main() !void {
125 const S = struct {
126 extern fn foo() void;
127 extern const bar: u32;
128 extern const other: u32;
129 };
130 S.foo();
131 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
132}
133const std = @import("std");
134#expect_stdout="123 456\n"
135
136#update=reintroduce reference to exporting decl, introducing conflict
137#file=main.zig
138export fn foo() void {}
139export const bar: u32 = 123;
140export const other: u32 = 456;
141const does_exports = {
142 @export(&bar, .{ .name = "bar" });
143 @export(&other, .{ .name = "other" });
144};
145comptime {
146 _ = does_exports;
147}
148pub fn main() !void {
149 const S = struct {
150 extern fn foo() void;
151 extern const bar: u32;
152 extern const other: u32;
153 };
154 S.foo();
155 try std.io.getStdOut().writer().print("{} {}\n", .{ S.bar, S.other });
156}
157const std = @import("std");
158#expect_error=main.zig:5:5: error: exported symbol collision: bar
159#expect_error=main.zig:2:1: note: other symbol here
160#expect_error=main.zig:6:5: error: exported symbol collision: other
161#expect_error=main.zig:3:1: note: other symbol here