lib/choir/src/core/dialects/registry.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const core = @import("../root.zig");
  3 
  4 const Type = core.Type;
  5 const Operation = core.Operation;
  6 const Context = core.Context;
  7 const interfaces = core.interfaces;
  8 
  9 pub const DialectLoaderFn = *const fn (ctx: *Context) anyerror!void;
 10 
 11 pub const DialectExtensionFn = *const fn (ctx: *Context) anyerror!void;
 12 
 13 pub const DialectRegistryEntry = struct {
 14     name: []const u8,
 15     load: DialectLoaderFn,
 16 };
 17 
 18 pub const DialectRegistrySpec = struct {
 19     entries: []const DialectRegistryEntry,
 20 };
 21 
 22 pub const OpInterfaceFallbackFn = *const fn (op: *const Operation) ?*const anyopaque;
 23 
 24 pub const OpInterfaceFallbackEntry = struct {
 25     id: interfaces.InterfaceId,
 26     fallback: OpInterfaceFallbackFn,
 27 };
 28 
 29 pub const TypeInterfaceFallbackFn = *const fn (ctx: *const Context, typ: Type) ?*const anyopaque;
 30 
 31 pub const TypeInterfaceFallbackEntry = struct {
 32     id: interfaces.InterfaceId,
 33     fallback: TypeInterfaceFallbackFn,
 34 };
 35 
 36 pub fn InterfaceFallbackRegistry(comptime FallbackFn: type) type {
 37     return struct {
 38         groups: std.ArrayListUnmanaged(Group) = .empty,
 39 
 40         const Self = @This();
 41 
 42         const Entry = struct {
 43             dialect_name: []const u8,
 44             fallback: FallbackFn,
 45         };
 46 
 47         pub const Lookup = struct {
 48             entries: []const Entry,
 49 
 50             pub fn get(self: Lookup, dialect_name: []const u8) ?FallbackFn {
 51                 for (self.entries) |entry| {
 52                     if (std.mem.eql(u8, entry.dialect_name, dialect_name)) return entry.fallback;
 53                 }
 54                 return null;
 55             }
 56         };
 57 
 58         const Group = struct {
 59             id: interfaces.InterfaceId,
 60             entries: std.ArrayListUnmanaged(Entry),
 61         };
 62 
 63         pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
 64             for (self.groups.items) |*group| {
 65                 group.entries.deinit(allocator);
 66             }
 67             self.groups.deinit(allocator);
 68         }
 69 
 70         pub fn register(
 71             self: *Self,
 72             allocator: std.mem.Allocator,
 73             dialect_name: []const u8,
 74             id: interfaces.InterfaceId,
 75             fallback: FallbackFn,
 76         ) (error{DuplicateInterface} || std.mem.Allocator.Error)!void {
 77             const index = self.lowerBound(id);
 78             if (index < self.groups.items.len and self.groups.items[index].id == id) {
 79                 const group = &self.groups.items[index];
 80                 for (group.entries.items) |entry| {
 81                     if (std.mem.eql(u8, entry.dialect_name, dialect_name)) {
 82                         return error.DuplicateInterface;
 83                     }
 84                 }
 85                 try group.entries.append(allocator, .{ .dialect_name = dialect_name, .fallback = fallback });
 86                 return;
 87             }
 88 
 89             var entries: std.ArrayListUnmanaged(Entry) = .empty;
 90             errdefer entries.deinit(allocator);
 91             try entries.append(allocator, .{ .dialect_name = dialect_name, .fallback = fallback });
 92             try self.groups.insert(allocator, index, .{ .id = id, .entries = entries });
 93         }
 94 
 95         pub fn get(self: *const Self, dialect_name: []const u8, id: interfaces.InterfaceId) ?FallbackFn {
 96             const candidates = self.lookup(id) orelse return null;
 97             return candidates.get(dialect_name);
 98         }
 99 
100         pub fn lookup(self: *const Self, id: interfaces.InterfaceId) ?Lookup {
101             const index = self.lowerBound(id);
102             if (index == self.groups.items.len or self.groups.items[index].id != id) return null;
103             return .{ .entries = self.groups.items[index].entries.items };
104         }
105 
106         fn lowerBound(self: *const Self, id: interfaces.InterfaceId) usize {
107             var left: usize = 0;
108             var right = self.groups.items.len;
109             while (left < right) {
110                 const mid = left + (right - left) / 2;
111                 if (self.groups.items[mid].id < id) {
112                     left = mid + 1;
113                 } else {
114                     right = mid;
115                 }
116             }
117             return left;
118         }
119     };
120 }
121 
122 pub const DialectLoadState = enum {
123     loading,
124     loaded,
125 };
126 
127 pub const Dialect = struct {
128     name: []const u8,
129     context: *Context,
130 
131     pub fn init(name: []const u8, context: *Context) Dialect {
132         return .{
133             .name = name,
134             .context = context,
135         };
136     }
137 };
138 
139 pub const DialectRegistry = struct {
140     names: std.StringHashMapUnmanaged(void),
141 
142     loaded: std.StringHashMap(*Dialect),
143 
144     loaders: std.StringHashMapUnmanaged(DialectLoaderFn),
145 
146     load_state: std.StringHashMapUnmanaged(DialectLoadState),
147 
148     pending_extensions: std.StringHashMapUnmanaged(std.ArrayListUnmanaged(DialectExtensionFn)),
149 
150     interfaces: std.StringHashMapUnmanaged(std.ArrayListUnmanaged(interfaces.InterfaceEntry)),
151 
152     op_interface_fallbacks: InterfaceFallbackRegistry(OpInterfaceFallbackFn),
153 
154     type_interface_fallbacks: InterfaceFallbackRegistry(TypeInterfaceFallbackFn),
155 
156     backend_names: std.StringHashMapUnmanaged(void),
157 
158     operation_registry: interfaces.OperationRegistry,
159 
160     allow_unregistered: bool,
161 
162     pub fn init(
163         table_allocator: std.mem.Allocator,
164         interface_allocator: std.mem.Allocator,
165     ) DialectRegistry {
166         return .{
167             .names = .{},
168             .loaded = std.StringHashMap(*Dialect).init(table_allocator),
169             .loaders = .{},
170             .load_state = .{},
171             .pending_extensions = .{},
172             .interfaces = .{},
173             .op_interface_fallbacks = .{},
174             .type_interface_fallbacks = .{},
175             .backend_names = .{},
176             .operation_registry = interfaces.OperationRegistry.init(interface_allocator),
177             .allow_unregistered = false,
178         };
179     }
180 
181     pub fn deinit(
182         self: *DialectRegistry,
183         table_allocator: std.mem.Allocator,
184         name_allocator: std.mem.Allocator,
185         interface_allocator: std.mem.Allocator,
186     ) void {
187         var dialect_iter = self.loaded.valueIterator();
188         while (dialect_iter.next()) |dialect| {
189             table_allocator.destroy(dialect.*);
190         }
191         self.loaded.deinit();
192 
193         self.loaders.deinit(table_allocator);
194 
195         self.load_state.deinit(table_allocator);
196 
197         var ext_iter = self.pending_extensions.valueIterator();
198         while (ext_iter.next()) |ext_list| {
199             ext_list.deinit(interface_allocator);
200         }
201         self.pending_extensions.deinit(interface_allocator);
202 
203         var di_iter = self.interfaces.valueIterator();
204         while (di_iter.next()) |iface_list| {
205             iface_list.deinit(interface_allocator);
206         }
207         self.interfaces.deinit(interface_allocator);
208 
209         self.op_interface_fallbacks.deinit(interface_allocator);
210 
211         self.type_interface_fallbacks.deinit(interface_allocator);
212 
213         self.backend_names.deinit(table_allocator);
214 
215         var name_iter = self.names.keyIterator();
216         while (name_iter.next()) |name_ptr| {
217             name_allocator.free(name_ptr.*);
218         }
219         self.names.deinit(table_allocator);
220 
221         self.operation_registry.deinit();
222     }
223 
224     pub fn internDialectName(
225         self: *DialectRegistry,
226         table_allocator: std.mem.Allocator,
227         name_allocator: std.mem.Allocator,
228         name: []const u8,
229     ) ![]const u8 {
230         if (self.names.getKeyPtr(name)) |key_ptr| {
231             return key_ptr.*;
232         }
233         const copy = try name_allocator.dupe(u8, name);
234         errdefer name_allocator.free(copy);
235         try self.names.putNoClobber(table_allocator, copy, {});
236         return copy;
237     }
238 };
239 
240 test "interface fallback registry survives allocation failures" {
241     const testing = std.testing;
242     const Harness = struct {
243         const FallbackFn = *const fn () void;
244 
245         fn fallback() void {}
246 
247         fn run(allocator: std.mem.Allocator) !void {
248             var registry: InterfaceFallbackRegistry(FallbackFn) = .{};
249             defer registry.deinit(allocator);
250 
251             try registry.register(allocator, "beta", 17, fallback);
252             try registry.register(allocator, "alpha", 3, fallback);
253             try registry.register(allocator, "alpha", 17, fallback);
254             const id_17 = registry.lookup(17).?;
255             try testing.expect(id_17.get("alpha") != null);
256             try testing.expect(id_17.get("beta") != null);
257             try testing.expectEqual(@as(?FallbackFn, null), id_17.get("missing"));
258             try testing.expectEqual(@as(?InterfaceFallbackRegistry(FallbackFn).Lookup, null), registry.lookup(19));
259             try testing.expect(registry.get("alpha", 3) != null);
260             try testing.expect(registry.get("alpha", 17) != null);
261             try testing.expect(registry.get("beta", 17) != null);
262         }
263     };
264 
265     try testing.checkAllAllocationFailures(testing.allocator, Harness.run, .{});
266 }
267 
268 test {
269     @import("std").testing.refAllDecls(@This());
270 }