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 }