| 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 |
| 16 | const ConstPool = @This(); |
| 17 | |
| 18 | values: std.array_hash_map.Auto(InternPool.Index, void), |
| 19 | pending: std.ArrayList(Index), |
| 20 | complete_containers: std.array_hash_map.Auto(InternPool.Index, void), |
| 21 | container_deps: std.array_hash_map.Auto(InternPool.Index, ContainerDepEntry.Index), |
| 22 | container_dep_entries: std.ArrayList(ContainerDepEntry), |
| 23 | |
| 24 | pub const empty: ConstPool = .{ |
| 25 | .values = .empty, |
| 26 | .pending = .empty, |
| 27 | .complete_containers = .empty, |
| 28 | .container_deps = .empty, |
| 29 | .container_dep_entries = .empty, |
| 30 | }; |
| 31 | |
| 32 | pub 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 | |
| 40 | pub 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 | |
| 47 | pub 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 | |
| 120 | const 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. |
| 146 | pub 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. |
| 167 | pub 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 | } |
| 184 | pub fn getIfExists(pool: *ConstPool, val: InternPool.Index) ?ConstPool.Index { |
| 185 | return @fromBackingInt(@intCast(pool.values.getIndex(val) orelse return null)); |
| 186 | } |
| 187 | pub 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 | |
| 193 | fn 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 | } |
| 207 | fn 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 | } |
| 253 | fn 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 | |
| 309 | const std = @import("std"); |
| 310 | const Allocator = std.mem.Allocator; |
| 311 | |
| 312 | const dev = @import("../dev.zig"); |
| 313 | const InternPool = @import("../InternPool.zig"); |
| 314 | const link = @import("../link.zig"); |
| 315 | const Type = @import("../Type.zig"); |
| 316 | const Zcu = @import("../Zcu.zig"); |