lib/choir/src/core/interfaces/ops.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const base = @import("base.zig");
3 const registry_entry = @import("entry.zig");
4 const traits = @import("traits.zig");
5 const format = @import("../root.zig").format;
6 const IrAttribute = @import("../root.zig").Attribute;
7 const IrBlock = @import("../root.zig").Block;
8 const IrOperation = @import("../root.zig").Operation;
9 const IrSymbolTable = @import("../root.zig").SymbolTable;
10 const IrType = @import("../root.zig").Type;
11 const IrValue = @import("../root.zig").Value;
12
13 const InterfaceId = base.InterfaceId;
14 const InterfaceEntry = base.InterfaceEntry;
15 const InterfaceEntries = base.InterfaceEntries;
16 const TraitId = traits.TraitId;
17 const OperationTraits = traits.OperationTraits;
18 const AttributeNameList = registry_entry.InlineList([]const u8, 0);
19
20 fn name_less_than(_: void, a: []const u8, b: []const u8) bool {
21 return std.mem.order(u8, a, b) == .lt;
22 }
23
24 fn type_constraint_less_than(
25 _: void,
26 a: OperationTypeConstraint,
27 b: OperationTypeConstraint,
28 ) bool {
29 return a.index < b.index;
30 }
31
32 pub const CountRange = struct {
33 min: usize = 0,
34 max: ?usize = null,
35
36 pub fn exactly(count: usize) CountRange {
37 return .{ .min = count, .max = count };
38 }
39
40 pub fn atLeast(count: usize) CountRange {
41 return .{ .min = count };
42 }
43
44 pub fn atMost(count: usize) CountRange {
45 return .{ .max = count };
46 }
47
48 pub fn between(min: usize, max: usize) CountRange {
49 return .{ .min = min, .max = max };
50 }
51
52 pub fn allows(self: CountRange, count: usize) bool {
53 if (count < self.min) return false;
54 if (self.max) |max| {
55 if (count > max) return false;
56 }
57 return true;
58 }
59
60 pub fn hasConstraint(self: CountRange) bool {
61 return self.min != 0 or self.max != null;
62 }
63 };
64
65 pub const OperationShape = struct {
66 operands: CountRange = .{},
67 results: CountRange = .{},
68 regions: CountRange = .{},
69 successors: CountRange = .{},
70
71 pub fn hasConstraints(self: OperationShape) bool {
72 return self.operands.hasConstraint() or
73 self.results.hasConstraint() or
74 self.regions.hasConstraint() or
75 self.successors.hasConstraint();
76 }
77
78 pub fn eql(self: OperationShape, other: OperationShape) bool {
79 return std.meta.eql(self, other);
80 }
81 };
82
83 pub const OperationSegmentSpec = struct {
84 attribute_name: []const u8,
85 segments: []const CountRange,
86
87 pub fn hasSegments(self: OperationSegmentSpec) bool {
88 return self.segments.len > 0;
89 }
90
91 pub fn eql(self: OperationSegmentSpec, other: OperationSegmentSpec) bool {
92 if (!std.mem.eql(u8, self.attribute_name, other.attribute_name)) return false;
93 if (self.segments.len != other.segments.len) return false;
94 for (self.segments, other.segments) |a, b| {
95 if (!std.meta.eql(a, b)) return false;
96 }
97 return true;
98 }
99
100 pub fn size(self: OperationSegmentSpec, op: *const IrOperation, index: usize) ?usize {
101 if (index >= self.segments.len) return null;
102 const attr = op.getAttrAs(IrAttribute.ArrayAttr, self.attribute_name) orelse return null;
103 const values = attr.getValues();
104 if (index >= values.len) return null;
105 const int_attr = values[index].cast(IrAttribute.IntegerAttr) orelse return null;
106 return std.math.cast(usize, int_attr.getValue());
107 }
108
109 pub fn offset(self: OperationSegmentSpec, op: *const IrOperation, index: usize) ?usize {
110 if (index > self.segments.len) return null;
111 const attr = op.getAttrAs(IrAttribute.ArrayAttr, self.attribute_name) orelse return null;
112 const values = attr.getValues();
113 if (index > values.len) return null;
114 var result: usize = 0;
115 for (values[0..index]) |value| {
116 const int_attr = value.cast(IrAttribute.IntegerAttr) orelse return null;
117 const size_value = std.math.cast(usize, int_attr.getValue()) orelse return null;
118 result = std.math.add(usize, result, size_value) catch return null;
119 }
120 return result;
121 }
122 };
123
124 pub const OperationTypeConstraint = struct {
125 index: usize,
126 type_name: []const u8,
127 allow_parameterized: bool = false,
128
129 pub fn eql(self: OperationTypeConstraint, other: OperationTypeConstraint) bool {
130 return self.index == other.index and
131 self.allow_parameterized == other.allow_parameterized and
132 std.mem.eql(u8, self.type_name, other.type_name);
133 }
134 };
135
136 pub const OperationPropertiesModel = struct {
137 name: []const u8,
138 size: usize,
139 alignment: std.mem.Alignment,
140 /// Only owner-defined complete representations qualify immutable capture.
141 serialization: enum { unsupported, single_attribute } = .unsupported,
142
143 init: *const fn (storage: *anyopaque, allocator: std.mem.Allocator) anyerror!void,
144 deinit: *const fn (storage: *anyopaque, allocator: std.mem.Allocator) void,
145 getInherentAttr: ?*const fn (
146 op: *const IrOperation,
147 storage: *const anyopaque,
148 name: []const u8,
149 ) ?IrAttribute = null,
150 setInherentAttr: ?*const fn (
151 op: *IrOperation,
152 storage: *anyopaque,
153 name: []const u8,
154 value: IrAttribute,
155 ) anyerror!bool = null,
156 removeInherentAttr: ?*const fn (
157 op: *IrOperation,
158 storage: *anyopaque,
159 name: []const u8,
160 ) bool = null,
161 getPropertiesAsAttr: ?*const fn (
162 op: *const IrOperation,
163 storage: *const anyopaque,
164 ) ?IrAttribute = null,
165 setPropertiesFromAttr: ?*const fn (
166 op: *IrOperation,
167 storage: *anyopaque,
168 attr: IrAttribute,
169 ) anyerror!void = null,
170 copyProperties: *const fn (
171 dest: *anyopaque,
172 source: *const anyopaque,
173 ) anyerror!void,
174 };
175
176 fn single_attribute_properties_model_adapter(
177 comptime model_name: []const u8,
178 comptime attr_name: []const u8,
179 ) type {
180 return struct {
181 const Storage = struct {
182 value: ?IrAttribute = null,
183 };
184
185 comptime {
186 std.debug.assert(@typeInfo(Storage).@"struct".field_names.len == 1);
187 }
188
189 fn from(storage: *anyopaque) *Storage {
190 return @ptrCast(@alignCast(storage));
191 }
192
193 fn fromConst(storage: *const anyopaque) *const Storage {
194 return @ptrCast(@alignCast(storage));
195 }
196
197 fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
198 from(storage).* = .{};
199 }
200
201 fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
202
203 fn get(_: *const IrOperation, storage: *const anyopaque, name: []const u8) ?IrAttribute {
204 if (!std.mem.eql(u8, name, attr_name)) return null;
205 return fromConst(storage).value;
206 }
207
208 fn set(_: *IrOperation, storage: *anyopaque, name: []const u8, attr: IrAttribute) anyerror!bool {
209 if (!std.mem.eql(u8, name, attr_name)) return false;
210 from(storage).value = attr;
211 return true;
212 }
213
214 fn remove(_: *IrOperation, storage: *anyopaque, name: []const u8) bool {
215 if (!std.mem.eql(u8, name, attr_name)) return false;
216 const self = from(storage);
217 const existed = self.value != null;
218 self.value = null;
219 return existed;
220 }
221
222 fn getProperties(_: *const IrOperation, storage: *const anyopaque) ?IrAttribute {
223 return fromConst(storage).value;
224 }
225
226 fn setProperties(_: *IrOperation, storage: *anyopaque, attr: IrAttribute) anyerror!void {
227 from(storage).value = attr;
228 }
229
230 fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void {
231 from(dest).* = fromConst(source).*;
232 }
233
234 const model = OperationPropertiesModel{
235 .name = model_name,
236 .size = @sizeOf(Storage),
237 .alignment = std.mem.Alignment.fromByteUnits(@alignOf(Storage)),
238 .serialization = .single_attribute,
239 .init = init,
240 .deinit = deinit,
241 .getInherentAttr = get,
242 .setInherentAttr = set,
243 .removeInherentAttr = remove,
244 .getPropertiesAsAttr = getProperties,
245 .setPropertiesFromAttr = setProperties,
246 .copyProperties = copyProperties,
247 };
248 };
249 }
250
251 pub fn singleAttributePropertiesModel(
252 comptime model_name: []const u8,
253 comptime attr_name: []const u8,
254 ) OperationPropertiesModel {
255 return single_attribute_properties_model_adapter(model_name, attr_name).model;
256 }
257
258 pub const OperationInfo = struct {
259 pub const dynamic_trait_inline_capacity = traits.TraitIds.inline_capacity;
260 pub const interface_inline_capacity = InterfaceEntries.inline_capacity;
261
262 pub const AttributeNameCapacity = struct {
263 inherent: usize = 0,
264 required: usize = 0,
265 };
266
267 name: []const u8,
268
269 traits: OperationTraits = .{},
270
271 shape: OperationShape = .{},
272
273 dynamic_trait_ids: traits.TraitIds = .{},
274
275 interfaces: InterfaceEntries = .{},
276
277 inherent_attribute_names: AttributeNameList = .{},
278
279 required_attribute_names: AttributeNameList = .{},
280
281 operand_type_constraints: std.ArrayListUnmanaged(OperationTypeConstraint) = .empty,
282
283 result_type_constraints: std.ArrayListUnmanaged(OperationTypeConstraint) = .empty,
284
285 operand_segments: ?OperationSegmentSpec = null,
286
287 result_segments: ?OperationSegmentSpec = null,
288
289 properties_model: ?OperationPropertiesModel = null,
290
291 pub const AddTraitError = error{DuplicateTrait} || std.mem.Allocator.Error;
292
293 pub const AddInterfaceError = error{DuplicateInterface} || std.mem.Allocator.Error;
294
295 pub const AddInherentAttributeNameError = error{DuplicateInherentAttributeName} || std.mem.Allocator.Error;
296
297 pub const AddRequiredAttributeNameError = error{DuplicateRequiredAttributeName} || AddInherentAttributeNameError;
298
299 pub const SetPropertiesModelError = error{
300 DuplicateOperationProperties,
301 IncompleteOperationPropertiesCodec,
302 };
303 pub const SetShapeError = error{ConflictingOperationShape};
304 pub const SetSegmentSpecError = error{
305 EmptySegmentAttributeName,
306 EmptyOperationSegments,
307 ConflictingOperandSegments,
308 ConflictingResultSegments,
309 } || std.mem.Allocator.Error;
310 pub const AddTypeConstraintError = error{
311 DuplicateOperandTypeConstraint,
312 DuplicateResultTypeConstraint,
313 ConflictingOperandTypeConstraint,
314 ConflictingResultTypeConstraint,
315 } || std.mem.Allocator.Error;
316
317 pub fn init(name: []const u8) OperationInfo {
318 return .{ .name = name };
319 }
320
321 pub fn initEntryStorage(
322 name: []const u8,
323 trait_storage: *[dynamic_trait_inline_capacity]TraitId,
324 interface_storage: *[interface_inline_capacity]InterfaceEntry,
325 inherent_attribute_storage: [][]const u8,
326 required_attribute_storage: [][]const u8,
327 ) OperationInfo {
328 return .{
329 .name = name,
330 .dynamic_trait_ids = traits.TraitIds.initInline(trait_storage),
331 .interfaces = InterfaceEntries.initInline(interface_storage),
332 .inherent_attribute_names = initAttributeNameList(inherent_attribute_storage),
333 .required_attribute_names = initAttributeNameList(required_attribute_storage),
334 };
335 }
336
337 fn initAttributeNameList(storage: [][]const u8) AttributeNameList {
338 if (storage.len == 0) return .{};
339 return AttributeNameList.initBorrowed(storage);
340 }
341
342 pub fn deinit(self: *OperationInfo, allocator: std.mem.Allocator) void {
343 self.dynamic_trait_ids.deinit(allocator);
344 self.interfaces.deinit(allocator);
345 for (self.inherent_attribute_names.values()) |name| {
346 allocator.free(name);
347 }
348 self.inherent_attribute_names.deinit(allocator);
349 for (self.required_attribute_names.values()) |name| {
350 allocator.free(name);
351 }
352 self.required_attribute_names.deinit(allocator);
353 for (self.operand_type_constraints.items) |constraint| {
354 allocator.free(constraint.type_name);
355 }
356 self.operand_type_constraints.deinit(allocator);
357 for (self.result_type_constraints.items) |constraint| {
358 allocator.free(constraint.type_name);
359 }
360 self.result_type_constraints.deinit(allocator);
361 if (self.operand_segments) |segment_spec| {
362 freeSegmentSpec(allocator, segment_spec);
363 }
364 if (self.result_segments) |segment_spec| {
365 freeSegmentSpec(allocator, segment_spec);
366 }
367 }
368
369 pub fn addTrait(
370 self: *OperationInfo,
371 allocator: std.mem.Allocator,
372 trait_id: TraitId,
373 ) AddTraitError!void {
374 try self.dynamic_trait_ids.insert(allocator, trait_id);
375 }
376
377 pub fn addInterface(
378 self: *OperationInfo,
379 allocator: std.mem.Allocator,
380 entry: InterfaceEntry,
381 ) AddInterfaceError!void {
382 try self.interfaces.insert(allocator, entry);
383 }
384
385 pub fn addOrReplaceInterface(
386 self: *OperationInfo,
387 allocator: std.mem.Allocator,
388 entry: InterfaceEntry,
389 ) !void {
390 try self.interfaces.insertOrReplace(allocator, entry);
391 }
392
393 pub fn addInherentAttributeName(
394 self: *OperationInfo,
395 allocator: std.mem.Allocator,
396 name: []const u8,
397 ) AddInherentAttributeNameError!void {
398 if (self.hasInherentAttributeName(name)) return error.DuplicateInherentAttributeName;
399
400 const owned_name = try allocator.dupe(u8, name);
401 errdefer allocator.free(owned_name);
402
403 try self.inherent_attribute_names.append(allocator, owned_name);
404
405 std.mem.sort(
406 []const u8,
407 self.inherent_attribute_names.valuesMut(),
408 {},
409 name_less_than,
410 );
411 }
412
413 pub fn addInherentAttributeNames(
414 self: *OperationInfo,
415 allocator: std.mem.Allocator,
416 names: []const []const u8,
417 ) AddInherentAttributeNameError!void {
418 for (names) |name| {
419 try self.addInherentAttributeName(allocator, name);
420 }
421 }
422
423 pub fn getInherentAttributeNames(self: *const OperationInfo) []const []const u8 {
424 return self.inherent_attribute_names.values();
425 }
426
427 pub fn hasInherentAttributeName(self: *const OperationInfo, name: []const u8) bool {
428 return sortedNameContains(self.inherent_attribute_names.values(), name);
429 }
430
431 pub fn addRequiredAttributeName(
432 self: *OperationInfo,
433 allocator: std.mem.Allocator,
434 name: []const u8,
435 ) AddRequiredAttributeNameError!void {
436 if (!self.hasInherentAttributeName(name)) {
437 try self.addInherentAttributeName(allocator, name);
438 }
439 if (self.hasRequiredAttributeName(name)) return error.DuplicateRequiredAttributeName;
440
441 const owned_name = try allocator.dupe(u8, name);
442 errdefer allocator.free(owned_name);
443
444 try self.required_attribute_names.append(allocator, owned_name);
445
446 std.mem.sort(
447 []const u8,
448 self.required_attribute_names.valuesMut(),
449 {},
450 name_less_than,
451 );
452 }
453
454 pub fn addRequiredAttributeNames(
455 self: *OperationInfo,
456 allocator: std.mem.Allocator,
457 names: []const []const u8,
458 ) AddRequiredAttributeNameError!void {
459 for (names) |name| {
460 try self.addRequiredAttributeName(allocator, name);
461 }
462 }
463
464 pub fn getRequiredAttributeNames(self: *const OperationInfo) []const []const u8 {
465 return self.required_attribute_names.values();
466 }
467
468 pub fn hasRequiredAttributeName(self: *const OperationInfo, name: []const u8) bool {
469 return sortedNameContains(self.required_attribute_names.values(), name);
470 }
471
472 fn sortedNameContains(names: []const []const u8, name: []const u8) bool {
473 var left: usize = 0;
474 var right: usize = names.len;
475 while (left < right) {
476 const mid = left + (right - left) / 2;
477 const cmp = std.mem.order(u8, names[mid], name);
478 switch (cmp) {
479 .eq => return true,
480 .lt => left = mid + 1,
481 .gt => right = mid,
482 }
483 }
484 return false;
485 }
486
487 pub fn setPropertiesModel(
488 self: *OperationInfo,
489 model: OperationPropertiesModel,
490 ) SetPropertiesModelError!void {
491 if (self.properties_model != null) return error.DuplicateOperationProperties;
492 if ((model.getPropertiesAsAttr == null) != (model.setPropertiesFromAttr == null)) {
493 return error.IncompleteOperationPropertiesCodec;
494 }
495 self.properties_model = model;
496 }
497
498 pub fn getPropertiesModel(self: *const OperationInfo) ?*const OperationPropertiesModel {
499 if (self.properties_model) |*model| return model;
500 return null;
501 }
502
503 pub fn hasPropertiesModel(self: *const OperationInfo) bool {
504 return self.properties_model != null;
505 }
506
507 pub fn setShape(
508 self: *OperationInfo,
509 shape: OperationShape,
510 ) SetShapeError!void {
511 if (self.shape.hasConstraints() and !self.shape.eql(shape)) {
512 return error.ConflictingOperationShape;
513 }
514 self.shape = shape;
515 }
516
517 pub fn setOperandSegments(
518 self: *OperationInfo,
519 allocator: std.mem.Allocator,
520 spec: OperationSegmentSpec,
521 ) SetSegmentSpecError!void {
522 try self.setSegmentSpec(allocator, &self.operand_segments, spec, error.ConflictingOperandSegments);
523 }
524
525 pub fn setResultSegments(
526 self: *OperationInfo,
527 allocator: std.mem.Allocator,
528 spec: OperationSegmentSpec,
529 ) SetSegmentSpecError!void {
530 try self.setSegmentSpec(allocator, &self.result_segments, spec, error.ConflictingResultSegments);
531 }
532
533 fn setSegmentSpec(
534 self: *OperationInfo,
535 allocator: std.mem.Allocator,
536 slot: *?OperationSegmentSpec,
537 spec: OperationSegmentSpec,
538 conflict: SetSegmentSpecError,
539 ) SetSegmentSpecError!void {
540 _ = self;
541 if (spec.attribute_name.len == 0) return error.EmptySegmentAttributeName;
542 if (spec.segments.len == 0) return error.EmptyOperationSegments;
543 if (slot.*) |existing| {
544 if (existing.eql(spec)) return;
545 return conflict;
546 }
547
548 const owned_name = try allocator.dupe(u8, spec.attribute_name);
549 errdefer allocator.free(owned_name);
550 const owned_segments = try allocator.dupe(CountRange, spec.segments);
551 errdefer allocator.free(owned_segments);
552 slot.* = .{
553 .attribute_name = owned_name,
554 .segments = owned_segments,
555 };
556 }
557
558 pub fn addOperandTypeConstraint(
559 self: *OperationInfo,
560 allocator: std.mem.Allocator,
561 constraint: OperationTypeConstraint,
562 ) AddTypeConstraintError!void {
563 try self.addTypeConstraint(
564 allocator,
565 &self.operand_type_constraints,
566 constraint,
567 error.DuplicateOperandTypeConstraint,
568 error.ConflictingOperandTypeConstraint,
569 );
570 }
571
572 pub fn addResultTypeConstraint(
573 self: *OperationInfo,
574 allocator: std.mem.Allocator,
575 constraint: OperationTypeConstraint,
576 ) AddTypeConstraintError!void {
577 try self.addTypeConstraint(
578 allocator,
579 &self.result_type_constraints,
580 constraint,
581 error.DuplicateResultTypeConstraint,
582 error.ConflictingResultTypeConstraint,
583 );
584 }
585
586 fn addTypeConstraint(
587 self: *OperationInfo,
588 allocator: std.mem.Allocator,
589 list: *std.ArrayListUnmanaged(OperationTypeConstraint),
590 constraint: OperationTypeConstraint,
591 duplicate: AddTypeConstraintError,
592 conflict: AddTypeConstraintError,
593 ) AddTypeConstraintError!void {
594 _ = self;
595 for (list.items) |existing| {
596 if (existing.index == constraint.index) {
597 if (existing.eql(constraint)) return duplicate;
598 return conflict;
599 }
600 }
601
602 const owned_name = try allocator.dupe(u8, constraint.type_name);
603 errdefer allocator.free(owned_name);
604 try list.append(allocator, .{
605 .index = constraint.index,
606 .type_name = owned_name,
607 .allow_parameterized = constraint.allow_parameterized,
608 });
609 std.mem.sort(OperationTypeConstraint, list.items, {}, type_constraint_less_than);
610 }
611
612 pub fn getOperandTypeConstraints(self: *const OperationInfo) []const OperationTypeConstraint {
613 return self.operand_type_constraints.items;
614 }
615
616 pub fn getResultTypeConstraints(self: *const OperationInfo) []const OperationTypeConstraint {
617 return self.result_type_constraints.items;
618 }
619
620 pub fn getOperandSegments(self: *const OperationInfo) ?OperationSegmentSpec {
621 return self.operand_segments;
622 }
623
624 pub fn getResultSegments(self: *const OperationInfo) ?OperationSegmentSpec {
625 return self.result_segments;
626 }
627
628 pub fn getInterface(self: *const OperationInfo, id: InterfaceId) ?*const anyopaque {
629 return self.interfaces.get(id);
630 }
631
632 pub fn hasTraitId(self: *const OperationInfo, trait_id: TraitId) bool {
633 return self.dynamic_trait_ids.contains(trait_id);
634 }
635
636 pub fn getDynamicTraitIds(self: *const OperationInfo) []const TraitId {
637 return self.dynamic_trait_ids.values();
638 }
639
640 pub fn hasInterface(self: *const OperationInfo, id: InterfaceId) bool {
641 return self.getInterface(id) != null;
642 }
643
644 pub fn getNumInterfaces(self: *const OperationInfo) usize {
645 return self.interfaces.count();
646 }
647 };
648
649 pub const OperationRegistry = struct {
650 allocator: std.mem.Allocator,
651
652 ops: std.StringHashMapUnmanaged(*OperationInfo),
653 batch_regions: ?*BatchEntryStorage.RegionOwner,
654
655 allow_unregistered_operations: bool,
656
657 pub const RegisterPropertiesModelError = OperationInfo.SetPropertiesModelError || GetOrCreateError;
658 pub const RegisterShapeError = OperationInfo.SetShapeError || GetOrCreateError;
659 pub const RegisterSegmentSpecError = OperationInfo.SetSegmentSpecError || GetOrCreateError;
660 pub const RegisterTypeConstraintError = OperationInfo.AddTypeConstraintError || GetOrCreateError;
661
662 pub const GetOrCreateError: type = std.mem.Allocator.Error;
663
664 pub const GetOrCreateResult = struct {
665 info: *OperationInfo,
666 created: bool,
667 };
668
669 pub const RegisterTraitError: type = OperationInfo.AddTraitError;
670 pub const RegisterInterfaceError: type = OperationInfo.AddInterfaceError;
671 pub const RegisterInherentAttributeNameError: type =
672 OperationInfo.AddInherentAttributeNameError;
673 pub const RegisterRequiredAttributeNameError: type =
674 OperationInfo.AddRequiredAttributeNameError;
675
676 const EntryStorage: type = registry_entry.InlineStorage(
677 OperationInfo,
678 TraitId,
679 OperationInfo.dynamic_trait_inline_capacity,
680 InterfaceEntry,
681 OperationInfo.interface_inline_capacity,
682 []const u8,
683 []const u8,
684 );
685 const BatchEntryStorage: type = EntryStorage.BatchStorage;
686
687 pub fn init(allocator: std.mem.Allocator) OperationRegistry {
688 return .{
689 .allocator = allocator,
690 .ops = .{},
691 .batch_regions = null,
692 .allow_unregistered_operations = false,
693 };
694 }
695
696 pub fn deinit(self: *OperationRegistry) void {
697 var it = self.ops.valueIterator();
698 while (it.next()) |info_ptr| {
699 const info = info_ptr.*;
700 if (self.findBatchRegionLink(info) != null) {
701 info.deinit(self.allocator);
702 info.* = undefined;
703 } else {
704 EntryStorage.destroy(self.allocator, info);
705 }
706 }
707 self.ops.deinit(self.allocator);
708 while (self.batch_regions) |region| {
709 self.batch_regions = region.next;
710 BatchEntryStorage.destroy(self.allocator, region);
711 }
712 }
713
714 pub fn lookup(self: *const OperationRegistry, op_name: []const u8) ?*OperationInfo {
715 return self.ops.get(op_name);
716 }
717
718 pub fn getOrCreate(self: *OperationRegistry, op_name: []const u8) GetOrCreateError!*OperationInfo {
719 return (try self.getOrCreateTracked(op_name)).info;
720 }
721
722 pub fn getOrCreateTracked(self: *OperationRegistry, op_name: []const u8) GetOrCreateError!GetOrCreateResult {
723 return self.getOrCreateTrackedWithAttributeNameCapacity(op_name, .{});
724 }
725
726 pub fn getOrCreateTrackedWithAttributeNameCapacity(
727 self: *OperationRegistry,
728 op_name: []const u8,
729 capacity: OperationInfo.AttributeNameCapacity,
730 ) GetOrCreateError!GetOrCreateResult {
731 if (self.ops.get(op_name)) |existing| {
732 return .{ .info = existing, .created = false };
733 }
734
735 const info = try EntryStorage.create(
736 self.allocator,
737 op_name,
738 capacity.inherent,
739 capacity.required,
740 );
741 errdefer EntryStorage.destroy(self.allocator, info);
742
743 const gop = try self.ops.getOrPut(self.allocator, info.name);
744 if (gop.found_existing) {
745 EntryStorage.destroy(self.allocator, info);
746 return .{ .info = gop.value_ptr.*, .created = false };
747 }
748
749 gop.value_ptr.* = info;
750 return .{ .info = info, .created = true };
751 }
752
753 pub fn getOrCreateOperationBatch(
754 self: *OperationRegistry,
755 operation_specs: anytype,
756 ) GetOrCreateError!void {
757 var capacity_value: BatchEntryStorage.Capacity = .{};
758 for (operation_specs) |op| {
759 if (self.ops.get(op.name) != null) continue;
760 capacity_value.add(
761 op.name.len,
762 op.inherent_attribute_names.len,
763 op.required_attribute_names.len,
764 ) catch return error.OutOfMemory;
765 }
766 if (capacity_value.entry_count == 0) return;
767 const unused_capacity = std.math.cast(
768 u32,
769 capacity_value.entry_count,
770 ) orelse return error.OutOfMemory;
771
772 const allocation = try BatchEntryStorage.create(
773 self.allocator,
774 capacity_value,
775 );
776 errdefer BatchEntryStorage.destroy(self.allocator, allocation.region);
777
778 try self.ops.ensureUnusedCapacity(self.allocator, unused_capacity);
779 var cursor = BatchEntryStorage.Cursor.init(allocation.entries);
780 for (operation_specs) |op| {
781 if (self.ops.get(op.name) != null) continue;
782 const info = cursor.create(
783 op.name,
784 op.inherent_attribute_names.len,
785 op.required_attribute_names.len,
786 );
787 self.ops.putAssumeCapacityNoClobber(info.name, info);
788 }
789 std.debug.assert(cursor.offset <= allocation.entries.len);
790
791 allocation.region.next = self.batch_regions;
792 self.batch_regions = allocation.region;
793 }
794
795 pub fn registerOperation(
796 self: *OperationRegistry,
797 op_name: []const u8,
798 traits_val: OperationTraits,
799 ) GetOrCreateError!*OperationInfo {
800 const info = try self.getOrCreate(op_name);
801 info.traits = info.traits.merge(traits_val);
802 return info;
803 }
804
805 pub fn registerTrait(
806 self: *OperationRegistry,
807 op_name: []const u8,
808 trait_id: TraitId,
809 ) RegisterTraitError!void {
810 const info = try self.getOrCreate(op_name);
811 try info.addTrait(self.allocator, trait_id);
812 }
813
814 pub fn registerInterface(
815 self: *OperationRegistry,
816 op_name: []const u8,
817 entry: InterfaceEntry,
818 ) RegisterInterfaceError!void {
819 const info = try self.getOrCreate(op_name);
820 try info.addInterface(self.allocator, entry);
821 }
822
823 pub fn registerOrReplaceInterface(
824 self: *OperationRegistry,
825 op_name: []const u8,
826 entry: InterfaceEntry,
827 ) GetOrCreateError!void {
828 const info = try self.getOrCreate(op_name);
829 try info.addOrReplaceInterface(self.allocator, entry);
830 }
831
832 pub fn registerInherentAttributeName(
833 self: *OperationRegistry,
834 op_name: []const u8,
835 attr_name: []const u8,
836 ) RegisterInherentAttributeNameError!void {
837 const info = try self.getOrCreate(op_name);
838 try info.addInherentAttributeName(self.allocator, attr_name);
839 }
840
841 pub fn registerRequiredAttributeName(
842 self: *OperationRegistry,
843 op_name: []const u8,
844 attr_name: []const u8,
845 ) RegisterRequiredAttributeNameError!void {
846 const info = try self.getOrCreate(op_name);
847 try info.addRequiredAttributeName(self.allocator, attr_name);
848 }
849
850 pub fn registerInherentAttributeNames(
851 self: *OperationRegistry,
852 op_name: []const u8,
853 attr_names: []const []const u8,
854 ) RegisterInherentAttributeNameError!void {
855 const info = try self.getOrCreate(op_name);
856 try info.addInherentAttributeNames(self.allocator, attr_names);
857 }
858
859 pub fn registerPropertiesModel(
860 self: *OperationRegistry,
861 op_name: []const u8,
862 model: OperationPropertiesModel,
863 ) RegisterPropertiesModelError!void {
864 const info = try self.getOrCreate(op_name);
865 try info.setPropertiesModel(model);
866 }
867
868 pub fn registerShape(
869 self: *OperationRegistry,
870 op_name: []const u8,
871 shape: OperationShape,
872 ) RegisterShapeError!void {
873 const info = try self.getOrCreate(op_name);
874 try info.setShape(shape);
875 }
876
877 pub fn registerOperandSegments(
878 self: *OperationRegistry,
879 op_name: []const u8,
880 spec: OperationSegmentSpec,
881 ) RegisterSegmentSpecError!void {
882 const info = try self.getOrCreate(op_name);
883 try info.setOperandSegments(self.allocator, spec);
884 }
885
886 pub fn registerResultSegments(
887 self: *OperationRegistry,
888 op_name: []const u8,
889 spec: OperationSegmentSpec,
890 ) RegisterSegmentSpecError!void {
891 const info = try self.getOrCreate(op_name);
892 try info.setResultSegments(self.allocator, spec);
893 }
894
895 pub fn registerOperandTypeConstraint(
896 self: *OperationRegistry,
897 op_name: []const u8,
898 constraint: OperationTypeConstraint,
899 ) RegisterTypeConstraintError!void {
900 const info = try self.getOrCreate(op_name);
901 try info.addOperandTypeConstraint(self.allocator, constraint);
902 }
903
904 pub fn registerResultTypeConstraint(
905 self: *OperationRegistry,
906 op_name: []const u8,
907 constraint: OperationTypeConstraint,
908 ) RegisterTypeConstraintError!void {
909 const info = try self.getOrCreate(op_name);
910 try info.addResultTypeConstraint(self.allocator, constraint);
911 }
912
913 pub fn count(self: *const OperationRegistry) usize {
914 return self.ops.count();
915 }
916
917 pub fn removeOperation(self: *OperationRegistry, op_name: []const u8) bool {
918 const removed = self.ops.fetchRemove(op_name) orelse return false;
919 const info = removed.value;
920 std.debug.assert(removed.key.ptr == info.name.ptr);
921 if (self.findBatchRegionLink(info)) |region_link| {
922 const region = region_link.*.?;
923 info.deinit(self.allocator);
924 info.* = undefined;
925 if (!self.batchRegionHasLiveInfo(region)) {
926 region_link.* = region.next;
927 BatchEntryStorage.destroy(self.allocator, region);
928 }
929 } else {
930 EntryStorage.destroy(self.allocator, info);
931 }
932 return true;
933 }
934
935 fn findBatchRegionLink(
936 self: *OperationRegistry,
937 info: *const OperationInfo,
938 ) ?*?*BatchEntryStorage.RegionOwner {
939 var region_link = &self.batch_regions;
940 while (region_link.*) |region| {
941 if (BatchEntryStorage.contains(region, info)) return region_link;
942 region_link = ®ion.next;
943 }
944 return null;
945 }
946
947 fn batchRegionHasLiveInfo(
948 self: *const OperationRegistry,
949 region: *const BatchEntryStorage.RegionOwner,
950 ) bool {
951 var it = self.ops.valueIterator();
952 while (it.next()) |info_ptr| {
953 if (BatchEntryStorage.contains(region, info_ptr.*)) return true;
954 }
955 return false;
956 }
957 };
958
959 fn freeSegmentSpec(allocator: std.mem.Allocator, spec: OperationSegmentSpec) void {
960 allocator.free(spec.attribute_name);
961 allocator.free(spec.segments);
962 }
963
964 pub const CseOpInterface = struct {
965 pub const interface_name = "ir.interface.cse_op";
966 pub const id: InterfaceId = base.interfaceId(interface_name);
967
968 pub const VTable = struct {
969 includeAttributeInKey: *const fn (
970 op: *const anyopaque,
971 name: []const u8,
972 value: IrAttribute,
973 ) bool,
974 commuteOperandsInKey: *const fn (op: *const anyopaque) bool,
975 };
976
977 pub fn entry(vtable: *const VTable) InterfaceEntry {
978 return .{ .id = id, .vtable = vtable };
979 }
980
981 fn neverCommuteOperandsInKey(_: *const anyopaque) bool {
982 return false;
983 }
984
985 pub fn vtableFor(
986 comptime includeAttributeInKey: *const fn (
987 op: *const anyopaque,
988 name: []const u8,
989 value: IrAttribute,
990 ) bool,
991 ) *const VTable {
992 return &.{
993 .includeAttributeInKey = includeAttributeInKey,
994 .commuteOperandsInKey = neverCommuteOperandsInKey,
995 };
996 }
997
998 pub fn keyPolicyVTableFor(
999 comptime includeAttributeInKey: *const fn (
1000 op: *const anyopaque,
1001 name: []const u8,
1002 value: IrAttribute,
1003 ) bool,
1004 comptime commuteOperandsInKey: *const fn (op: *const anyopaque) bool,
1005 ) *const VTable {
1006 return &.{
1007 .includeAttributeInKey = includeAttributeInKey,
1008 .commuteOperandsInKey = commuteOperandsInKey,
1009 };
1010 }
1011
1012 pub fn entryFor(
1013 comptime includeAttributeInKey: *const fn (
1014 op: *const anyopaque,
1015 name: []const u8,
1016 value: IrAttribute,
1017 ) bool,
1018 ) InterfaceEntry {
1019 return entry(vtableFor(includeAttributeInKey));
1020 }
1021
1022 pub fn keyPolicyEntryFor(
1023 comptime includeAttributeInKey: *const fn (
1024 op: *const anyopaque,
1025 name: []const u8,
1026 value: IrAttribute,
1027 ) bool,
1028 comptime commuteOperandsInKey: *const fn (op: *const anyopaque) bool,
1029 ) InterfaceEntry {
1030 return entry(keyPolicyVTableFor(includeAttributeInKey, commuteOperandsInKey));
1031 }
1032 };
1033
1034 pub const SymbolOpInterface = struct {
1035 pub const interface_name = "ir.interface.symbol_op";
1036 pub const id: InterfaceId = base.interfaceId(interface_name);
1037
1038 pub const VTable = struct {
1039 getSymbolName: *const fn (op: *const anyopaque) ?[]const u8,
1040 setSymbolName: *const fn (op: *const anyopaque, name: []const u8) anyerror!void,
1041 isDeclaration: *const fn (op: *const anyopaque) bool,
1042 };
1043
1044 pub fn entry(vtable: *const VTable) InterfaceEntry {
1045 return .{ .id = id, .vtable = vtable };
1046 }
1047 };
1048
1049 pub const SymbolUserOpInterface = struct {
1050 pub const interface_name = "ir.interface.symbol_user_op";
1051 pub const id: InterfaceId = base.interfaceId(interface_name);
1052
1053 pub const VTable = struct {
1054 verifySymbolUses: *const fn (
1055 op: *const anyopaque,
1056 symbol_tables: *IrSymbolTable.Collection,
1057 ) anyerror!void,
1058 };
1059
1060 pub fn entry(vtable: *const VTable) InterfaceEntry {
1061 return .{ .id = id, .vtable = vtable };
1062 }
1063
1064 pub fn vtableFor(
1065 comptime verifySymbolUses: *const fn (
1066 op: *const anyopaque,
1067 symbol_tables: *IrSymbolTable.Collection,
1068 ) anyerror!void,
1069 ) *const VTable {
1070 return &.{ .verifySymbolUses = verifySymbolUses };
1071 }
1072
1073 pub fn entryFor(
1074 comptime verifySymbolUses: *const fn (
1075 op: *const anyopaque,
1076 symbol_tables: *IrSymbolTable.Collection,
1077 ) anyerror!void,
1078 ) InterfaceEntry {
1079 return entry(vtableFor(verifySymbolUses));
1080 }
1081 };
1082
1083 pub const CallOpInterface = struct {
1084 pub const interface_name = "ir.interface.call_op";
1085 pub const id: InterfaceId = base.interfaceId(interface_name);
1086
1087 pub const VTable = struct {
1088 getCalleeSymbol: *const fn (op: *const anyopaque) ?[]const u8,
1089 getCalleeValue: *const fn (op: *const anyopaque) ?*IrValue,
1090 getArgumentValues: *const fn (op: *const anyopaque) []const *IrValue,
1091 getArgumentKeywords: *const fn (op: *const anyopaque) []const []const u8,
1092 };
1093
1094 pub fn entry(vtable: *const VTable) InterfaceEntry {
1095 return .{ .id = id, .vtable = vtable };
1096 }
1097 };
1098
1099 pub const FunctionOpInterface = struct {
1100 pub const interface_name = "ir.interface.function_op";
1101 pub const id: InterfaceId = base.interfaceId(interface_name);
1102
1103 pub const VTable = struct {
1104 hasBody: *const fn (op: *const anyopaque) bool,
1105 getEntryBlock: *const fn (op: *const anyopaque) ?*IrBlock,
1106 getArgumentCount: *const fn (op: *const anyopaque) usize,
1107 getResultCount: *const fn (op: *const anyopaque) usize,
1108 };
1109
1110 pub fn entry(vtable: *const VTable) InterfaceEntry {
1111 return .{ .id = id, .vtable = vtable };
1112 }
1113 };
1114
1115 pub const RegionKind = enum {
1116 ssacfg,
1117 graph,
1118 };
1119
1120 pub const RegionKindInterface = struct {
1121 pub const interface_name = "ir.interface.region_kind";
1122 pub const id: InterfaceId = base.interfaceId(interface_name);
1123
1124 pub const VTable = struct {
1125 getRegionKind: *const fn (op: *const anyopaque, index: usize) RegionKind,
1126 hasSSADominance: *const fn (op: *const anyopaque, index: usize) bool,
1127 };
1128
1129 pub fn entry(vtable: *const VTable) InterfaceEntry {
1130 return .{ .id = id, .vtable = vtable };
1131 }
1132
1133 pub fn vtableFor(
1134 comptime getRegionKind: *const fn (op: *const anyopaque, index: usize) RegionKind,
1135 comptime hasSSADominance: *const fn (op: *const anyopaque, index: usize) bool,
1136 ) *const VTable {
1137 return &.{
1138 .getRegionKind = getRegionKind,
1139 .hasSSADominance = hasSSADominance,
1140 };
1141 }
1142
1143 pub fn entryFor(
1144 comptime getRegionKind: *const fn (op: *const anyopaque, index: usize) RegionKind,
1145 comptime hasSSADominance: *const fn (op: *const anyopaque, index: usize) bool,
1146 ) InterfaceEntry {
1147 return entry(vtableFor(getRegionKind, hasSSADominance));
1148 }
1149
1150 fn graphRegionKind(_: *const anyopaque, _: usize) RegionKind {
1151 return .graph;
1152 }
1153
1154 fn graphHasSSADominance(_: *const anyopaque, _: usize) bool {
1155 return false;
1156 }
1157
1158 pub fn allGraphRegionsEntry() InterfaceEntry {
1159 return entry(vtableFor(graphRegionKind, graphHasSSADominance));
1160 }
1161 };
1162
1163 pub const ControlFlowInterface = struct {
1164 pub const interface_name = "ir.interface.control_flow";
1165 pub const id: InterfaceId = base.interfaceId(interface_name);
1166
1167 pub const VTable = struct {
1168 getSuccessorCount: *const fn (op: *const anyopaque) usize,
1169 getSuccessor: *const fn (op: *const anyopaque, index: usize) ?*IrBlock,
1170 };
1171
1172 pub fn entry(vtable: *const VTable) InterfaceEntry {
1173 return .{ .id = id, .vtable = vtable };
1174 }
1175
1176 pub fn vtableFor(
1177 comptime getSuccessorCount: *const fn (op: *const anyopaque) usize,
1178 comptime getSuccessor: *const fn (op: *const anyopaque, index: usize) ?*IrBlock,
1179 ) *const VTable {
1180 return &.{
1181 .getSuccessorCount = getSuccessorCount,
1182 .getSuccessor = getSuccessor,
1183 };
1184 }
1185
1186 pub fn entryFor(
1187 comptime getSuccessorCount: *const fn (op: *const anyopaque) usize,
1188 comptime getSuccessor: *const fn (op: *const anyopaque, index: usize) ?*IrBlock,
1189 ) InterfaceEntry {
1190 return entry(vtableFor(getSuccessorCount, getSuccessor));
1191 }
1192 };
1193
1194 pub const InferTypeOpInterface = struct {
1195 pub const interface_name = "ir.interface.infer_type";
1196 pub const id: InterfaceId = base.interfaceId(interface_name);
1197
1198 pub const VTable = struct {
1199 inferResultTypes: *const fn (
1200 op: *const anyopaque,
1201 allocator: std.mem.Allocator,
1202 out_types: *std.ArrayListUnmanaged(IrType),
1203 ) anyerror!void,
1204 };
1205
1206 pub fn entry(vtable: *const VTable) InterfaceEntry {
1207 return .{ .id = id, .vtable = vtable };
1208 }
1209
1210 pub fn vtableFor(
1211 comptime inferResultTypes: *const fn (
1212 op: *const anyopaque,
1213 allocator: std.mem.Allocator,
1214 out_types: *std.ArrayListUnmanaged(IrType),
1215 ) anyerror!void,
1216 ) *const VTable {
1217 return &.{ .inferResultTypes = inferResultTypes };
1218 }
1219
1220 pub fn entryFor(
1221 comptime inferResultTypes: *const fn (
1222 op: *const anyopaque,
1223 allocator: std.mem.Allocator,
1224 out_types: *std.ArrayListUnmanaged(IrType),
1225 ) anyerror!void,
1226 ) InterfaceEntry {
1227 return entry(vtableFor(inferResultTypes));
1228 }
1229 };
1230
1231 pub const FoldResult = union(enum) {
1232 value: *IrValue,
1233 attribute: IrAttribute,
1234 };
1235
1236 pub const FoldResults = struct {
1237 storage: []FoldResult,
1238 len: usize = 0,
1239
1240 pub fn init(storage: []FoldResult) FoldResults {
1241 return .{ .storage = storage };
1242 }
1243
1244 pub fn append(self: *FoldResults, result: FoldResult) error{CapacityExceeded}!void {
1245 std.debug.assert(self.len <= self.storage.len);
1246 if (self.len == self.storage.len) return error.CapacityExceeded;
1247 self.storage[self.len] = result;
1248 self.len += 1;
1249 }
1250
1251 pub fn slice(self: *const FoldResults) []const FoldResult {
1252 std.debug.assert(self.len <= self.storage.len);
1253 return self.storage[0..self.len];
1254 }
1255 };
1256
1257 test "FoldResults enforces caller-provided capacity" {
1258 var storage: [1]FoldResult = undefined;
1259 var results = FoldResults.init(storage[0..]);
1260 try results.append(.{ .value = undefined });
1261 try std.testing.expectEqual(@as(usize, 1), results.slice().len);
1262 try std.testing.expectError(
1263 error.CapacityExceeded,
1264 results.append(.{ .value = undefined }),
1265 );
1266 try std.testing.expectEqual(@as(usize, 1), results.slice().len);
1267 }
1268
1269 pub const FoldOpInterface = struct {
1270 pub const interface_name = "ir.interface.fold_op";
1271 pub const id: InterfaceId = base.interfaceId(interface_name);
1272
1273 pub const VTable = struct {
1274 fold: *const fn (
1275 op: *const anyopaque,
1276 results: *FoldResults,
1277 ) anyerror!void,
1278 };
1279
1280 pub fn entry(vtable: *const VTable) InterfaceEntry {
1281 return .{ .id = id, .vtable = vtable };
1282 }
1283
1284 pub fn vtableFor(
1285 comptime fold: *const fn (
1286 op: *const anyopaque,
1287 results: *FoldResults,
1288 ) anyerror!void,
1289 ) *const VTable {
1290 return &.{ .fold = fold };
1291 }
1292
1293 pub fn entryFor(
1294 comptime fold: *const fn (
1295 op: *const anyopaque,
1296 results: *FoldResults,
1297 ) anyerror!void,
1298 ) InterfaceEntry {
1299 return entry(vtableFor(fold));
1300 }
1301 };
1302
1303 pub const DiagnosticKind = enum {
1304 error_,
1305 warning,
1306 };
1307
1308 pub const EvalError = error{
1309 UnsupportedOperation,
1310 UnknownEffect,
1311 EffectViolation,
1312 LocationViolation,
1313 DivisionByZero,
1314 InvalidOperand,
1315 InvalidConstant,
1316 RequiresDynamicInfo,
1317 InvalidShiftAmount,
1318 InvalidPredicate,
1319 EvaluationFailed,
1320 Overflow,
1321 RecursionDepthExceeded,
1322 InvalidCondition,
1323 InvalidSize,
1324 NegativeSize,
1325 InvalidHandle,
1326 HandleAlreadyDropped,
1327 BorrowOfInvalidHandle,
1328 MoveOfInvalidHandle,
1329 BranchQuotaExceeded,
1330 IterationQuotaExceeded,
1331 DeviceLocationForbidden,
1332 UnifiedLocationForbidden,
1333 YieldMissingOperand,
1334 NoYield,
1335 ValueNotFound,
1336 OutOfMemory,
1337 };
1338
1339 pub const EvalContext = struct {
1340 state: *anyopaque,
1341 allocator: std.mem.Allocator,
1342 emitDiagnostic: *const fn (state: *anyopaque, kind: DiagnosticKind, message: []const u8) EvalError!void,
1343
1344 evaluateRegion: *const fn (state: *anyopaque, region: *const anyopaque) EvalError!IrAttribute,
1345
1346 evaluateRegionWithArgs: *const fn (
1347 state: *anyopaque,
1348 region: *const anyopaque,
1349 args: []const IrAttribute,
1350 ) EvalError!IrAttribute,
1351
1352 evaluateSymbol: *const fn (
1353 state: *anyopaque,
1354 symbol: []const u8,
1355 args: []const IrAttribute,
1356 ) EvalError!IrAttribute,
1357
1358 consumeBranchFuel: *const fn (state: *anyopaque) EvalError!void,
1359
1360 consumeIterationFuel: *const fn (state: *anyopaque) EvalError!void,
1361
1362 allocHandle: *const fn (state: *anyopaque, size: i64) EvalError!i64,
1363
1364 borrowHandle: *const fn (state: *anyopaque, handle: i64) EvalError!i64,
1365
1366 borrowMutHandle: *const fn (state: *anyopaque, handle: i64) EvalError!i64,
1367
1368 moveHandle: *const fn (state: *anyopaque, handle: i64) EvalError!i64,
1369
1370 dropHandle: *const fn (state: *anyopaque, handle: i64) EvalError!void,
1371
1372 createRewriteBuilder: *const fn (state: *anyopaque, root: *IrOperation) EvalError!*anyopaque,
1373 };
1374
1375 pub const Evaluatable = struct {
1376 pub const interface_name = "ir.interface.evaluatable";
1377
1378 pub const id: InterfaceId = base.interfaceId(interface_name);
1379
1380 pub const VTable = struct {
1381 canEval: *const fn (op: *const anyopaque) bool,
1382
1383 evaluate: *const fn (
1384 op: *const anyopaque,
1385 operands: []const IrAttribute,
1386 ctx: *const EvalContext,
1387 ) EvalError!IrAttribute,
1388 };
1389
1390 pub fn entry(vtable: *const VTable) InterfaceEntry {
1391 return .{ .id = id, .vtable = vtable };
1392 }
1393
1394 pub fn vtableFor(
1395 comptime can_eval: *const fn (op: *const anyopaque) bool,
1396 comptime evaluate: *const fn (
1397 op: *const anyopaque,
1398 operands: []const IrAttribute,
1399 ctx: *const EvalContext,
1400 ) EvalError!IrAttribute,
1401 ) *const VTable {
1402 return &.{
1403 .canEval = can_eval,
1404 .evaluate = evaluate,
1405 };
1406 }
1407
1408 pub fn entryFor(
1409 comptime can_eval: *const fn (op: *const anyopaque) bool,
1410 comptime evaluate: *const fn (
1411 op: *const anyopaque,
1412 operands: []const IrAttribute,
1413 ctx: *const EvalContext,
1414 ) EvalError!IrAttribute,
1415 ) InterfaceEntry {
1416 return entry(vtableFor(can_eval, evaluate));
1417 }
1418
1419 pub fn canAlwaysFold(_: *const anyopaque) bool {
1420 return true;
1421 }
1422
1423 pub fn canNeverFold(_: *const anyopaque) bool {
1424 return false;
1425 }
1426 };
1427
1428 pub const YieldOpInterface = struct {
1429 pub const interface_name = "ir.interface.yield_op";
1430
1431 pub const id: InterfaceId = base.interfaceId(interface_name);
1432
1433 pub const VTable = struct {
1434 getYieldOperandCount: *const fn (op: *const anyopaque) usize,
1435 getYieldOperand: *const fn (op: *const anyopaque, index: usize) ?*IrValue,
1436 };
1437
1438 pub fn entry(vtable: *const VTable) InterfaceEntry {
1439 return .{ .id = id, .vtable = vtable };
1440 }
1441
1442 pub fn vtableFor(
1443 comptime getYieldOperandCount: *const fn (op: *const anyopaque) usize,
1444 comptime getYieldOperand: *const fn (op: *const anyopaque, index: usize) ?*IrValue,
1445 ) *const VTable {
1446 return &.{
1447 .getYieldOperandCount = getYieldOperandCount,
1448 .getYieldOperand = getYieldOperand,
1449 };
1450 }
1451
1452 pub fn entryFor(
1453 comptime getYieldOperandCount: *const fn (op: *const anyopaque) usize,
1454 comptime getYieldOperand: *const fn (op: *const anyopaque, index: usize) ?*IrValue,
1455 ) InterfaceEntry {
1456 return entry(vtableFor(getYieldOperandCount, getYieldOperand));
1457 }
1458 };
1459
1460 pub const HandlerEntryOpInterface = struct {
1461 pub const interface_name = "ir.interface.handler_entry";
1462
1463 pub const id: InterfaceId = base.interfaceId(interface_name);
1464
1465 pub const VTable = struct {
1466 getEffectName: *const fn (op: *const anyopaque) ?[]const u8,
1467 };
1468
1469 pub fn entry(vtable: *const VTable) InterfaceEntry {
1470 return .{ .id = id, .vtable = vtable };
1471 }
1472
1473 pub fn vtableFor(comptime getEffectName: *const fn (op: *const anyopaque) ?[]const u8) *const VTable {
1474 return &.{ .getEffectName = getEffectName };
1475 }
1476
1477 pub fn entryFor(comptime getEffectName: *const fn (op: *const anyopaque) ?[]const u8) InterfaceEntry {
1478 return entry(vtableFor(getEffectName));
1479 }
1480 };
1481
1482 pub const TranslationDialectInterface = struct {
1483 pub const interface_name = "ir.interface.dialect_translation";
1484
1485 pub const id: InterfaceId = base.interfaceId(interface_name);
1486
1487 pub const TranslationError = error{UnsupportedOperation} || std.mem.Allocator.Error;
1488
1489 pub const VTable = struct {
1490 translate: *const fn (op: *const anyopaque, ctx: *const anyopaque) TranslationError!void,
1491 };
1492
1493 pub fn entry(vtable: *const VTable) InterfaceEntry {
1494 return .{ .id = id, .vtable = vtable };
1495 }
1496 };
1497
1498 test "OperationInfo with traits" {
1499 const testing = std.testing;
1500 const allocator = testing.allocator;
1501
1502 var info = OperationInfo.init("arith.addi");
1503 defer info.deinit(allocator);
1504
1505 info.traits = .{ .is_idempotent = true, .is_commutative = true };
1506
1507 try testing.expect(info.traits.is_idempotent);
1508 try testing.expect(info.traits.is_commutative);
1509 try testing.expect(!info.traits.is_terminator);
1510 }
1511
1512 test "OperationInfo dynamic traits" {
1513 const testing = std.testing;
1514 const allocator = testing.allocator;
1515
1516 var info = OperationInfo.init("test.op");
1517 defer info.deinit(allocator);
1518
1519 const trait_a: TraitId = 100;
1520 const trait_b: TraitId = 200;
1521
1522 try info.addTrait(allocator, trait_b);
1523 try info.addTrait(allocator, trait_a);
1524
1525 try testing.expect(info.hasTraitId(trait_a));
1526 try testing.expect(info.hasTraitId(trait_b));
1527
1528 const result = info.addTrait(allocator, trait_a);
1529 try testing.expectError(error.DuplicateTrait, result);
1530 }
1531
1532 test "OperationInfo inherent attribute names" {
1533 const testing = std.testing;
1534 const allocator = testing.allocator;
1535
1536 var info = OperationInfo.init("test.op");
1537 defer info.deinit(allocator);
1538
1539 try info.addInherentAttributeName(allocator, "zeta");
1540 try info.addInherentAttributeName(allocator, "alpha");
1541
1542 const names = info.getInherentAttributeNames();
1543 try testing.expectEqual(@as(usize, 2), names.len);
1544 try testing.expectEqualStrings("alpha", names[0]);
1545 try testing.expectEqualStrings("zeta", names[1]);
1546 try testing.expect(info.hasInherentAttributeName("alpha"));
1547 try testing.expect(info.hasInherentAttributeName("zeta"));
1548 try testing.expect(!info.hasInherentAttributeName("mid"));
1549
1550 const result = info.addInherentAttributeName(allocator, "alpha");
1551 try testing.expectError(error.DuplicateInherentAttributeName, result);
1552 }
1553
1554 test "OperationInfo attribute name storage borrows exact capacities and spills transactionally" {
1555 const testing = std.testing;
1556 var failing = testing.FailingAllocator.init(testing.allocator, .{});
1557 var trait_storage: [OperationInfo.dynamic_trait_inline_capacity]TraitId = undefined;
1558 var interface_storage: [OperationInfo.interface_inline_capacity]InterfaceEntry = undefined;
1559 var inherent_storage: [2][]const u8 = undefined;
1560 var required_storage: [1][]const u8 = undefined;
1561 var info = OperationInfo.initEntryStorage(
1562 "test.op",
1563 &trait_storage,
1564 &interface_storage,
1565 &inherent_storage,
1566 &required_storage,
1567 );
1568
1569 const before = failing.alloc_index;
1570 const before_allocated = failing.allocated_bytes;
1571 try info.addInherentAttributeName(failing.allocator(), "zeta");
1572 try info.addInherentAttributeName(failing.allocator(), "alpha");
1573 try info.addRequiredAttributeName(failing.allocator(), "alpha");
1574 try testing.expectEqual(before + 3, failing.alloc_index);
1575 try testing.expectEqual(before_allocated + "zeta".len + "alpha".len * 2, failing.allocated_bytes);
1576 try testing.expectEqualStrings("alpha", info.getInherentAttributeNames()[0]);
1577 try testing.expectEqualStrings("zeta", info.getInherentAttributeNames()[1]);
1578 try testing.expectEqualStrings("alpha", info.getRequiredAttributeNames()[0]);
1579
1580 const before_duplicate = failing.alloc_index;
1581 try testing.expectError(
1582 error.DuplicateInherentAttributeName,
1583 info.addInherentAttributeName(failing.allocator(), "alpha"),
1584 );
1585 try testing.expectError(
1586 error.DuplicateRequiredAttributeName,
1587 info.addRequiredAttributeName(failing.allocator(), "alpha"),
1588 );
1589 try testing.expectEqual(before_duplicate, failing.alloc_index);
1590
1591 failing.fail_index = failing.alloc_index + 1;
1592 const before_failed_inherent_freed = failing.freed_bytes;
1593 try testing.expectError(
1594 error.OutOfMemory,
1595 info.addInherentAttributeName(failing.allocator(), "middle"),
1596 );
1597 try testing.expectEqual(@as(usize, 2), info.getInherentAttributeNames().len);
1598 try testing.expectEqual(before_failed_inherent_freed + "middle".len, failing.freed_bytes);
1599
1600 failing.fail_index = std.math.maxInt(usize);
1601 const before_inherent_spill = failing.allocated_bytes;
1602 try info.addInherentAttributeName(failing.allocator(), "middle");
1603 try testing.expectEqual(
1604 before_inherent_spill + "middle".len + 3 * @sizeOf([]const u8),
1605 failing.allocated_bytes,
1606 );
1607 try testing.expectEqualStrings("middle", info.getInherentAttributeNames()[1]);
1608
1609 failing.fail_index = failing.alloc_index + 1;
1610 const before_failed_required_freed = failing.freed_bytes;
1611 try testing.expectError(
1612 error.OutOfMemory,
1613 info.addRequiredAttributeName(failing.allocator(), "zeta"),
1614 );
1615 try testing.expectEqual(@as(usize, 1), info.getRequiredAttributeNames().len);
1616 try testing.expectEqual(before_failed_required_freed + "zeta".len, failing.freed_bytes);
1617
1618 failing.fail_index = std.math.maxInt(usize);
1619 const before_required_spill = failing.allocated_bytes;
1620 try info.addRequiredAttributeName(failing.allocator(), "zeta");
1621 try testing.expectEqual(
1622 before_required_spill + "zeta".len + 2 * @sizeOf([]const u8),
1623 failing.allocated_bytes,
1624 );
1625 try testing.expectEqualStrings("alpha", info.getRequiredAttributeNames()[0]);
1626 try testing.expectEqualStrings("zeta", info.getRequiredAttributeNames()[1]);
1627
1628 info.deinit(failing.allocator());
1629 try testing.expectEqual(failing.allocated_bytes, failing.freed_bytes);
1630 }
1631
1632 test "CountRange checks exact and bounded counts" {
1633 const testing = std.testing;
1634
1635 try testing.expect(CountRange.exactly(2).allows(2));
1636 try testing.expect(!CountRange.exactly(2).allows(1));
1637 try testing.expect(CountRange.atLeast(2).allows(3));
1638 try testing.expect(!CountRange.atLeast(2).allows(1));
1639 try testing.expect(CountRange.atMost(2).allows(1));
1640 try testing.expect(!CountRange.atMost(2).allows(3));
1641 try testing.expect(CountRange.between(1, 3).allows(2));
1642 try testing.expect(!CountRange.between(1, 3).allows(4));
1643 }
1644
1645 test "OperationInfo shape registration is idempotent and rejects conflicts" {
1646 const testing = std.testing;
1647
1648 var info = OperationInfo.init("shape.test");
1649
1650 const shape = OperationShape{
1651 .operands = CountRange.exactly(2),
1652 .results = CountRange.exactly(1),
1653 };
1654 try info.setShape(shape);
1655 try info.setShape(shape);
1656
1657 const conflict = OperationShape{
1658 .operands = CountRange.exactly(1),
1659 .results = CountRange.exactly(1),
1660 };
1661 try testing.expectError(error.ConflictingOperationShape, info.setShape(conflict));
1662 }
1663
1664 test "OperationInfo rejects incomplete properties codecs" {
1665 const testing = std.testing;
1666 const complete = singleAttributePropertiesModel("test.properties", "value");
1667
1668 var getter_only = complete;
1669 getter_only.setPropertiesFromAttr = null;
1670 var getter_info = OperationInfo.init("test.getter_only");
1671 try testing.expectError(
1672 error.IncompleteOperationPropertiesCodec,
1673 getter_info.setPropertiesModel(getter_only),
1674 );
1675
1676 var setter_only = complete;
1677 setter_only.getPropertiesAsAttr = null;
1678 var setter_info = OperationInfo.init("test.setter_only");
1679 try testing.expectError(
1680 error.IncompleteOperationPropertiesCodec,
1681 setter_info.setPropertiesModel(setter_only),
1682 );
1683
1684 var complete_info = OperationInfo.init("test.complete");
1685 try complete_info.setPropertiesModel(complete);
1686 try testing.expect(complete_info.hasPropertiesModel());
1687 }
1688
1689 test "OperationInfo segment registration is idempotent and rejects conflicts" {
1690 const testing = std.testing;
1691 const allocator = testing.allocator;
1692
1693 var info = OperationInfo.init("segments.test");
1694 defer info.deinit(allocator);
1695
1696 const operand_segments = OperationSegmentSpec{
1697 .attribute_name = "operand_segment_sizes",
1698 .segments = &.{ CountRange.exactly(1), CountRange.atMost(1) },
1699 };
1700 try info.setOperandSegments(allocator, operand_segments);
1701 try info.setOperandSegments(allocator, operand_segments);
1702
1703 const registered = info.getOperandSegments() orelse return error.TestExpectedSegmentSpec;
1704 try testing.expect(registered.eql(operand_segments));
1705 try testing.expectEqual(@as(usize, 2), registered.segments.len);
1706
1707 const conflict = OperationSegmentSpec{
1708 .attribute_name = "operand_segment_sizes",
1709 .segments = &.{ CountRange.exactly(1), CountRange.exactly(1) },
1710 };
1711 try testing.expectError(error.ConflictingOperandSegments, info.setOperandSegments(allocator, conflict));
1712
1713 const result_segments = OperationSegmentSpec{
1714 .attribute_name = "result_segment_sizes",
1715 .segments = &.{CountRange.atLeast(1)},
1716 };
1717 try info.setResultSegments(allocator, result_segments);
1718 try testing.expect((info.getResultSegments() orelse return error.TestExpectedSegmentSpec).eql(result_segments));
1719 }
1720
1721 test "OperationRegistry basic operations" {
1722 const testing = std.testing;
1723 const allocator = testing.allocator;
1724
1725 var registry = OperationRegistry.init(allocator);
1726 defer registry.deinit();
1727
1728 try testing.expectEqual(@as(usize, 0), registry.count());
1729 try testing.expectEqual(@as(?*OperationInfo, null), registry.lookup("arith.addi"));
1730
1731 const info1 = try registry.getOrCreate("arith.addi");
1732 try testing.expectEqual(@as(usize, 1), registry.count());
1733 try testing.expectEqualStrings("arith.addi", info1.name);
1734
1735 const info2 = try registry.getOrCreate("arith.addi");
1736 try testing.expectEqual(info1, info2);
1737 try testing.expectEqual(@as(usize, 1), registry.count());
1738
1739 const found = registry.lookup("arith.addi");
1740 try testing.expectEqual(info1, found.?);
1741 }
1742
1743 test "OperationRegistry co-owns each header metadata storage and name in one allocation" {
1744 const testing = std.testing;
1745 var failing = testing.FailingAllocator.init(testing.allocator, .{});
1746 var registry = OperationRegistry.init(failing.allocator());
1747 defer registry.deinit();
1748 try registry.ops.ensureTotalCapacity(failing.allocator(), 1);
1749
1750 const before = failing.alloc_index;
1751 const before_allocated = failing.allocated_bytes;
1752 const before_freed = failing.freed_bytes;
1753 const expected = std.math.add(usize, before, 1) catch unreachable;
1754 const created = try registry.getOrCreateTrackedWithAttributeNameCapacity(
1755 "test.shared",
1756 .{ .inherent = 2, .required = 1 },
1757 );
1758 try testing.expect(created.created);
1759 try testing.expectEqual(expected, failing.alloc_index);
1760 try testing.expectEqual(@as(usize, 448), @sizeOf(OperationInfo));
1761 const trait_inline_bytes = OperationInfo.dynamic_trait_inline_capacity * @sizeOf(TraitId);
1762 const interface_inline_bytes = OperationInfo.interface_inline_capacity * @sizeOf(InterfaceEntry);
1763 const inherent_attribute_bytes = 2 * @sizeOf([]const u8);
1764 const required_attribute_bytes = @sizeOf([]const u8);
1765 const inherent_attribute_address = std.math.add(
1766 usize,
1767 @intFromPtr(created.info),
1768 @sizeOf(OperationInfo) + trait_inline_bytes + interface_inline_bytes,
1769 ) catch unreachable;
1770 try testing.expectEqual(
1771 inherent_attribute_address,
1772 @intFromPtr(created.info.inherent_attribute_names.values().ptr),
1773 );
1774 const required_attribute_address = std.math.add(
1775 usize,
1776 inherent_attribute_address,
1777 inherent_attribute_bytes,
1778 ) catch unreachable;
1779 try testing.expectEqual(
1780 required_attribute_address,
1781 @intFromPtr(created.info.required_attribute_names.values().ptr),
1782 );
1783 const name_address = std.math.add(
1784 usize,
1785 @intFromPtr(created.info),
1786 @sizeOf(OperationInfo) + trait_inline_bytes + interface_inline_bytes +
1787 inherent_attribute_bytes + required_attribute_bytes,
1788 ) catch unreachable;
1789 try testing.expectEqual(name_address, @intFromPtr(created.info.name.ptr));
1790 try created.info.addTrait(failing.allocator(), 40);
1791 try created.info.addTrait(failing.allocator(), 10);
1792 try created.info.addTrait(failing.allocator(), 30);
1793 try created.info.addTrait(failing.allocator(), 20);
1794 try testing.expectEqual(expected, failing.alloc_index);
1795 try testing.expectEqualSlices(TraitId, &.{ 10, 20, 30, 40 }, created.info.getDynamicTraitIds());
1796 const vtable_a: u8 = 1;
1797 const vtable_b: u8 = 2;
1798 try created.info.addInterface(failing.allocator(), .{ .id = 20, .vtable = &vtable_b });
1799 try created.info.addInterface(failing.allocator(), .{ .id = 10, .vtable = &vtable_a });
1800 try testing.expectEqual(expected, failing.alloc_index);
1801 try testing.expectEqual(@as(*const u8, &vtable_a), @as(*const u8, @ptrCast(created.info.getInterface(10).?)));
1802 try testing.expectEqual(@as(*const u8, &vtable_b), @as(*const u8, @ptrCast(created.info.getInterface(20).?)));
1803
1804 const duplicate = try registry.getOrCreateTracked("test.shared");
1805 try testing.expect(!duplicate.created);
1806 try testing.expectEqual(created.info, duplicate.info);
1807 try testing.expectEqual(expected, failing.alloc_index);
1808 const entry_bytes = @sizeOf(OperationInfo) + trait_inline_bytes + interface_inline_bytes +
1809 inherent_attribute_bytes + required_attribute_bytes + "test.shared".len;
1810 try testing.expectEqual(
1811 std.math.add(usize, before_allocated, entry_bytes) catch unreachable,
1812 failing.allocated_bytes,
1813 );
1814 try testing.expect(registry.removeOperation("test.shared"));
1815 try testing.expectEqual(
1816 std.math.add(usize, before_freed, entry_bytes) catch unreachable,
1817 failing.freed_bytes,
1818 );
1819 try testing.expectEqual(@as(usize, 0), registry.count());
1820 }
1821
1822 test "OperationRegistry cleans a shared entry when map growth fails" {
1823 const testing = std.testing;
1824 var failing = testing.FailingAllocator.init(
1825 testing.allocator,
1826 .{ .fail_index = 1 },
1827 );
1828 var registry = OperationRegistry.init(failing.allocator());
1829 defer registry.deinit();
1830
1831 try testing.expectError(error.OutOfMemory, registry.getOrCreate("test.retry"));
1832 try testing.expectEqual(@as(usize, 0), registry.count());
1833
1834 failing.fail_index = std.math.maxInt(usize);
1835 const info = try registry.getOrCreate("test.retry");
1836 try testing.expectEqualStrings("test.retry", info.name);
1837 try testing.expectEqual(@as(usize, 1), registry.count());
1838 }
1839
1840 test "OperationRegistry batches exact variable entries with stable mixed ownership" {
1841 const testing = std.testing;
1842 const BatchSpec = struct {
1843 name: []const u8,
1844 inherent_attribute_names: []const []const u8 = &.{},
1845 required_attribute_names: []const []const u8 = &.{},
1846 };
1847 const specs = [_]BatchSpec{
1848 .{
1849 .name = "test.batch_first",
1850 .inherent_attribute_names = &.{ "alpha", "gamma" },
1851 .required_attribute_names = &.{"gamma"},
1852 },
1853 .{ .name = "test.batch_second" },
1854 };
1855
1856 var failing = testing.FailingAllocator.init(testing.allocator, .{});
1857 var registry = OperationRegistry.init(failing.allocator());
1858 defer registry.deinit();
1859
1860 const ordinary = try registry.getOrCreate("test.ordinary");
1861 try registry.ops.ensureTotalCapacity(failing.allocator(), 3);
1862
1863 var expected_capacity: OperationRegistry.BatchEntryStorage.Capacity = .{};
1864 for (specs) |spec| {
1865 try expected_capacity.add(
1866 spec.name.len,
1867 spec.inherent_attribute_names.len,
1868 spec.required_attribute_names.len,
1869 );
1870 }
1871 const before = failing.alloc_index;
1872 const before_allocated = failing.allocated_bytes;
1873 try registry.getOrCreateOperationBatch(&specs);
1874 try testing.expectEqual(before + 1, failing.alloc_index);
1875 try testing.expectEqual(
1876 before_allocated + expected_capacity.total_bytes,
1877 failing.allocated_bytes,
1878 );
1879 try testing.expectEqual(@as(usize, 3), registry.count());
1880
1881 const first = registry.lookup(specs[0].name) orelse return error.OperationMissing;
1882 const second = registry.lookup(specs[1].name) orelse return error.OperationMissing;
1883 const first_region = registry.batch_regions orelse return error.BatchRegionMissing;
1884 try testing.expect(OperationRegistry.BatchEntryStorage.contains(first_region, first));
1885 try testing.expect(OperationRegistry.BatchEntryStorage.contains(first_region, second));
1886 try testing.expect(!OperationRegistry.BatchEntryStorage.contains(first_region, ordinary));
1887 try testing.expect(first.name.ptr != specs[0].name.ptr);
1888 try testing.expect(second.name.ptr != specs[1].name.ptr);
1889
1890 const before_attribute_names = failing.alloc_index;
1891 try first.addInherentAttributeName(failing.allocator(), "alpha");
1892 try first.addInherentAttributeName(failing.allocator(), "gamma");
1893 try first.addRequiredAttributeName(failing.allocator(), "gamma");
1894 try testing.expectEqual(before_attribute_names + 3, failing.alloc_index);
1895 try testing.expectEqual(@as(usize, 2), first.getInherentAttributeNames().len);
1896 try testing.expectEqualStrings("alpha", first.getInherentAttributeNames()[0]);
1897 try testing.expectEqualStrings("gamma", first.getInherentAttributeNames()[1]);
1898 try testing.expectEqual(@as(usize, 1), first.getRequiredAttributeNames().len);
1899 try testing.expectEqualStrings("gamma", first.getRequiredAttributeNames()[0]);
1900 for ([_]TraitId{ 10, 20, 30, 40, 50 }) |trait_id| {
1901 try first.addTrait(failing.allocator(), trait_id);
1902 }
1903 const vtables = [_]u8{ 1, 2, 3 };
1904 for (&vtables, 0..) |*vtable, index| {
1905 try first.addInterface(failing.allocator(), .{
1906 .id = @intCast(100 + index),
1907 .vtable = vtable,
1908 });
1909 }
1910
1911 const more_specs = [_]BatchSpec{.{ .name = "test.batch_third" }};
1912 try registry.getOrCreateOperationBatch(&more_specs);
1913 try testing.expectEqual(first, registry.lookup(specs[0].name).?);
1914 try testing.expectEqual(second, registry.lookup(specs[1].name).?);
1915
1916 try testing.expect(registry.removeOperation(specs[0].name));
1917 try testing.expect(registry.batch_regions != null);
1918 try testing.expect(registry.removeOperation(specs[1].name));
1919 try testing.expect(registry.batch_regions != first_region);
1920 try testing.expectEqual(ordinary, registry.lookup("test.ordinary").?);
1921 try testing.expect(registry.removeOperation("test.ordinary"));
1922 }
1923
1924 test "OperationRegistry batch preserves duplicate and existing entry semantics" {
1925 const testing = std.testing;
1926 const BatchSpec = struct {
1927 name: []const u8,
1928 inherent_attribute_names: []const []const u8 = &.{},
1929 required_attribute_names: []const []const u8 = &.{},
1930 };
1931 const specs = [_]BatchSpec{
1932 .{ .name = "test.existing" },
1933 .{ .name = "test.duplicate" },
1934 .{ .name = "test.duplicate" },
1935 };
1936
1937 var failing = testing.FailingAllocator.init(testing.allocator, .{});
1938 var registry = OperationRegistry.init(failing.allocator());
1939 defer registry.deinit();
1940
1941 const existing = try registry.getOrCreate(specs[0].name);
1942 try registry.getOrCreateOperationBatch(&specs);
1943 const duplicate = registry.lookup(specs[1].name) orelse return error.OperationMissing;
1944 try testing.expectEqual(@as(usize, 2), registry.count());
1945 try testing.expectEqual(existing, registry.lookup(specs[0].name).?);
1946
1947 const before_retry = failing.alloc_index;
1948 try registry.getOrCreateOperationBatch(&specs);
1949 try testing.expectEqual(before_retry, failing.alloc_index);
1950 try testing.expectEqual(duplicate, registry.lookup(specs[1].name).?);
1951 }
1952
1953 test "OperationRegistry batch cleans region and retries every outer allocation failure" {
1954 const testing = std.testing;
1955 const BatchSpec = struct {
1956 name: []const u8,
1957 inherent_attribute_names: []const []const u8 = &.{},
1958 required_attribute_names: []const []const u8 = &.{},
1959 };
1960 const specs = [_]BatchSpec{
1961 .{ .name = "test.retry_first" },
1962 .{ .name = "test.retry_second" },
1963 };
1964
1965 for ([_]usize{ 0, 1 }) |fail_index| {
1966 var failing = testing.FailingAllocator.init(
1967 testing.allocator,
1968 .{ .fail_index = fail_index },
1969 );
1970 var registry = OperationRegistry.init(failing.allocator());
1971 defer registry.deinit();
1972
1973 try testing.expectError(
1974 error.OutOfMemory,
1975 registry.getOrCreateOperationBatch(&specs),
1976 );
1977 try testing.expectEqual(@as(usize, 0), registry.count());
1978 try testing.expectEqual(@as(?*OperationRegistry.BatchEntryStorage.RegionOwner, null), registry.batch_regions);
1979
1980 failing.fail_index = std.math.maxInt(usize);
1981 try registry.getOrCreateOperationBatch(&specs);
1982 try testing.expectEqual(@as(usize, 2), registry.count());
1983 try testing.expect(registry.batch_regions != null);
1984 }
1985 }
1986
1987 test "OperationRegistry registerOperation merges traits" {
1988 const testing = std.testing;
1989 const allocator = testing.allocator;
1990
1991 var registry = OperationRegistry.init(allocator);
1992 defer registry.deinit();
1993
1994 const info1 = try registry.registerOperation("arith.addi", .{ .is_idempotent = true });
1995 try testing.expect(info1.traits.is_idempotent);
1996 try testing.expect(!info1.traits.is_commutative);
1997
1998 const info2 = try registry.registerOperation("arith.addi", .{ .is_commutative = true });
1999 try testing.expectEqual(info1, info2);
2000 try testing.expect(info2.traits.is_idempotent);
2001 try testing.expect(info2.traits.is_commutative);
2002 }
2003
2004 test "OperationRegistry registerInterface" {
2005 const testing = std.testing;
2006 const allocator = testing.allocator;
2007
2008 var registry = OperationRegistry.init(allocator);
2009 defer registry.deinit();
2010
2011 const iface_id = base.interfaceId("ir.interface.symbol_op");
2012 const vtable: u8 = 42;
2013
2014 try registry.registerInterface("arith.addi", .{ .id = iface_id, .vtable = &vtable });
2015
2016 const info = registry.lookup("arith.addi").?;
2017 try testing.expect(info.hasInterface(iface_id));
2018 try testing.expectEqual(&vtable, @as(*const u8, @ptrCast(info.getInterface(iface_id).?)));
2019
2020 const result = registry.registerInterface("arith.addi", .{ .id = iface_id, .vtable = &vtable });
2021 try testing.expectError(error.DuplicateInterface, result);
2022 }
2023
2024 test "OperationRegistry registerInherentAttributeName" {
2025 const testing = std.testing;
2026 const allocator = testing.allocator;
2027
2028 var registry = OperationRegistry.init(allocator);
2029 defer registry.deinit();
2030
2031 try registry.registerInherentAttributeName("arith.constant", "value");
2032 try registry.registerInherentAttributeName("arith.cmp", "predicate");
2033
2034 const constant = registry.lookup("arith.constant").?;
2035 try testing.expect(constant.hasInherentAttributeName("value"));
2036 try testing.expect(!constant.hasInherentAttributeName("predicate"));
2037
2038 const result = registry.registerInherentAttributeName("arith.constant", "value");
2039 try testing.expectError(error.DuplicateInherentAttributeName, result);
2040 }
2041
2042 test "OperationRegistry registerOrReplaceInterface" {
2043 const testing = std.testing;
2044 const allocator = testing.allocator;
2045
2046 var registry = OperationRegistry.init(allocator);
2047 defer registry.deinit();
2048
2049 const iface_id = base.interfaceId("ir.interface.symbol_op");
2050 const vtable1: u8 = 1;
2051 const vtable2: u8 = 2;
2052
2053 try registry.registerOrReplaceInterface("arith.addi", .{ .id = iface_id, .vtable = &vtable1 });
2054 const info = registry.lookup("arith.addi").?;
2055 try testing.expectEqual(&vtable1, @as(*const u8, @ptrCast(info.getInterface(iface_id).?)));
2056
2057 try registry.registerOrReplaceInterface("arith.addi", .{ .id = iface_id, .vtable = &vtable2 });
2058 try testing.expectEqual(&vtable2, @as(*const u8, @ptrCast(info.getInterface(iface_id).?)));
2059 }
2060
2061 test "OperationRegistry multiple operations" {
2062 const testing = std.testing;
2063 const allocator = testing.allocator;
2064
2065 var registry = OperationRegistry.init(allocator);
2066 defer registry.deinit();
2067
2068 const addi = try registry.registerOperation(
2069 "arith.addi",
2070 .{ .is_idempotent = true, .is_commutative = true },
2071 );
2072 const subi = try registry.registerOperation("arith.subi", .{ .is_idempotent = true });
2073 const store = try registry.registerOperation("memref.store", .{ .is_terminator = true });
2074
2075 try testing.expectEqual(@as(usize, 3), registry.count());
2076
2077 try testing.expect(addi != subi);
2078 try testing.expect(subi != store);
2079
2080 try testing.expect(addi.traits.is_commutative);
2081 try testing.expect(!subi.traits.is_commutative);
2082 try testing.expect(store.traits.is_terminator);
2083
2084 try testing.expectEqual(addi, registry.lookup("arith.addi").?);
2085 try testing.expectEqual(subi, registry.lookup("arith.subi").?);
2086 try testing.expectEqual(store, registry.lookup("memref.store").?);
2087 }
2088
2089 test "OperationRegistry stable pointers" {
2090 const testing = std.testing;
2091 const allocator = testing.allocator;
2092
2093 var registry = OperationRegistry.init(allocator);
2094 defer registry.deinit();
2095
2096 const info1 = try registry.getOrCreate("op1");
2097 const ptr1 = info1;
2098
2099 for (0..100) |i| {
2100 var buf: [32]u8 = undefined;
2101 var pos: usize = 0;
2102 pos = try format.appendFmt(buf[0..], pos, "test.op{d}", .{i});
2103 const name = buf[0..pos];
2104 _ = try registry.getOrCreate(name);
2105 }
2106
2107 try testing.expectEqual(ptr1, registry.lookup("op1").?);
2108 try testing.expectEqualStrings("op1", ptr1.name);
2109 }
2110
2111 test "SymbolOpInterface stable ID" {
2112 const testing = std.testing;
2113
2114 const id1 = SymbolOpInterface.id;
2115 const id2 = base.interfaceId(SymbolOpInterface.interface_name);
2116 try testing.expectEqual(id1, id2);
2117
2118 try testing.expect(SymbolOpInterface.id != Evaluatable.id);
2119 }
2120
2121 test "CallOpInterface stable ID" {
2122 const testing = std.testing;
2123
2124 const id1 = CallOpInterface.id;
2125 const id2 = base.interfaceId(CallOpInterface.interface_name);
2126 try testing.expectEqual(id1, id2);
2127
2128 try testing.expect(CallOpInterface.id != SymbolOpInterface.id);
2129 }
2130
2131 test "FunctionOpInterface stable ID" {
2132 const testing = std.testing;
2133
2134 const id1 = FunctionOpInterface.id;
2135 const id2 = base.interfaceId(FunctionOpInterface.interface_name);
2136 try testing.expectEqual(id1, id2);
2137
2138 try testing.expect(FunctionOpInterface.id != CallOpInterface.id);
2139 }
2140
2141 test "RegionKindInterface stable ID" {
2142 const testing = std.testing;
2143
2144 const id1 = RegionKindInterface.id;
2145 const id2 = base.interfaceId(RegionKindInterface.interface_name);
2146 try testing.expectEqual(id1, id2);
2147
2148 try testing.expect(RegionKindInterface.id != CallOpInterface.id);
2149 }
2150
2151 test "RegionKindInterface all graph entry" {
2152 const testing = std.testing;
2153
2154 const entry = RegionKindInterface.allGraphRegionsEntry();
2155 const vtable: *const RegionKindInterface.VTable = @ptrCast(@alignCast(entry.vtable));
2156
2157 try testing.expectEqual(RegionKindInterface.id, entry.id);
2158 try testing.expectEqual(RegionKind.graph, vtable.getRegionKind(undefined, 0));
2159 try testing.expect(!vtable.hasSSADominance(undefined, 0));
2160 }
2161
2162 test "SymbolUserOpInterface stable ID" {
2163 const testing = std.testing;
2164
2165 const id1 = SymbolUserOpInterface.id;
2166 const id2 = base.interfaceId(SymbolUserOpInterface.interface_name);
2167
2168 try testing.expectEqual(id1, id2);
2169 try testing.expect(SymbolUserOpInterface.id != SymbolOpInterface.id);
2170 try testing.expect(SymbolUserOpInterface.id != CallOpInterface.id);
2171 }
2172
2173 test "ControlFlowInterface stable ID" {
2174 const testing = std.testing;
2175
2176 const id1 = ControlFlowInterface.id;
2177 const id2 = base.interfaceId(ControlFlowInterface.interface_name);
2178 try testing.expectEqual(id1, id2);
2179
2180 try testing.expect(ControlFlowInterface.id != CallOpInterface.id);
2181 }
2182
2183 test "InferTypeOpInterface stable ID" {
2184 const testing = std.testing;
2185
2186 const id1 = InferTypeOpInterface.id;
2187 const id2 = base.interfaceId(InferTypeOpInterface.interface_name);
2188 try testing.expectEqual(id1, id2);
2189
2190 try testing.expect(InferTypeOpInterface.id != Evaluatable.id);
2191 }
2192
2193 test "Evaluatable stable ID" {
2194 const testing = std.testing;
2195
2196 const id1 = Evaluatable.id;
2197 const id2 = base.interfaceId(Evaluatable.interface_name);
2198 try testing.expectEqual(id1, id2);
2199 }
2200
2201 test "TranslationDialectInterface stable ID" {
2202 const testing = std.testing;
2203
2204 const id1 = TranslationDialectInterface.id;
2205 const id2 = base.interfaceId(TranslationDialectInterface.interface_name);
2206 try testing.expectEqual(id1, id2);
2207 }
2208
2209 test "YieldOpInterface stable ID" {
2210 const testing = std.testing;
2211
2212 const id1 = YieldOpInterface.id;
2213 const id2 = base.interfaceId(YieldOpInterface.interface_name);
2214 try testing.expectEqual(id1, id2);
2215
2216 try testing.expect(YieldOpInterface.id != Evaluatable.id);
2217 }
2218
2219 test "HandlerEntryOpInterface stable ID" {
2220 const testing = std.testing;
2221
2222 const id1 = HandlerEntryOpInterface.id;
2223 const id2 = base.interfaceId(HandlerEntryOpInterface.interface_name);
2224 try testing.expectEqual(id1, id2);
2225
2226 try testing.expect(HandlerEntryOpInterface.id != YieldOpInterface.id);
2227 }