1/// Helper type for debug information implementations (such as `link.Dwarf`) to help them emit
2/// information about comptime-known values (constants), including types.
3///
4/// Every constant with associated debug information is assigned an `Index` by calling `get`. The
5/// pool will track which container types do and do not have a resolved layout, as well as which
6/// constants in the pool depend on which types, and call into the implementation to emit debug
7/// information for a constant only when all information is available.
8///
9/// Indices into the pool are dense, and constants are never removed from the pool, so the debug
10/// info implementation can store information for each one with a simple `ArrayList`.
11///
12/// To use `ConstPool`, the debug info implementation is required to:
13/// * forward `updateContainerType` calls to its `ConstPool`
14/// * expose some callback functions---see functions in `User`
15/// * ensure that any `get` call is eventually followed by a `flushPending` call
16const ConstPool = @This();
17
18values: std.array_hash_map.Auto(InternPool.Index, void),
19pending: std.ArrayList(Index),
20complete_containers: std.array_hash_map.Auto(InternPool.Index, void),
21container_deps: std.array_hash_map.Auto(InternPool.Index, ContainerDepEntry.Index),
22container_dep_entries: std.ArrayList(ContainerDepEntry),
23
24pub const empty: ConstPool = .{
25 .values = .empty,
26 .pending = .empty,
27 .complete_containers = .empty,
28 .container_deps = .empty,
29 .container_dep_entries = .empty,
30};
31
32pub fn deinit(pool: *ConstPool, gpa: Allocator) void {
33 pool.values.deinit(gpa);
34 pool.pending.deinit(gpa);
35 pool.complete_containers.deinit(gpa);
36 pool.container_deps.deinit(gpa);
37 pool.container_dep_entries.deinit(gpa);
38}
39
40pub const Index = enum(u32) {
41 _,
42 pub fn val(i: Index, pool: *const ConstPool) InternPool.Index {
43 return pool.values.keys()[@backingInt(i)];
44 }
45};
46
47pub const User = union(enum) {
48 elf: *@import("Dwarf.zig"),
49 elf2: *@import("Elf2.zig"),
50 macho: *@import("Dwarf.zig"),
51 c: *@import("C.zig"),
52 llvm: @import("../codegen/llvm.zig").Object.Ptr,
53
54 fn devFeature(tag: @typeInfo(User).@"union".tag_type.?) dev.Feature {
55 return switch (tag) {
56 .elf => .elf_linker,
57 .elf2 => .elf2_linker,
58 .macho => .macho_linker,
59 .c => .c_linker,
60 .llvm => .llvm_backend,
61 };
62 }
63
64 /// Inform the debug info implementation that the new constant `val` was added to the pool at
65 /// the given index (which equals the current pool length) due to a `get` call. It is guaranteed
66 /// that there will eventually be a call to either `updateConst` or `updateConstIncomplete`
67 /// following the `addConst` call, to actually populate the constant's debug info.
68 fn addConst(
69 user: User,
70 pt: Zcu.PerThread,
71 index: Index,
72 val: InternPool.Index,
73 ) link.Error!void {
74 switch (user) {
75 inline else => |impl, tag| {
76 dev.check(devFeature(tag));
77 return impl.addConst(pt, index, val);
78 },
79 }
80 }
81
82 /// Tell the debug info implementation to emit information for the constant `val`, which is in
83 /// the pool at the given index. `val` is "complete", which means:
84 /// * If it is a type, its layout is known.
85 /// * Otherwise, the layout of its type is known.
86 fn updateConst(
87 user: User,
88 pt: Zcu.PerThread,
89 index: Index,
90 val: InternPool.Index,
91 ) link.Error!void {
92 switch (user) {
93 inline else => |impl, tag| {
94 dev.check(devFeature(tag));
95 return impl.updateConst(pt, index, val);
96 },
97 }
98 }
99
100 /// Tell the debug info implementation to emit information for the constant `val`, which is in
101 /// the pool at the given index. `val` is "incomplete", meaning the implementation cannot emit
102 /// full information for it (for instance, perhaps it is a struct type which was never actually
103 /// initialized so never had its layout resolved). Instead, the implementation must emit some
104 /// form of placeholder entry representing an incomplete/unknown constant.
105 fn updateConstIncomplete(
106 user: User,
107 pt: Zcu.PerThread,
108 index: Index,
109 val: InternPool.Index,
110 ) link.Error!void {
111 switch (user) {
112 inline else => |impl, tag| {
113 dev.check(devFeature(tag));
114 return impl.updateConstIncomplete(pt, index, val);
115 },
116 }
117 }
118};
119
120const ContainerDepEntry = extern struct {
121 next: ContainerDepEntry.Index.Optional,
122 depender: ConstPool.Index,
123 const Index = enum(u32) {
124 _,
125 const Optional = enum(u32) {
126 none = std.math.maxInt(u32),
127 _,
128 fn unwrap(o: Optional) ?ContainerDepEntry.Index {
129 return switch (o) {
130 .none => null,
131 else => @fromBackingInt(@intCast(@backingInt(o))),
132 };
133 }
134 };
135 fn toOptional(i: ContainerDepEntry.Index) Optional {
136 return @fromBackingInt(@intCast(@backingInt(i)));
137 }
138 fn ptr(i: ContainerDepEntry.Index, pool: *ConstPool) *ContainerDepEntry {
139 return &pool.container_dep_entries.items[@backingInt(i)];
140 }
141 };
142};
143
144/// Calls to `link.File.updateContainerType` must be forwarded to this function so that the debug
145/// constant pool has up-to-date information about the resolution status of types.
146pub fn updateContainerType(
147 pool: *ConstPool,
148 pt: Zcu.PerThread,
149 user: User,
150 container_ty: InternPool.Index,
151 success: bool,
152) link.Error!void {
153 if (success) {
154 const gpa = pt.zcu.comp.gpa;
155 try pool.complete_containers.put(gpa, container_ty, {});
156 } else {
157 _ = pool.complete_containers.swapRemove(container_ty);
158 }
159 var opt_dep = pool.container_deps.get(container_ty);
160 while (opt_dep) |dep| : (opt_dep = dep.ptr(pool).next.unwrap()) {
161 try pool.update(pt, user, dep.ptr(pool).depender);
162 }
163}
164
165/// After this is called, there may be a constant for which debug information (complete or not) has
166/// not yet been emitted, so the user must call `flushPending` at some point after this call.
167pub fn get(pool: *ConstPool, pt: Zcu.PerThread, user: User, val: InternPool.Index) link.Error!ConstPool.Index {
168 const zcu = pt.zcu;
169 const ip = &zcu.intern_pool;
170 const gpa = zcu.comp.gpa;
171 const gop = try pool.values.getOrPut(gpa, val);
172 const index: ConstPool.Index = @fromBackingInt(@intCast(gop.index));
173 if (!gop.found_existing) {
174 const ty: Type = switch (ip.typeOf(val)) {
175 .type_type => if (ip.isUndef(val)) .type else .fromInterned(val),
176 else => |ty| .fromInterned(ty),
177 };
178 try pool.registerTypeDeps(index, ty, zcu);
179 try pool.pending.append(gpa, index);
180 try user.addConst(pt, index, val);
181 }
182 return index;
183}
184pub fn getIfExists(pool: *ConstPool, val: InternPool.Index) ?ConstPool.Index {
185 return @fromBackingInt(@intCast(pool.values.getIndex(val) orelse return null));
186}
187pub fn flushPending(pool: *ConstPool, pt: Zcu.PerThread, user: User) link.Error!void {
188 while (pool.pending.pop()) |pending_ty| {
189 try pool.update(pt, user, pending_ty);
190 }
191}
192
193fn update(pool: *ConstPool, pt: Zcu.PerThread, user: User, index: ConstPool.Index) link.Error!void {
194 const zcu = pt.zcu;
195 const ip = &zcu.intern_pool;
196 const val = index.val(pool);
197 const ty: Type = switch (ip.typeOf(val)) {
198 .type_type => if (ip.isUndef(val)) .type else .fromInterned(val),
199 else => |ty| .fromInterned(ty),
200 };
201 if (pool.checkType(ty, zcu)) {
202 try user.updateConst(pt, index, val);
203 } else {
204 try user.updateConstIncomplete(pt, index, val);
205 }
206}
207fn checkType(pool: *const ConstPool, ty: Type, zcu: *const Zcu) bool {
208 if (ty.isGenericPoison()) return true;
209 return switch (ty.zigTypeTag(zcu)) {
210 .type,
211 .void,
212 .bool,
213 .noreturn,
214 .int,
215 .float,
216 .pointer,
217 .comptime_float,
218 .comptime_int,
219 .undefined,
220 .null,
221 .error_set,
222 .@"opaque",
223 .spirv,
224 .frame,
225 .@"anyframe",
226 .enum_literal,
227 => true,
228
229 .array, .vector => pool.checkType(ty.childType(zcu), zcu),
230 .optional => pool.checkType(ty.optionalChild(zcu), zcu),
231 .error_union => pool.checkType(ty.errorUnionPayload(zcu), zcu),
232 .@"fn" => {
233 const ip = &zcu.intern_pool;
234 const func = ip.indexToKey(ty.toIntern()).func_type;
235 for (func.param_types.get(ip)) |param_ty_ip| {
236 if (!pool.checkType(.fromInterned(param_ty_ip), zcu)) return false;
237 }
238 return pool.checkType(.fromInterned(func.return_type), zcu);
239 },
240 .@"struct" => if (ty.isTuple(zcu)) {
241 for (0..ty.structFieldCount(zcu)) |field_index| {
242 if (!pool.checkType(ty.fieldType(field_index, zcu), zcu)) return false;
243 }
244 return true;
245 } else {
246 return pool.complete_containers.contains(ty.toIntern());
247 },
248 .@"union", .@"enum" => {
249 return pool.complete_containers.contains(ty.toIntern());
250 },
251 };
252}
253fn registerTypeDeps(pool: *ConstPool, root: Index, ty: Type, zcu: *const Zcu) Allocator.Error!void {
254 if (ty.isGenericPoison()) return;
255 switch (ty.zigTypeTag(zcu)) {
256 .type,
257 .void,
258 .bool,
259 .noreturn,
260 .int,
261 .float,
262 .pointer,
263 .comptime_float,
264 .comptime_int,
265 .undefined,
266 .null,
267 .error_set,
268 .@"opaque",
269 .spirv,
270 .frame,
271 .@"anyframe",
272 .enum_literal,
273 => {},
274
275 .array, .vector => try pool.registerTypeDeps(root, ty.childType(zcu), zcu),
276 .optional => try pool.registerTypeDeps(root, ty.optionalChild(zcu), zcu),
277 .error_union => try pool.registerTypeDeps(root, ty.errorUnionPayload(zcu), zcu),
278 .@"fn" => {
279 const ip = &zcu.intern_pool;
280 const func = ip.indexToKey(ty.toIntern()).func_type;
281 for (func.param_types.get(ip)) |param_ty_ip| {
282 try pool.registerTypeDeps(root, .fromInterned(param_ty_ip), zcu);
283 }
284 try pool.registerTypeDeps(root, .fromInterned(func.return_type), zcu);
285 },
286 .@"struct", .@"union", .@"enum" => if (ty.isTuple(zcu)) {
287 for (0..ty.structFieldCount(zcu)) |field_index| {
288 try pool.registerTypeDeps(root, ty.fieldType(field_index, zcu), zcu);
289 }
290 } else {
291 // `ty` is a container; register the dependency.
292
293 const gpa = zcu.comp.gpa;
294 try pool.container_deps.ensureUnusedCapacity(gpa, 1);
295 try pool.container_dep_entries.ensureUnusedCapacity(gpa, 1);
296 errdefer comptime unreachable;
297
298 const gop = pool.container_deps.getOrPutAssumeCapacity(ty.toIntern());
299 const entry: ContainerDepEntry.Index = @fromBackingInt(@intCast(pool.container_dep_entries.items.len));
300 pool.container_dep_entries.appendAssumeCapacity(.{
301 .next = if (gop.found_existing) gop.value_ptr.toOptional() else .none,
302 .depender = root,
303 });
304 gop.value_ptr.* = entry;
305 },
306 }
307}
308
309const std = @import("std");
310const Allocator = std.mem.Allocator;
311
312const dev = @import("../dev.zig");
313const InternPool = @import("../InternPool.zig");
314const link = @import("../link.zig");
315const Type = @import("../Type.zig");
316const Zcu = @import("../Zcu.zig");