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 dwarf: *@import("Dwarf.zig"),
49 c: *@import("C.zig"),
50 llvm: @import("../codegen/llvm.zig").Object.Ptr,
51
52 /// Inform the debug info implementation that the new constant `val` was added to the pool at
53 /// the given index (which equals the current pool length) due to a `get` call. It is guaranteed
54 /// that there will eventually be a call to either `updateConst` or `updateConstIncomplete`
55 /// following the `addConst` call, to actually populate the constant's debug info.
56 fn addConst(
57 user: User,
58 pt: Zcu.PerThread,
59 index: Index,
60 val: InternPool.Index,
61 ) Allocator.Error!void {
62 switch (user) {
63 inline else => |impl| return impl.addConst(pt, index, val),
64 }
65 }
66
67 /// Tell the debug info implementation to emit information for the constant `val`, which is in
68 /// the pool at the given index. `val` is "complete", which means:
69 /// * If it is a type, its layout is known.
70 /// * Otherwise, the layout of its type is known.
71 fn updateConst(
72 user: User,
73 pt: Zcu.PerThread,
74 index: Index,
75 val: InternPool.Index,
76 ) Allocator.Error!void {
77 switch (user) {
78 inline else => |impl| return impl.updateConst(pt, index, val),
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 "incomplete", meaning the implementation cannot emit
84 /// full information for it (for instance, perhaps it is a struct type which was never actually
85 /// initialized so never had its layout resolved). Instead, the implementation must emit some
86 /// form of placeholder entry representing an incomplete/unknown constant.
87 fn updateConstIncomplete(
88 user: User,
89 pt: Zcu.PerThread,
90 index: Index,
91 val: InternPool.Index,
92 ) Allocator.Error!void {
93 switch (user) {
94 inline else => |impl| return impl.updateConstIncomplete(pt, index, val),
95 }
96 }
97};
98
99const ContainerDepEntry = extern struct {
100 next: ContainerDepEntry.Index.Optional,
101 depender: ConstPool.Index,
102 const Index = enum(u32) {
103 _,
104 const Optional = enum(u32) {
105 none = std.math.maxInt(u32),
106 _,
107 fn unwrap(o: Optional) ?ContainerDepEntry.Index {
108 return switch (o) {
109 .none => null,
110 else => @fromBackingInt(@intCast(@backingInt(o))),
111 };
112 }
113 };
114 fn toOptional(i: ContainerDepEntry.Index) Optional {
115 return @fromBackingInt(@intCast(@backingInt(i)));
116 }
117 fn ptr(i: ContainerDepEntry.Index, pool: *ConstPool) *ContainerDepEntry {
118 return &pool.container_dep_entries.items[@backingInt(i)];
119 }
120 };
121};
122
123/// Calls to `link.File.updateContainerType` must be forwarded to this function so that the debug
124/// constant pool has up-to-date information about the resolution status of types.
125pub fn updateContainerType(
126 pool: *ConstPool,
127 pt: Zcu.PerThread,
128 user: User,
129 container_ty: InternPool.Index,
130 success: bool,
131) Allocator.Error!void {
132 if (success) {
133 const gpa = pt.zcu.comp.gpa;
134 try pool.complete_containers.put(gpa, container_ty, {});
135 } else {
136 _ = pool.complete_containers.fetchSwapRemove(container_ty);
137 }
138 var opt_dep = pool.container_deps.get(container_ty);
139 while (opt_dep) |dep| : (opt_dep = dep.ptr(pool).next.unwrap()) {
140 try pool.update(pt, user, dep.ptr(pool).depender);
141 }
142}
143
144/// After this is called, there may be a constant for which debug information (complete or not) has
145/// not yet been emitted, so the user must call `flushPending` at some point after this call.
146pub fn get(pool: *ConstPool, pt: Zcu.PerThread, user: User, val: InternPool.Index) Allocator.Error!ConstPool.Index {
147 const zcu = pt.zcu;
148 const ip = &zcu.intern_pool;
149 const gpa = zcu.comp.gpa;
150 const gop = try pool.values.getOrPut(gpa, val);
151 const index: ConstPool.Index = @fromBackingInt(@intCast(gop.index));
152 if (!gop.found_existing) {
153 const ty: Type = switch (ip.typeOf(val)) {
154 .type_type => if (ip.isUndef(val)) .type else .fromInterned(val),
155 else => |ty| .fromInterned(ty),
156 };
157 try pool.registerTypeDeps(index, ty, zcu);
158 try pool.pending.append(gpa, index);
159 try user.addConst(pt, index, val);
160 }
161 return index;
162}
163pub fn flushPending(pool: *ConstPool, pt: Zcu.PerThread, user: User) Allocator.Error!void {
164 while (pool.pending.pop()) |pending_ty| {
165 try pool.update(pt, user, pending_ty);
166 }
167}
168
169fn update(pool: *ConstPool, pt: Zcu.PerThread, user: User, index: ConstPool.Index) Allocator.Error!void {
170 const zcu = pt.zcu;
171 const ip = &zcu.intern_pool;
172 const val = index.val(pool);
173 const ty: Type = switch (ip.typeOf(val)) {
174 .type_type => if (ip.isUndef(val)) .type else .fromInterned(val),
175 else => |ty| .fromInterned(ty),
176 };
177 if (pool.checkType(ty, zcu)) {
178 try user.updateConst(pt, index, val);
179 } else {
180 try user.updateConstIncomplete(pt, index, val);
181 }
182}
183fn checkType(pool: *const ConstPool, ty: Type, zcu: *const Zcu) bool {
184 if (ty.isGenericPoison()) return true;
185 return switch (ty.zigTypeTag(zcu)) {
186 .type,
187 .void,
188 .bool,
189 .noreturn,
190 .int,
191 .float,
192 .pointer,
193 .comptime_float,
194 .comptime_int,
195 .undefined,
196 .null,
197 .error_set,
198 .@"opaque",
199 .spirv,
200 .frame,
201 .@"anyframe",
202 .enum_literal,
203 => true,
204
205 .array, .vector => pool.checkType(ty.childType(zcu), zcu),
206 .optional => pool.checkType(ty.optionalChild(zcu), zcu),
207 .error_union => pool.checkType(ty.errorUnionPayload(zcu), zcu),
208 .@"fn" => {
209 const ip = &zcu.intern_pool;
210 const func = ip.indexToKey(ty.toIntern()).func_type;
211 for (func.param_types.get(ip)) |param_ty_ip| {
212 if (!pool.checkType(.fromInterned(param_ty_ip), zcu)) return false;
213 }
214 return pool.checkType(.fromInterned(func.return_type), zcu);
215 },
216 .@"struct" => if (ty.isTuple(zcu)) {
217 for (0..ty.structFieldCount(zcu)) |field_index| {
218 if (!pool.checkType(ty.fieldType(field_index, zcu), zcu)) return false;
219 }
220 return true;
221 } else {
222 return pool.complete_containers.contains(ty.toIntern());
223 },
224 .@"union", .@"enum" => {
225 return pool.complete_containers.contains(ty.toIntern());
226 },
227 };
228}
229fn registerTypeDeps(pool: *ConstPool, root: Index, ty: Type, zcu: *const Zcu) Allocator.Error!void {
230 if (ty.isGenericPoison()) return;
231 switch (ty.zigTypeTag(zcu)) {
232 .type,
233 .void,
234 .bool,
235 .noreturn,
236 .int,
237 .float,
238 .pointer,
239 .comptime_float,
240 .comptime_int,
241 .undefined,
242 .null,
243 .error_set,
244 .@"opaque",
245 .spirv,
246 .frame,
247 .@"anyframe",
248 .enum_literal,
249 => {},
250
251 .array, .vector => try pool.registerTypeDeps(root, ty.childType(zcu), zcu),
252 .optional => try pool.registerTypeDeps(root, ty.optionalChild(zcu), zcu),
253 .error_union => try pool.registerTypeDeps(root, ty.errorUnionPayload(zcu), zcu),
254 .@"fn" => {
255 const ip = &zcu.intern_pool;
256 const func = ip.indexToKey(ty.toIntern()).func_type;
257 for (func.param_types.get(ip)) |param_ty_ip| {
258 try pool.registerTypeDeps(root, .fromInterned(param_ty_ip), zcu);
259 }
260 try pool.registerTypeDeps(root, .fromInterned(func.return_type), zcu);
261 },
262 .@"struct", .@"union", .@"enum" => if (ty.isTuple(zcu)) {
263 for (0..ty.structFieldCount(zcu)) |field_index| {
264 try pool.registerTypeDeps(root, ty.fieldType(field_index, zcu), zcu);
265 }
266 } else {
267 // `ty` is a container; register the dependency.
268
269 const gpa = zcu.comp.gpa;
270 try pool.container_deps.ensureUnusedCapacity(gpa, 1);
271 try pool.container_dep_entries.ensureUnusedCapacity(gpa, 1);
272 errdefer comptime unreachable;
273
274 const gop = pool.container_deps.getOrPutAssumeCapacity(ty.toIntern());
275 const entry: ContainerDepEntry.Index = @fromBackingInt(@intCast(pool.container_dep_entries.items.len));
276 pool.container_dep_entries.appendAssumeCapacity(.{
277 .next = if (gop.found_existing) gop.value_ptr.toOptional() else .none,
278 .depender = root,
279 });
280 gop.value_ptr.* = entry;
281 },
282 }
283}
284
285const std = @import("std");
286const Allocator = std.mem.Allocator;
287
288const InternPool = @import("../InternPool.zig");
289const Type = @import("../Type.zig");
290const Zcu = @import("../Zcu.zig");