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 }