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 }