lib/choir/src/backends/translation.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const ir = @import("../core/root.zig");
3 const artifact_mod = @import("root.zig").artifact;
4
5 const Allocator = std.mem.Allocator;
6
7 pub const Error = Allocator.Error || error{
8 DuplicateTranslation,
9 InvalidTranslation,
10 UnknownTranslation,
11 };
12
13 pub const Request = struct {
14 module: *ir.Operation,
15 entry: ?[]const u8 = null,
16 };
17
18 pub const EmitBytesFn = *const fn (Allocator, Request) anyerror![]u8;
19
20 pub const Descriptor = struct {
21 name: []const u8,
22 target_dialect_name: []const u8,
23 format_name: []const u8,
24 artifact_kind: artifact_mod.ArtifactKind = .unknown,
25 buffer_format: artifact_mod.BufferFormat = .vendor,
26 };
27
28 pub const Registration = struct {
29 name: []const u8,
30 target_dialect_name: []const u8,
31 format_name: []const u8,
32 artifact_kind: artifact_mod.ArtifactKind = .unknown,
33 buffer_format: artifact_mod.BufferFormat = .vendor,
34 emit_bytes: EmitBytesFn,
35 };
36
37 pub fn registration(comptime Impl: type, descriptor: Descriptor) Registration {
38 comptime {
39 if (!@hasDecl(Impl, "emitBytes")) @compileError("backend translation missing emitBytes");
40 const emit_bytes: EmitBytesFn = Impl.emitBytes;
41 _ = emit_bytes;
42 }
43
44 return .{
45 .name = descriptor.name,
46 .target_dialect_name = descriptor.target_dialect_name,
47 .format_name = descriptor.format_name,
48 .artifact_kind = descriptor.artifact_kind,
49 .buffer_format = descriptor.buffer_format,
50 .emit_bytes = Impl.emitBytes,
51 };
52 }
53
54 pub const Registry = struct {
55 allocator: Allocator,
56 registrations: std.ArrayListUnmanaged(Registration) = .empty,
57
58 pub fn init(allocator: Allocator) Registry {
59 return .{ .allocator = allocator };
60 }
61
62 pub fn deinit(self: *Registry) void {
63 self.registrations.deinit(self.allocator);
64 self.* = undefined;
65 }
66
67 pub fn register(self: *Registry, item: Registration) Error!void {
68 try validate(item);
69 if (self.lookup(item.name) != null) return error.DuplicateTranslation;
70 if (self.lookupTargetFormat(item.target_dialect_name, item.format_name) != null) return error.DuplicateTranslation;
71 try self.registrations.ensureTotalCapacity(self.allocator, self.registrations.items.len + 1);
72 self.registrations.appendAssumeCapacity(item);
73 }
74
75 pub fn registerAll(self: *Registry, items: []const Registration) Error!void {
76 for (items) |item| {
77 try self.register(item);
78 }
79 }
80
81 pub fn lookup(self: *const Registry, name: []const u8) ?Registration {
82 for (self.registrations.items) |item| {
83 if (std.mem.eql(u8, item.name, name)) return item;
84 }
85 return null;
86 }
87
88 pub fn lookupTargetFormat(self: *const Registry, target_dialect_name: []const u8, format_name: []const u8) ?Registration {
89 for (self.registrations.items) |item| {
90 if (std.mem.eql(u8, item.target_dialect_name, target_dialect_name) and
91 std.mem.eql(u8, item.format_name, format_name))
92 {
93 return item;
94 }
95 }
96 return null;
97 }
98
99 pub fn emitBytes(
100 self: *const Registry,
101 result_allocator: Allocator,
102 target_dialect_name: []const u8,
103 format_name: []const u8,
104 request: Request,
105 ) anyerror![]u8 {
106 const item = self.lookupTargetFormat(target_dialect_name, format_name) orelse return error.UnknownTranslation;
107 return item.emit_bytes(result_allocator, request);
108 }
109
110 pub fn emitArtifact(
111 self: *const Registry,
112 result_allocator: Allocator,
113 target_dialect_name: []const u8,
114 format_name: []const u8,
115 request: Request,
116 ) anyerror!artifact_mod.Artifact {
117 const item = self.lookupTargetFormat(target_dialect_name, format_name) orelse return error.UnknownTranslation;
118 const bytes = try item.emit_bytes(result_allocator, request);
119 defer result_allocator.free(bytes);
120
121 var artifact = try artifact_mod.Artifact.init(result_allocator, .{
122 .kind = item.artifact_kind,
123 .producer = item.name,
124 });
125 errdefer artifact.deinit();
126
127 try artifact.payload.addBuffer(.{
128 .name = item.format_name,
129 .format = item.buffer_format,
130 .bytes = bytes,
131 });
132 return artifact;
133 }
134 };
135
136 fn validate(item: Registration) Error!void {
137 if (item.name.len == 0) return error.InvalidTranslation;
138 if (item.target_dialect_name.len == 0) return error.InvalidTranslation;
139 if (item.format_name.len == 0) return error.InvalidTranslation;
140 }
141
142 const FakeTranslation = struct {
143 pub fn emitBytes(result_allocator: Allocator, request: Request) anyerror![]u8 {
144 _ = request;
145 return result_allocator.dupe(u8, "fake-bytes");
146 }
147 };
148
149 const fake_registration = registration(FakeTranslation, .{
150 .name = "fake.binary",
151 .target_dialect_name = "fake",
152 .format_name = "fake-binary",
153 });
154
155 test "backend translation registry dispatches by target dialect and format" {
156 var registry = Registry.init(std.testing.allocator);
157 defer registry.deinit();
158
159 try registry.register(fake_registration);
160 try std.testing.expect(registry.lookup("fake.binary") != null);
161 try std.testing.expect(registry.lookupTargetFormat("fake", "fake-binary") != null);
162 try std.testing.expect(registry.lookupTargetFormat("fake", "missing") == null);
163
164 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
165 defer ctx.deinit(std.testing.allocator);
166 try ctx.allowUnregistered();
167
168 const op = try ctx.createOperation(ir.Operation.State.init("fake.op", .unknown));
169
170 const bytes = try registry.emitBytes(std.testing.allocator, "fake", "fake-binary", .{ .module = op });
171 defer std.testing.allocator.free(bytes);
172 try std.testing.expectEqualStrings("fake-bytes", bytes);
173 }
174
175 test "backend translation registry rejects duplicate registrations" {
176 var registry = Registry.init(std.testing.allocator);
177 defer registry.deinit();
178
179 try registry.register(fake_registration);
180 try std.testing.expectError(error.DuplicateTranslation, registry.register(fake_registration));
181
182 const duplicate_target = registration(FakeTranslation, .{
183 .name = "fake.second",
184 .target_dialect_name = "fake",
185 .format_name = "fake-binary",
186 });
187 try std.testing.expectError(error.DuplicateTranslation, registry.register(duplicate_target));
188 }
189
190 test "backend translation registry emits artifacts" {
191 var registry = Registry.init(std.testing.allocator);
192 defer registry.deinit();
193
194 const artifact_registration = registration(FakeTranslation, .{
195 .name = "fake.artifact",
196 .target_dialect_name = "fake",
197 .format_name = "fake-object",
198 .artifact_kind = .object_file,
199 .buffer_format = .object_file,
200 });
201 try registry.register(artifact_registration);
202
203 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
204 defer ctx.deinit(std.testing.allocator);
205 try ctx.allowUnregistered();
206
207 const op = try ctx.createOperation(ir.Operation.State.init("fake.op", .unknown));
208
209 var artifact = try registry.emitArtifact(std.testing.allocator, "fake", "fake-object", .{ .module = op });
210 defer artifact.deinit();
211
212 try std.testing.expectEqual(artifact_mod.ArtifactKind.object_file, artifact.metadata.kind);
213 try std.testing.expectEqual(@as(usize, 1), artifact.payload.buffers.items.len);
214 try std.testing.expectEqualStrings("fake-object", artifact.payload.buffers.items[0].name);
215 try std.testing.expectEqualStrings("fake-bytes", artifact.payload.buffers.items[0].bytes);
216 }
217
218 test "backend translation registry reports unknown targets" {
219 var registry = Registry.init(std.testing.allocator);
220 defer registry.deinit();
221
222 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
223 defer ctx.deinit(std.testing.allocator);
224 try ctx.allowUnregistered();
225
226 const op = try ctx.createOperation(ir.Operation.State.init("fake.op", .unknown));
227 try std.testing.expectError(
228 error.UnknownTranslation,
229 registry.emitBytes(std.testing.allocator, "missing", "fake-binary", .{ .module = op }),
230 );
231 }