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 = &region.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 }