lib/choir/src/core/interfaces/base.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const entry = @import("entry.zig");
3
4 pub const ContextOpaque = opaque {};
5
6 pub fn castContext(comptime CtxT: type, ctx_ptr: *const ContextOpaque) *CtxT {
7 return @ptrCast(@alignCast(@constCast(ctx_ptr)));
8 }
9
10 pub const InterfaceId = u64;
11
12 pub fn interfaceId(comptime name: []const u8) InterfaceId {
13 return std.hash.Wyhash.hash(0, name);
14 }
15
16 pub fn interfaceIdRuntime(name: []const u8) InterfaceId {
17 return std.hash.Wyhash.hash(0, name);
18 }
19
20 pub const InterfaceEntry = struct {
21 id: InterfaceId,
22 vtable: *const anyopaque,
23 };
24
25 pub const InterfaceEntries = struct {
26 pub const inline_capacity: usize = 2;
27
28 const Storage = entry.InlineList(InterfaceEntry, inline_capacity);
29
30 storage: Storage = .{},
31
32 pub const InsertError = error{DuplicateInterface} || std.mem.Allocator.Error;
33
34 pub fn initInline(storage: *[inline_capacity]InterfaceEntry) InterfaceEntries {
35 return .{ .storage = Storage.initInline(storage) };
36 }
37
38 pub fn deinit(self: *InterfaceEntries, allocator: std.mem.Allocator) void {
39 self.storage.deinit(allocator);
40 }
41
42 pub fn insert(
43 self: *InterfaceEntries,
44 allocator: std.mem.Allocator,
45 item: InterfaceEntry,
46 ) InsertError!void {
47 if (self.findIndex(item.id) != null) return error.DuplicateInterface;
48 try self.storage.append(allocator, item);
49 std.mem.sort(InterfaceEntry, self.storage.valuesMut(), {}, lessThan);
50 }
51
52 pub fn insertOrReplace(
53 self: *InterfaceEntries,
54 allocator: std.mem.Allocator,
55 item: InterfaceEntry,
56 ) std.mem.Allocator.Error!void {
57 if (self.findIndex(item.id)) |index| {
58 self.storage.valuesMut()[index].vtable = item.vtable;
59 return;
60 }
61 try self.storage.append(allocator, item);
62 std.mem.sort(InterfaceEntry, self.storage.valuesMut(), {}, lessThan);
63 }
64
65 pub fn get(self: *const InterfaceEntries, id: InterfaceId) ?*const anyopaque {
66 const index = self.findIndex(id) orelse return null;
67 return self.storage.values()[index].vtable;
68 }
69
70 pub fn values(self: *const InterfaceEntries) []const InterfaceEntry {
71 return self.storage.values();
72 }
73
74 pub fn count(self: *const InterfaceEntries) usize {
75 return self.storage.values().len;
76 }
77
78 fn findIndex(self: *const InterfaceEntries, id: InterfaceId) ?usize {
79 const sorted = self.storage.values();
80 var left: usize = 0;
81 var right = sorted.len;
82 while (left < right) {
83 const mid = left + (right - left) / 2;
84 if (sorted[mid].id == id) return mid;
85 if (sorted[mid].id < id) {
86 left = mid + 1;
87 } else {
88 right = mid;
89 }
90 }
91 return null;
92 }
93
94 fn lessThan(_: void, a: InterfaceEntry, b: InterfaceEntry) bool {
95 return a.id < b.id;
96 }
97 };
98
99 pub const TypeParamPayload = struct {
100 ptr: *anyopaque,
101 deinit: *const fn (allocator: std.mem.Allocator, ptr: *anyopaque) void,
102 };
103
104 test "interfaceId produces stable hashes" {
105 const testing = std.testing;
106
107 const id1 = interfaceId("ir.interface.symbol_op");
108 const id2 = interfaceId("ir.interface.symbol_op");
109 const id3 = interfaceId("ir.interface.comptime_evaluatable");
110
111 try testing.expectEqual(id1, id2);
112 try testing.expect(id1 != id3);
113
114 const runtime_id = interfaceIdRuntime("ir.interface.symbol_op");
115 try testing.expectEqual(id1, runtime_id);
116 }
117
118 test "InterfaceEntries keeps two sorted entries inline and spills transactionally" {
119 const testing = std.testing;
120 try testing.expectEqual(@as(usize, 24), @sizeOf(InterfaceEntries));
121
122 const vtable_a: u8 = 1;
123 const vtable_b: u8 = 2;
124 const vtable_c: u8 = 3;
125 const vtable_replacement: u8 = 4;
126 const vtable_d: u8 = 5;
127 var failing = testing.FailingAllocator.init(
128 testing.allocator,
129 .{ .fail_index = 0 },
130 );
131 var inline_storage: [InterfaceEntries.inline_capacity]InterfaceEntry = undefined;
132 var entries = InterfaceEntries.initInline(&inline_storage);
133 defer entries.deinit(failing.allocator());
134
135 try entries.insert(failing.allocator(), .{ .id = 20, .vtable = &vtable_b });
136 try entries.insert(failing.allocator(), .{ .id = 10, .vtable = &vtable_a });
137 try testing.expectEqual(@as(usize, 0), failing.alloc_index);
138 try testing.expectEqual(@as(InterfaceId, 10), entries.values()[0].id);
139 try testing.expectEqual(@as(InterfaceId, 20), entries.values()[1].id);
140 try testing.expectError(
141 error.DuplicateInterface,
142 entries.insert(failing.allocator(), .{ .id = 10, .vtable = &vtable_a }),
143 );
144 try entries.insertOrReplace(
145 failing.allocator(),
146 .{ .id = 10, .vtable = &vtable_replacement },
147 );
148 try testing.expectEqual(
149 @as(*const u8, &vtable_replacement),
150 @as(*const u8, @ptrCast(entries.get(10).?)),
151 );
152 try testing.expectEqual(@as(usize, 0), failing.alloc_index);
153
154 try testing.expectError(
155 error.OutOfMemory,
156 entries.insert(failing.allocator(), .{ .id = 30, .vtable = &vtable_c }),
157 );
158 try testing.expectEqual(@as(usize, 2), entries.count());
159 try testing.expect(entries.get(30) == null);
160
161 failing.fail_index = std.math.maxInt(usize);
162 const before_allocated = failing.allocated_bytes;
163 try entries.insert(failing.allocator(), .{ .id = 30, .vtable = &vtable_c });
164 try testing.expectEqual(@as(usize, 3), entries.count());
165 try testing.expectEqual(
166 std.math.add(usize, before_allocated, 3 * @sizeOf(InterfaceEntry)) catch unreachable,
167 failing.allocated_bytes,
168 );
169 try entries.insert(failing.allocator(), .{ .id = 5, .vtable = &vtable_d });
170 try testing.expectEqual(@as(InterfaceId, 5), entries.values()[0].id);
171 try testing.expectEqual(@as(usize, 4), entries.count());
172 }