lib/choir/src/core/interfaces/attrs.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const base = @import("base.zig");
3 const IrAttribute = @import("../root.zig").Attribute;
4 const IrOperation = @import("../root.zig").Operation;
5 const IrSymbolTable = @import("../root.zig").SymbolTable;
6
7 const InterfaceId = base.InterfaceId;
8 const InterfaceEntry = base.InterfaceEntry;
9
10 pub const AbstractAttribute = struct {
11 attr_id: u32,
12
13 name: []const u8,
14
15 interfaces: []const InterfaceEntry,
16
17 pub fn getInterface(self: *const AbstractAttribute, id: InterfaceId) ?*const anyopaque {
18 for (self.interfaces) |entry| {
19 if (entry.id == id) return entry.vtable;
20 }
21 return null;
22 }
23
24 pub fn hasInterface(self: *const AbstractAttribute, id: InterfaceId) bool {
25 return self.getInterface(id) != null;
26 }
27 };
28
29 pub const AttributeEqualInterface = struct {
30 pub const interface_name = "ir.interface.attribute_equal";
31 pub const id: InterfaceId = base.interfaceId(interface_name);
32
33 pub const VTable = struct {
34 eql: *const fn (self_impl: *const anyopaque, other_impl: *const anyopaque) bool,
35 };
36
37 pub fn entry(vtable: *const VTable) InterfaceEntry {
38 return .{ .id = id, .vtable = vtable };
39 }
40
41 pub fn vtableFor(comptime eql: *const fn (self_impl: *const anyopaque, other_impl: *const anyopaque) bool) *const VTable {
42 return &.{ .eql = eql };
43 }
44
45 pub fn entryFor(comptime eql: *const fn (self_impl: *const anyopaque, other_impl: *const anyopaque) bool) InterfaceEntry {
46 return entry(vtableFor(eql));
47 }
48 };
49
50 pub const AttributePrintInterface = struct {
51 pub const interface_name = "ir.interface.attribute_print";
52 pub const id: InterfaceId = base.interfaceId(interface_name);
53
54 pub const VTable = struct {
55 print: *const fn (attr_ptr: *const anyopaque, writer: *std.Io.Writer) std.Io.Writer.Error!void,
56 };
57
58 pub fn entry(vtable: *const VTable) InterfaceEntry {
59 return .{ .id = id, .vtable = vtable };
60 }
61
62 pub fn vtableFor(comptime print: *const fn (attr_ptr: *const anyopaque, writer: *std.Io.Writer) std.Io.Writer.Error!void) *const VTable {
63 return &.{ .print = print };
64 }
65
66 pub fn entryFor(comptime print: *const fn (attr_ptr: *const anyopaque, writer: *std.Io.Writer) std.Io.Writer.Error!void) InterfaceEntry {
67 return entry(vtableFor(print));
68 }
69 };
70
71 pub const AttributeArrayInterface = struct {
72 pub const interface_name = "ir.interface.attribute_array";
73 pub const id: InterfaceId = base.interfaceId(interface_name);
74
75 pub const VTable = struct {
76 getCount: *const fn (attr_ptr: *const anyopaque) usize,
77 getElement: *const fn (attr_ptr: *const anyopaque, index: usize) ?IrAttribute,
78 };
79
80 pub fn entry(vtable: *const VTable) InterfaceEntry {
81 return .{ .id = id, .vtable = vtable };
82 }
83
84 pub fn vtableFor(
85 comptime getCount: *const fn (attr_ptr: *const anyopaque) usize,
86 comptime getElement: *const fn (attr_ptr: *const anyopaque, index: usize) ?IrAttribute,
87 ) *const VTable {
88 return &.{
89 .getCount = getCount,
90 .getElement = getElement,
91 };
92 }
93
94 pub fn entryFor(
95 comptime getCount: *const fn (attr_ptr: *const anyopaque) usize,
96 comptime getElement: *const fn (attr_ptr: *const anyopaque, index: usize) ?IrAttribute,
97 ) InterfaceEntry {
98 return entry(vtableFor(getCount, getElement));
99 }
100 };
101
102 pub const SymbolUserAttrInterface = struct {
103 pub const interface_name = "ir.interface.symbol_user_attr";
104 pub const id: InterfaceId = base.interfaceId(interface_name);
105
106 pub const VTable = struct {
107 verifySymbolUses: *const fn (
108 attr_ptr: *const anyopaque,
109 op: *IrOperation,
110 symbol_tables: *IrSymbolTable.Collection,
111 ) anyerror!void,
112 };
113
114 pub fn entry(vtable: *const VTable) InterfaceEntry {
115 return .{ .id = id, .vtable = vtable };
116 }
117
118 pub fn vtableFor(
119 comptime verifySymbolUses: *const fn (
120 attr_ptr: *const anyopaque,
121 op: *IrOperation,
122 symbol_tables: *IrSymbolTable.Collection,
123 ) anyerror!void,
124 ) *const VTable {
125 return &.{ .verifySymbolUses = verifySymbolUses };
126 }
127
128 pub fn entryFor(
129 comptime verifySymbolUses: *const fn (
130 attr_ptr: *const anyopaque,
131 op: *IrOperation,
132 symbol_tables: *IrSymbolTable.Collection,
133 ) anyerror!void,
134 ) InterfaceEntry {
135 return entry(vtableFor(verifySymbolUses));
136 }
137 };
138
139 pub const AttributeRegistry = struct {
140 allocator: std.mem.Allocator,
141 attrs: std.StringHashMapUnmanaged(AttributeRegistryEntry),
142 next_attr_id: u32,
143
144 pub const AttributeRegistryEntry = struct {
145 abstract: *const AbstractAttribute,
146 owns_interfaces: bool,
147 };
148
149 pub const RegisterError = error{DuplicateAttribute} || std.mem.Allocator.Error;
150
151 pub const RegisterInterfaceError = error{ UnknownAttribute, DuplicateInterface } || std.mem.Allocator.Error;
152
153 pub const RegisterOrReplaceInterfaceError = error{UnknownAttribute} || std.mem.Allocator.Error;
154
155 fn interfaceLessThan(_: void, a: InterfaceEntry, b: InterfaceEntry) bool {
156 return a.id < b.id;
157 }
158
159 pub fn init(allocator: std.mem.Allocator, first_dynamic_id: u32) AttributeRegistry {
160 return .{
161 .allocator = allocator,
162 .attrs = .{},
163 .next_attr_id = first_dynamic_id,
164 };
165 }
166
167 pub fn deinit(self: *AttributeRegistry) void {
168 var it = self.attrs.valueIterator();
169 while (it.next()) |entry| {
170 const abstract = entry.abstract;
171 if (entry.owns_interfaces and abstract.interfaces.len > 0) {
172 self.allocator.free(@constCast(abstract.interfaces));
173 }
174 self.allocator.destroy(@constCast(abstract));
175 }
176
177 var key_iter = self.attrs.keyIterator();
178 while (key_iter.next()) |name_ptr| {
179 self.allocator.free(name_ptr.*);
180 }
181 self.attrs.deinit(self.allocator);
182 }
183
184 pub fn lookup(self: *const AttributeRegistry, attr_name: []const u8) ?*const AbstractAttribute {
185 if (self.attrs.get(attr_name)) |entry| {
186 return entry.abstract;
187 }
188 return null;
189 }
190
191 pub fn nextAttributeId(self: *AttributeRegistry) u32 {
192 return self.next_attr_id;
193 }
194
195 pub fn restoreNextAttributeId(self: *AttributeRegistry, id: u32) void {
196 self.next_attr_id = id;
197 }
198
199 pub fn registerAttribute(
200 self: *AttributeRegistry,
201 attr_name: []const u8,
202 interfaces: []const InterfaceEntry,
203 ) RegisterError!*const AbstractAttribute {
204 if (self.attrs.get(attr_name) != null) return error.DuplicateAttribute;
205
206 const owned_name = try self.allocator.dupe(u8, attr_name);
207 errdefer self.allocator.free(owned_name);
208
209 var iface_slice: []InterfaceEntry = &.{};
210 var owns_interfaces = false;
211 if (interfaces.len > 0) {
212 iface_slice = try self.allocator.dupe(InterfaceEntry, interfaces);
213 std.mem.sort(InterfaceEntry, iface_slice, {}, interfaceLessThan);
214 owns_interfaces = true;
215 }
216 errdefer if (owns_interfaces and iface_slice.len > 0) self.allocator.free(iface_slice);
217
218 const abstract = try self.allocator.create(AbstractAttribute);
219 errdefer self.allocator.destroy(abstract);
220
221 abstract.* = .{
222 .attr_id = self.next_attr_id,
223 .name = owned_name,
224 .interfaces = iface_slice,
225 };
226
227 const gop = try self.attrs.getOrPut(self.allocator, owned_name);
228 if (gop.found_existing) return error.DuplicateAttribute;
229
230 gop.key_ptr.* = owned_name;
231 gop.value_ptr.* = .{
232 .abstract = abstract,
233 .owns_interfaces = owns_interfaces,
234 };
235 self.next_attr_id += 1;
236
237 return abstract;
238 }
239
240 pub fn registerInterface(
241 self: *AttributeRegistry,
242 attr_name: []const u8,
243 entry: InterfaceEntry,
244 ) RegisterInterfaceError!void {
245 const reg_entry = self.attrs.getPtr(attr_name) orelse return error.UnknownAttribute;
246 const abstract: *AbstractAttribute = @constCast(reg_entry.abstract);
247
248 for (abstract.interfaces) |existing| {
249 if (existing.id == entry.id) return error.DuplicateInterface;
250 }
251
252 const old_slice = abstract.interfaces;
253 const new_slice = try self.allocator.alloc(InterfaceEntry, old_slice.len + 1);
254 if (old_slice.len > 0) {
255 @memcpy(new_slice[0..old_slice.len], old_slice);
256 }
257 new_slice[old_slice.len] = entry;
258 std.mem.sort(InterfaceEntry, new_slice, {}, interfaceLessThan);
259
260 if (reg_entry.owns_interfaces and old_slice.len > 0) {
261 self.allocator.free(@constCast(old_slice));
262 }
263
264 reg_entry.owns_interfaces = true;
265 abstract.interfaces = new_slice;
266 }
267
268 pub fn registerOrReplaceInterface(
269 self: *AttributeRegistry,
270 attr_name: []const u8,
271 entry: InterfaceEntry,
272 ) RegisterOrReplaceInterfaceError!void {
273 const reg_entry = self.attrs.getPtr(attr_name) orelse return error.UnknownAttribute;
274 const abstract: *AbstractAttribute = @constCast(reg_entry.abstract);
275
276 const old_slice = abstract.interfaces;
277 var replace_index: ?usize = null;
278 for (old_slice, 0..) |existing, i| {
279 if (existing.id == entry.id) {
280 replace_index = i;
281 break;
282 }
283 }
284
285 const new_len = if (replace_index == null) old_slice.len + 1 else old_slice.len;
286 const new_slice = try self.allocator.alloc(InterfaceEntry, new_len);
287 if (old_slice.len > 0) {
288 @memcpy(new_slice[0..old_slice.len], old_slice);
289 }
290
291 if (replace_index) |idx| {
292 new_slice[idx] = entry;
293 } else {
294 new_slice[old_slice.len] = entry;
295 }
296 std.mem.sort(InterfaceEntry, new_slice, {}, interfaceLessThan);
297
298 if (reg_entry.owns_interfaces and old_slice.len > 0) {
299 self.allocator.free(@constCast(old_slice));
300 }
301
302 reg_entry.owns_interfaces = true;
303 abstract.interfaces = new_slice;
304 }
305
306 pub fn count(self: *const AttributeRegistry) usize {
307 return self.attrs.count();
308 }
309
310 pub fn removeAttribute(self: *AttributeRegistry, attr_name: []const u8) bool {
311 const removed = self.attrs.fetchRemove(attr_name) orelse return false;
312
313 const abstract = removed.value.abstract;
314 if (removed.value.owns_interfaces and abstract.interfaces.len > 0) {
315 self.allocator.free(@constCast(abstract.interfaces));
316 }
317 self.allocator.destroy(@constCast(abstract));
318 self.allocator.free(removed.key);
319 return true;
320 }
321 };
322
323 test "SymbolUserAttrInterface stable ID" {
324 const testing = std.testing;
325
326 const id1 = SymbolUserAttrInterface.id;
327 const id2 = base.interfaceId(SymbolUserAttrInterface.interface_name);
328
329 try testing.expectEqual(id1, id2);
330 try testing.expect(SymbolUserAttrInterface.id != AttributeEqualInterface.id);
331 try testing.expect(SymbolUserAttrInterface.id != AttributeArrayInterface.id);
332 }
333
334 fn testAttributeEqual(_: *const anyopaque, _: *const anyopaque) bool {
335 return true;
336 }
337
338 fn checkRegisterAttributeAllocationFailures(allocator: std.mem.Allocator) !void {
339 var registry = AttributeRegistry.init(allocator, 1);
340 defer registry.deinit();
341 _ = try registry.registerAttribute(
342 "allocation.test",
343 &.{AttributeEqualInterface.entryFor(testAttributeEqual)},
344 );
345 }
346
347 test "attribute registration cleans every allocation failure" {
348 try std.testing.checkAllAllocationFailures(
349 std.testing.allocator,
350 checkRegisterAttributeAllocationFailures,
351 .{},
352 );
353 }
354
355 test "attribute registration preserves its next id after allocation failure" {
356 var failing = std.testing.FailingAllocator.init(
357 std.testing.allocator,
358 .{ .fail_index = 3 },
359 );
360 var registry = AttributeRegistry.init(failing.allocator(), 7);
361 defer registry.deinit();
362 const interfaces = &.{AttributeEqualInterface.entryFor(testAttributeEqual)};
363
364 try std.testing.expectError(
365 error.OutOfMemory,
366 registry.registerAttribute("allocation.retry", interfaces),
367 );
368 try std.testing.expectEqual(@as(u32, 7), registry.nextAttributeId());
369
370 failing.fail_index = std.math.maxInt(usize);
371 const registered = try registry.registerAttribute("allocation.retry", interfaces);
372 try std.testing.expectEqual(@as(u32, 7), registered.attr_id);
373 try std.testing.expectEqual(@as(u32, 8), registry.nextAttributeId());
374 }