lib/choir/src/core/dialects/spec.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const core = @import("../root.zig");
   3 const registry = @import("registry.zig");
   4 const Type = core.Type;
   5 const IrValue = core.Value;
   6 const Location = core.Location;
   7 const Operation = core.Operation;
   8 const Block = core.Block;
   9 const Region = core.Region;
  10 const OperationBuilder = core.OperationBuilder;
  11 const Attribute = core.Attribute;
  12 const interfaces = core.interfaces;
  13 const core_traits = core.traits;
  14 const VerifyOpInterface = core.VerifyOpInterface;
  15 const VerifyRegionOpInterface = core.VerifyRegionOpInterface;
  16 const Context = core.Context;
  17 const Dialect = registry.Dialect;
  18 const OpInterfaceFallbackEntry = registry.OpInterfaceFallbackEntry;
  19 const TypeInterfaceFallbackEntry = registry.TypeInterfaceFallbackEntry;
  20 
  21 pub const OperationTraitSpec = struct {
  22     id: interfaces.TraitId,
  23     entry: ?interfaces.TraitEntry = null,
  24     traits: interfaces.OperationTraits = .{},
  25 };
  26 
  27 pub const AttributeStorage = union(enum) {
  28     any,
  29     i64,
  30     bool,
  31     string,
  32     dialect: []const u8,
  33 };
  34 
  35 pub const AttributeSpec = struct {
  36     name: []const u8,
  37     storage: AttributeStorage = .any,
  38 };
  39 
  40 pub const OperationSpec = struct {
  41     name: []const u8,
  42     traits: interfaces.OperationTraits = .{},
  43     shape: interfaces.OperationShape = .{},
  44     operand_names: []const []const u8 = &.{},
  45     result_names: []const []const u8 = &.{},
  46     region_names: []const []const u8 = &.{},
  47     successor_names: []const []const u8 = &.{},
  48     inherent_attribute_names: []const []const u8 = &.{},
  49     required_attribute_names: []const []const u8 = &.{},
  50     attribute_specs: []const AttributeSpec = &.{},
  51     operand_segments: ?interfaces.OperationSegmentSpec = null,
  52     result_segments: ?interfaces.OperationSegmentSpec = null,
  53     operand_type_constraints: []const interfaces.OperationTypeConstraint = &.{},
  54     result_type_constraints: []const interfaces.OperationTypeConstraint = &.{},
  55     properties_model: ?interfaces.OperationPropertiesModel = null,
  56     interfaces: []const interfaces.InterfaceEntry = &.{},
  57     dynamic_traits: []const OperationTraitSpec = &.{},
  58 };
  59 
  60 const OperationLayout = struct {
  61     operands: []const []const u8 = &.{},
  62     results: []const []const u8 = &.{},
  63     regions: []const []const u8 = &.{},
  64     successors: []const []const u8 = &.{},
  65 };
  66 
  67 pub fn operand(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) *IrValue {
  68     return op.getOperand(operandIndex(spec, component_name)).?;
  69 }
  70 
  71 pub fn optionalOperand(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?*IrValue {
  72     return op.getOperand(operandIndex(spec, component_name));
  73 }
  74 
  75 pub fn result(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) *IrValue {
  76     return op.getResult(resultIndex(spec, component_name)).?;
  77 }
  78 
  79 pub fn optionalResult(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?*IrValue {
  80     return op.getResult(resultIndex(spec, component_name));
  81 }
  82 
  83 pub fn region(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) *Region {
  84     return op.getRegion(regionIndex(spec, component_name)).?;
  85 }
  86 
  87 pub fn optionalRegion(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?*Region {
  88     return op.getRegion(regionIndex(spec, component_name));
  89 }
  90 
  91 pub fn successor(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) *Block {
  92     return op.getSuccessor(successorIndex(spec, component_name)).?;
  93 }
  94 
  95 pub fn optionalSuccessor(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?*Block {
  96     return op.getSuccessor(successorIndex(spec, component_name));
  97 }
  98 
  99 pub fn setOperandSegmentSizes(comptime spec: OperationSpec, op: *Operation, sizes: []const usize) !void {
 100     const segment_spec = spec.operand_segments orelse @compileError("operation has no operand segments: " ++ spec.name);
 101     try setSegmentSizes(segment_spec, op, sizes);
 102 }
 103 
 104 pub fn setResultSegmentSizes(comptime spec: OperationSpec, op: *Operation, sizes: []const usize) !void {
 105     const segment_spec = spec.result_segments orelse @compileError("operation has no result segments: " ++ spec.name);
 106     try setSegmentSizes(segment_spec, op, sizes);
 107 }
 108 
 109 pub fn setSegmentSizes(comptime segment_spec: interfaces.OperationSegmentSpec, op: *Operation, sizes: []const usize) !void {
 110     try op.setAttr(segment_spec.attribute_name, try segmentSizeAttribute(segment_spec, op.getContext(), sizes));
 111 }
 112 
 113 pub fn segmentSizeAttribute(comptime segment_spec: interfaces.OperationSegmentSpec, ctx: *Context, sizes: []const usize) !Attribute {
 114     if (sizes.len != segment_spec.segments.len) return error.OperationSegmentSizeCountMismatch;
 115     var attrs: [segment_spec.segments.len]Attribute = undefined;
 116     for (sizes, 0..) |size, index| {
 117         if (!segment_spec.segments[index].allows(size)) return error.OperationSegmentSizeOutOfRange;
 118         const value = std.math.cast(i64, size) orelse return error.OperationSegmentSizeOverflow;
 119         attrs[index] = try ctx.getI64Attr(value);
 120     }
 121     return ctx.getArrayAttr(attrs[0..]);
 122 }
 123 
 124 pub fn operandSegmentValues(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?[]const *IrValue {
 125     const segment_spec = spec.operand_segments orelse @compileError("operation has no operand segments: " ++ spec.name);
 126     const index = comptime operandIndex(spec, component_name);
 127     const bounds = segmentBounds(segment_spec, op, index) orelse return null;
 128     if (bounds.offset > op.operand_values.len) return null;
 129     if (bounds.size > op.operand_values.len - bounds.offset) return null;
 130     return op.operand_values[bounds.offset .. bounds.offset + bounds.size];
 131 }
 132 
 133 pub fn operandSegmentValue(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?*IrValue {
 134     const values = operandSegmentValues(spec, op, component_name) orelse return null;
 135     if (values.len != 1) return null;
 136     return values[0];
 137 }
 138 
 139 pub fn resultSegmentValues(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?[]IrValue {
 140     const segment_spec = spec.result_segments orelse @compileError("operation has no result segments: " ++ spec.name);
 141     const index = comptime resultIndex(spec, component_name);
 142     const bounds = segmentBounds(segment_spec, op, index) orelse return null;
 143     if (bounds.offset > op.results.items.len) return null;
 144     if (bounds.size > op.results.items.len - bounds.offset) return null;
 145     return op.results.items[bounds.offset .. bounds.offset + bounds.size];
 146 }
 147 
 148 pub fn resultSegmentValue(comptime spec: OperationSpec, op: *Operation, comptime component_name: []const u8) ?*IrValue {
 149     const values = resultSegmentValues(spec, op, component_name) orelse return null;
 150     if (values.len != 1) return null;
 151     return &values[0];
 152 }
 153 
 154 pub fn operandIndex(comptime spec: OperationSpec, comptime component_name: []const u8) usize {
 155     return componentIndex(spec.operand_names, "operand", component_name);
 156 }
 157 
 158 pub fn resultIndex(comptime spec: OperationSpec, comptime component_name: []const u8) usize {
 159     return componentIndex(spec.result_names, "result", component_name);
 160 }
 161 
 162 pub fn regionIndex(comptime spec: OperationSpec, comptime component_name: []const u8) usize {
 163     return componentIndex(spec.region_names, "region", component_name);
 164 }
 165 
 166 pub fn successorIndex(comptime spec: OperationSpec, comptime component_name: []const u8) usize {
 167     return componentIndex(spec.successor_names, "successor", component_name);
 168 }
 169 
 170 fn componentIndex(comptime names: []const []const u8, comptime kind: []const u8, comptime component_name: []const u8) usize {
 171     if (component_name.len == 0) @compileError("operation " ++ kind ++ " name cannot be empty");
 172     const index = comptime lookup: {
 173         for (names, 0..) |name, index| {
 174             if (std.mem.eql(u8, name, component_name)) break :lookup index;
 175         }
 176         @compileError("unknown operation " ++ kind ++ " name: " ++ component_name);
 177     };
 178     return index;
 179 }
 180 
 181 const SegmentBounds = struct {
 182     offset: usize,
 183     size: usize,
 184 };
 185 
 186 fn segmentBounds(comptime segment_spec: interfaces.OperationSegmentSpec, op: *const Operation, index: usize) ?SegmentBounds {
 187     const size = segment_spec.size(op, index) orelse return null;
 188     const offset = segment_spec.offset(op, index) orelse return null;
 189     return .{
 190         .offset = offset,
 191         .size = size,
 192     };
 193 }
 194 
 195 fn tryComponentNames(comptime values: anytype, comptime field_name: []const u8) void {
 196     _ = componentNamesFromValue(values, field_name);
 197 }
 198 
 199 fn componentNamesFromValue(comptime values: anytype, comptime field_name: []const u8) []const []const u8 {
 200     const Values = @TypeOf(values);
 201     if (Values == interfaces.CountRange) return &.{};
 202     return switch (@typeInfo(Values)) {
 203         .comptime_int, .int => &.{},
 204         .pointer => |pointer_info| switch (pointer_info.size) {
 205             .one => switch (@typeInfo(pointer_info.child)) {
 206                 .array => |array_info| blk: {
 207                     if (array_info.child == u8) {
 208                         @compileError("operation " ++ field_name ++ " field must use a list of component names, not one component name");
 209                     }
 210                     break :blk componentNamesFromArray(values.*, field_name);
 211                 },
 212                 .@"struct" => |struct_info| blk: {
 213                     if (struct_info.field_names.len == 0) return &.{};
 214                     break :blk componentNamesFromTuple(values.*, field_name);
 215                 },
 216                 else => &.{},
 217             },
 218             .slice => componentNamesFromSlice(values, field_name),
 219             else => &.{},
 220         },
 221         .array => |array_info| blk: {
 222             if (array_info.child == u8) {
 223                 @compileError("operation " ++ field_name ++ " field must use a list of component names, not one component name");
 224             }
 225             break :blk componentNamesFromArray(values, field_name);
 226         },
 227         .@"struct" => |struct_info| blk: {
 228             if (struct_info.field_names.len == 0) return &.{};
 229             break :blk componentNamesFromTuple(values, field_name);
 230         },
 231         else => &.{},
 232     };
 233 }
 234 
 235 fn componentNamesFromSlice(comptime values: anytype, comptime field_name: []const u8) []const []const u8 {
 236     const names = comptime blk: {
 237         var out: [values.len][]const u8 = undefined;
 238         for (values, 0..) |value, index| {
 239             out[index] = componentName(value, field_name);
 240         }
 241         validateComponentNames(out[0..], field_name);
 242         break :blk out;
 243     };
 244     return &names;
 245 }
 246 
 247 fn componentNamesFromArray(comptime values: anytype, comptime field_name: []const u8) []const []const u8 {
 248     const names = comptime blk: {
 249         var out: [values.len][]const u8 = undefined;
 250         for (values, 0..) |value, index| {
 251             out[index] = componentName(value, field_name);
 252         }
 253         validateComponentNames(out[0..], field_name);
 254         break :blk out;
 255     };
 256     return &names;
 257 }
 258 
 259 fn componentNamesFromTuple(comptime values: anytype, comptime field_name: []const u8) []const []const u8 {
 260     const struct_info = @typeInfo(@TypeOf(values)).@"struct";
 261     const names = comptime blk: {
 262         var out: [struct_info.field_names.len][]const u8 = undefined;
 263         for (struct_info.field_names, 0..) |name, index| {
 264             out[index] = componentName(@field(values, name), field_name);
 265         }
 266         validateComponentNames(out[0..], field_name);
 267         break :blk out;
 268     };
 269     return &names;
 270 }
 271 
 272 fn componentName(comptime value: anytype, comptime field_name: []const u8) []const u8 {
 273     const name: []const u8 = value;
 274     if (name.len == 0) @compileError("operation " ++ field_name ++ " component name cannot be empty");
 275     return name;
 276 }
 277 
 278 fn validateComponentNames(comptime names: []const []const u8, comptime field_name: []const u8) void {
 279     inline for (names, 0..) |name, index| {
 280         inline for (names[0..index]) |existing| {
 281             if (std.mem.eql(u8, existing, name)) {
 282                 @compileError("operation " ++ field_name ++ " component name is duplicated: " ++ name);
 283             }
 284         }
 285     }
 286 }
 287 
 288 pub const attribute = struct {
 289     pub fn any(comptime name: []const u8) AttributeSpec {
 290         return attributeSpec(name, .any);
 291     }
 292 
 293     pub fn integer(comptime name: []const u8) AttributeSpec {
 294         return attributeSpec(name, .i64);
 295     }
 296 
 297     pub fn boolean(comptime name: []const u8) AttributeSpec {
 298         return attributeSpec(name, .bool);
 299     }
 300 
 301     pub fn string(comptime name: []const u8) AttributeSpec {
 302         return attributeSpec(name, .string);
 303     }
 304 
 305     pub fn dialect(comptime name: []const u8, comptime dialect_attr_name: []const u8) AttributeSpec {
 306         if (dialect_attr_name.len == 0) @compileError("dialect attribute storage name cannot be empty");
 307         return attributeSpec(name, .{ .dialect = dialect_attr_name });
 308     }
 309 };
 310 
 311 fn attributeSpec(comptime name: []const u8, comptime storage: AttributeStorage) AttributeSpec {
 312     if (name.len == 0) @compileError("operation attribute name cannot be empty");
 313     return .{ .name = name, .storage = storage };
 314 }
 315 
 316 fn attributeSpecFromValue(comptime value: anytype, comptime field_name: []const u8) AttributeSpec {
 317     const Value = @TypeOf(value);
 318     if (Value == AttributeSpec) return validateAttributeSpec(value);
 319     return switch (@typeInfo(Value)) {
 320         .pointer => |pointer_info| switch (pointer_info.size) {
 321             .one => switch (@typeInfo(pointer_info.child)) {
 322                 .array => |array_info| blk: {
 323                     if (array_info.child != u8) @compileError("operation declaration field " ++ field_name ++ " must contain attribute names or AttributeSpec values");
 324                     const name: []const u8 = value;
 325                     break :blk attribute.any(name);
 326                 },
 327                 else => @compileError("operation declaration field " ++ field_name ++ " must contain attribute names or AttributeSpec values"),
 328             },
 329             .slice => blk: {
 330                 if (pointer_info.child != u8) @compileError("operation declaration field " ++ field_name ++ " must contain attribute names or AttributeSpec values");
 331                 const name: []const u8 = value;
 332                 break :blk attribute.any(name);
 333             },
 334             else => @compileError("operation declaration field " ++ field_name ++ " must contain attribute names or AttributeSpec values"),
 335         },
 336         .array => |array_info| blk: {
 337             if (array_info.child != u8) @compileError("operation declaration field " ++ field_name ++ " must contain attribute names or AttributeSpec values");
 338             const name: []const u8 = &value;
 339             break :blk attribute.any(name);
 340         },
 341         else => @compileError("operation declaration field " ++ field_name ++ " must contain attribute names or AttributeSpec values"),
 342     };
 343 }
 344 
 345 fn validateAttributeSpec(comptime spec: AttributeSpec) AttributeSpec {
 346     if (spec.name.len == 0) @compileError("operation attribute name cannot be empty");
 347     switch (spec.storage) {
 348         .dialect => |dialect_attr_name| {
 349             if (dialect_attr_name.len == 0) @compileError("dialect attribute storage name cannot be empty");
 350         },
 351         else => {},
 352     }
 353     return spec;
 354 }
 355 
 356 fn attributeSpecsFromDecl(comptime spec: anytype, comptime name: []const u8) []const AttributeSpec {
 357     if (comptime !@hasField(@TypeOf(spec), name)) return &.{};
 358     return attributeSpecsFromValue(@field(spec, name), name);
 359 }
 360 
 361 fn attributeSpecsFromValue(comptime values: anytype, comptime field_name: []const u8) []const AttributeSpec {
 362     const Values = @TypeOf(values);
 363     if (Values == []const AttributeSpec) return values;
 364     return switch (@typeInfo(Values)) {
 365         .pointer => |pointer_info| switch (pointer_info.size) {
 366             .one => switch (@typeInfo(pointer_info.child)) {
 367                 .array => |array_info| blk: {
 368                     if (array_info.child == u8) {
 369                         @compileError("operation declaration field " ++ field_name ++ " must use a list of attribute specs, not one attribute name");
 370                     }
 371                     break :blk attributeSpecsFromArray(values.*, field_name);
 372                 },
 373                 .@"struct" => |struct_info| blk: {
 374                     if (!struct_info.is_tuple) @compileError("operation declaration field " ++ field_name ++ " must be a comptime slice, array, or tuple");
 375                     break :blk attributeSpecsFromTuple(values.*, field_name);
 376                 },
 377                 else => @compileError("operation declaration field " ++ field_name ++ " must be a comptime slice, array, or tuple"),
 378             },
 379             .slice => attributeSpecsFromSlice(values, field_name),
 380             else => @compileError("operation declaration field " ++ field_name ++ " must be a comptime slice, array, or tuple"),
 381         },
 382         .array => |array_info| blk: {
 383             if (array_info.child == u8) {
 384                 @compileError("operation declaration field " ++ field_name ++ " must use a list of attribute specs, not one attribute name");
 385             }
 386             break :blk attributeSpecsFromArray(values, field_name);
 387         },
 388         .@"struct" => |struct_info| blk: {
 389             if (!struct_info.is_tuple) @compileError("operation declaration field " ++ field_name ++ " must be a comptime slice, array, or tuple");
 390             break :blk attributeSpecsFromTuple(values, field_name);
 391         },
 392         else => @compileError("operation declaration field " ++ field_name ++ " must be a comptime slice, array, or tuple"),
 393     };
 394 }
 395 
 396 fn attributeSpecsFromSlice(comptime values: anytype, comptime field_name: []const u8) []const AttributeSpec {
 397     const specs = comptime blk: {
 398         var out: [values.len]AttributeSpec = undefined;
 399         for (values, 0..) |value, index| {
 400             out[index] = attributeSpecFromValue(value, field_name);
 401         }
 402         validateAttributeSpecs(out[0..], field_name);
 403         break :blk out;
 404     };
 405     return &specs;
 406 }
 407 
 408 fn attributeSpecsFromArray(comptime values: anytype, comptime field_name: []const u8) []const AttributeSpec {
 409     const specs = comptime blk: {
 410         var out: [values.len]AttributeSpec = undefined;
 411         for (values, 0..) |value, index| {
 412             out[index] = attributeSpecFromValue(value, field_name);
 413         }
 414         validateAttributeSpecs(out[0..], field_name);
 415         break :blk out;
 416     };
 417     return &specs;
 418 }
 419 
 420 fn attributeSpecsFromTuple(comptime values: anytype, comptime field_name: []const u8) []const AttributeSpec {
 421     const struct_info = @typeInfo(@TypeOf(values)).@"struct";
 422     const specs = comptime blk: {
 423         var out: [struct_info.field_names.len]AttributeSpec = undefined;
 424         for (struct_info.field_names, 0..) |name, index| {
 425             out[index] = attributeSpecFromValue(@field(values, name), field_name);
 426         }
 427         validateAttributeSpecs(out[0..], field_name);
 428         break :blk out;
 429     };
 430     return &specs;
 431 }
 432 
 433 fn validateAttributeSpecs(comptime specs: []const AttributeSpec, comptime field_name: []const u8) void {
 434     @setEvalBranchQuota(32_000);
 435     inline for (specs, 0..) |spec, index| {
 436         _ = validateAttributeSpec(spec);
 437         inline for (specs[0..index]) |existing| {
 438             if (std.mem.eql(u8, existing.name, spec.name)) {
 439                 @compileError("operation declaration field " ++ field_name ++ " duplicates attribute: " ++ spec.name);
 440             }
 441         }
 442     }
 443 }
 444 
 445 fn attributeNamesFromSpecs(comptime specs: []const AttributeSpec) []const []const u8 {
 446     const names = comptime blk: {
 447         var out: [specs.len][]const u8 = undefined;
 448         for (specs, 0..) |spec, index| {
 449             out[index] = spec.name;
 450         }
 451         break :blk out;
 452     };
 453     return &names;
 454 }
 455 
 456 fn attribute_names_with_required_storage(
 457     comptime attrs: []const []const u8,
 458     comptime required_attrs: []const []const u8,
 459 ) type {
 460     return struct {
 461         const values = blk: {
 462             var extra: usize = 0;
 463             for (required_attrs, 0..) |required, required_index| {
 464                 if (required.len == 0) @compileError("required attribute name cannot be empty");
 465                 for (required_attrs[0..required_index]) |existing_required| {
 466                     if (std.mem.eql(u8, existing_required, required)) {
 467                         @compileError("required attribute name is duplicated");
 468                     }
 469                 }
 470                 var found = false;
 471                 for (attrs) |attr| {
 472                     if (std.mem.eql(u8, attr, required)) {
 473                         found = true;
 474                         break;
 475                     }
 476                 }
 477                 if (!found) extra += 1;
 478             }
 479             var out: [attrs.len + extra][]const u8 = undefined;
 480             for (attrs, 0..) |attr, index| {
 481                 if (attr.len == 0) @compileError("operation attribute name cannot be empty");
 482                 for (attrs[0..index]) |existing| {
 483                     if (std.mem.eql(u8, existing, attr)) {
 484                         @compileError("operation attribute name is duplicated");
 485                     }
 486                 }
 487                 out[index] = attr;
 488             }
 489             var index = attrs.len;
 490             for (required_attrs) |required| {
 491                 var found = false;
 492                 for (attrs) |attr| {
 493                     if (std.mem.eql(u8, attr, required)) {
 494                         found = true;
 495                         break;
 496                     }
 497                 }
 498                 if (!found) {
 499                     out[index] = required;
 500                     index += 1;
 501                 }
 502             }
 503             break :blk out;
 504         };
 505     };
 506 }
 507 
 508 fn attributeNamesWithRequired(
 509     comptime attrs: []const []const u8,
 510     comptime required_attrs: []const []const u8,
 511 ) []const []const u8 {
 512     if (required_attrs.len == 0) return attrs;
 513     return &attribute_names_with_required_storage(attrs, required_attrs).values;
 514 }
 515 
 516 fn attribute_specs_with_required_storage(
 517     comptime attrs: []const AttributeSpec,
 518     comptime required_attrs: []const AttributeSpec,
 519 ) type {
 520     return struct {
 521         const values = blk: {
 522             var extra: usize = 0;
 523             for (required_attrs, 0..) |required, required_index| {
 524                 _ = validateAttributeSpec(required);
 525                 for (required_attrs[0..required_index]) |existing_required| {
 526                     if (std.mem.eql(u8, existing_required.name, required.name)) {
 527                         @compileError("required attribute name is duplicated");
 528                     }
 529                 }
 530                 var found = false;
 531                 for (attrs) |candidate| {
 532                     if (std.mem.eql(u8, candidate.name, required.name)) {
 533                         validateCompatibleAttributeSpecs(candidate, required);
 534                         found = true;
 535                         break;
 536                     }
 537                 }
 538                 if (!found) extra += 1;
 539             }
 540             var out: [attrs.len + extra]AttributeSpec = undefined;
 541             for (attrs, 0..) |candidate, attr_index| {
 542                 var merged = validateAttributeSpec(candidate);
 543                 for (attrs[0..attr_index]) |existing| {
 544                     if (std.mem.eql(u8, existing.name, candidate.name)) {
 545                         @compileError("operation attribute name is duplicated");
 546                     }
 547                 }
 548                 for (required_attrs) |required| {
 549                     if (std.mem.eql(u8, required.name, candidate.name)) {
 550                         validateCompatibleAttributeSpecs(candidate, required);
 551                         if (std.meta.activeTag(merged.storage) == .any) merged = required;
 552                         break;
 553                     }
 554                 }
 555                 out[attr_index] = merged;
 556             }
 557             var index = attrs.len;
 558             for (required_attrs) |required| {
 559                 var found = false;
 560                 for (attrs) |candidate| {
 561                     if (std.mem.eql(u8, candidate.name, required.name)) {
 562                         found = true;
 563                         break;
 564                     }
 565                 }
 566                 if (!found) {
 567                     out[index] = required;
 568                     index += 1;
 569                 }
 570             }
 571             break :blk out;
 572         };
 573     };
 574 }
 575 
 576 fn attributeSpecsWithRequired(
 577     comptime attrs: []const AttributeSpec,
 578     comptime required_attrs: []const AttributeSpec,
 579 ) []const AttributeSpec {
 580     if (required_attrs.len == 0) return attrs;
 581     return &attribute_specs_with_required_storage(attrs, required_attrs).values;
 582 }
 583 
 584 fn validateCompatibleAttributeSpecs(comptime lhs: AttributeSpec, comptime rhs: AttributeSpec) void {
 585     if (!std.mem.eql(u8, lhs.name, rhs.name)) return;
 586     if (std.meta.activeTag(lhs.storage) == .any or std.meta.activeTag(rhs.storage) == .any) return;
 587     switch (lhs.storage) {
 588         .i64 => if (std.meta.activeTag(rhs.storage) != .i64) @compileError("operation attribute storage is duplicated with incompatible kinds"),
 589         .bool => if (std.meta.activeTag(rhs.storage) != .bool) @compileError("operation attribute storage is duplicated with incompatible kinds"),
 590         .string => if (std.meta.activeTag(rhs.storage) != .string) @compileError("operation attribute storage is duplicated with incompatible kinds"),
 591         .dialect => |lhs_name| switch (rhs.storage) {
 592             .dialect => |rhs_name| {
 593                 if (!std.mem.eql(u8, lhs_name, rhs_name)) {
 594                     @compileError("operation dialect attribute storage name is duplicated with incompatible names");
 595                 }
 596             },
 597             else => @compileError("operation attribute storage is duplicated with incompatible kinds"),
 598         },
 599         .any => {},
 600     }
 601 }
 602 
 603 fn attribute_specs_with_names_storage(
 604     comptime specs: []const AttributeSpec,
 605     comptime names: []const []const u8,
 606 ) type {
 607     return struct {
 608         const values = blk: {
 609             var missing: usize = 0;
 610             for (names) |name| {
 611                 var found = false;
 612                 for (specs) |spec| {
 613                     if (std.mem.eql(u8, spec.name, name)) {
 614                         found = true;
 615                         break;
 616                     }
 617                 }
 618                 if (!found) missing += 1;
 619             }
 620             var out: [specs.len + missing]AttributeSpec = undefined;
 621             for (specs, 0..) |spec, index| {
 622                 out[index] = validateAttributeSpec(spec);
 623             }
 624             var index = specs.len;
 625             for (names) |name| {
 626                 var found = false;
 627                 for (specs) |spec| {
 628                     if (std.mem.eql(u8, spec.name, name)) {
 629                         found = true;
 630                         break;
 631                     }
 632                 }
 633                 if (!found) {
 634                     out[index] = attribute.any(name);
 635                     index += 1;
 636                 }
 637             }
 638             validateAttributeSpecs(out[0..], "attribute_specs");
 639             break :blk out;
 640         };
 641     };
 642 }
 643 
 644 fn attributeSpecsWithNames(
 645     comptime specs: []const AttributeSpec,
 646     comptime names: []const []const u8,
 647 ) []const AttributeSpec {
 648     if (names.len == 0) return specs;
 649     return &attribute_specs_with_names_storage(specs, names).values;
 650 }
 651 
 652 pub const TypeSpec = struct {
 653     name: []const u8,
 654     interfaces: []const interfaces.InterfaceEntry = &.{},
 655 };
 656 
 657 pub const DialectSpec = struct {
 658     name: []const u8,
 659     operations: []const OperationSpec = &.{},
 660     types: []const TypeSpec = &.{},
 661     dialect_attributes: []const []const u8 = &.{},
 662     interfaces: []const interfaces.InterfaceEntry = &.{},
 663     op_interface_fallbacks: []const OpInterfaceFallbackEntry = &.{},
 664     type_interface_fallbacks: []const TypeInterfaceFallbackEntry = &.{},
 665 };
 666 
 667 pub const DialectSpecOptions = struct {
 668     types: []const TypeSpec = &.{},
 669     dialect_attributes: []const []const u8 = &.{},
 670     interfaces: []const interfaces.InterfaceEntry = &.{},
 671     op_interface_fallbacks: []const OpInterfaceFallbackEntry = &.{},
 672     type_interface_fallbacks: []const TypeInterfaceFallbackEntry = &.{},
 673 };
 674 
 675 pub fn dialectSpec(comptime DialectType: type, comptime options: DialectSpecOptions) DialectSpec {
 676     if (!@hasDecl(DialectType, "name")) @compileError("dialect type must declare name");
 677     return .{
 678         .name = DialectType.name,
 679         .operations = operations(DialectType),
 680         .types = options.types,
 681         .dialect_attributes = options.dialect_attributes,
 682         .interfaces = options.interfaces,
 683         .op_interface_fallbacks = options.op_interface_fallbacks,
 684         .type_interface_fallbacks = options.type_interface_fallbacks,
 685     };
 686 }
 687 
 688 pub fn operationName(comptime dialect_name: []const u8, comptime mnemonic: []const u8) []const u8 {
 689     if (dialect_name.len == 0) @compileError("dialect name cannot be empty");
 690     if (mnemonic.len == 0) @compileError("operation mnemonic cannot be empty");
 691     return dialect_name ++ "." ++ mnemonic;
 692 }
 693 
 694 pub const shape = struct {
 695     pub fn of(comptime spec: anytype) interfaces.OperationShape {
 696         comptime validateSpec(@TypeOf(spec));
 697         return .{
 698             .operands = field(spec, "operands"),
 699             .results = field(spec, "results"),
 700             .regions = field(spec, "regions"),
 701             .successors = field(spec, "successors"),
 702         };
 703     }
 704 
 705     pub fn fixed(
 706         comptime operands: anytype,
 707         comptime results: anytype,
 708         comptime regions: anytype,
 709         comptime successors: anytype,
 710     ) interfaces.OperationShape {
 711         return of(.{
 712             .operands = operands,
 713             .results = results,
 714             .regions = regions,
 715             .successors = successors,
 716         });
 717     }
 718 
 719     pub fn leaf(comptime operands: anytype, comptime results: anytype) interfaces.OperationShape {
 720         return of(.{
 721             .operands = operands,
 722             .results = results,
 723             .regions = 0,
 724             .successors = 0,
 725         });
 726     }
 727 
 728     pub fn noNested(comptime spec: anytype) interfaces.OperationShape {
 729         comptime validateNoNestedSpec(@TypeOf(spec));
 730         const base = of(spec);
 731         return .{
 732             .operands = base.operands,
 733             .results = base.results,
 734             .regions = interfaces.CountRange.exactly(0),
 735             .successors = interfaces.CountRange.exactly(0),
 736         };
 737     }
 738 
 739     pub fn exactly(comptime count: usize) interfaces.CountRange {
 740         return interfaces.CountRange.exactly(count);
 741     }
 742 
 743     pub fn atLeast(comptime count: usize) interfaces.CountRange {
 744         return interfaces.CountRange.atLeast(count);
 745     }
 746 
 747     pub fn atMost(comptime count: usize) interfaces.CountRange {
 748         return interfaces.CountRange.atMost(count);
 749     }
 750 
 751     pub fn between(comptime min: usize, comptime max: usize) interfaces.CountRange {
 752         return interfaces.CountRange.between(min, max);
 753     }
 754 
 755     pub fn any() interfaces.CountRange {
 756         return .{};
 757     }
 758 
 759     pub fn range(comptime value: anytype) interfaces.CountRange {
 760         return rangeFrom(value);
 761     }
 762 
 763     fn field(comptime spec: anytype, comptime name: []const u8) interfaces.CountRange {
 764         if (comptime @hasField(@TypeOf(spec), name)) {
 765             return rangeFrom(@field(spec, name));
 766         }
 767         return .{};
 768     }
 769 
 770     fn rangeFrom(comptime value: anytype) interfaces.CountRange {
 771         const Value = @TypeOf(value);
 772         if (Value == interfaces.CountRange) return value;
 773         return switch (@typeInfo(Value)) {
 774             .comptime_int => blk: {
 775                 if (value < 0) @compileError("operation shape count cannot be negative");
 776                 break :blk interfaces.CountRange.exactly(@as(usize, value));
 777             },
 778             .int => |int_info| blk: {
 779                 if (int_info.signedness == .signed and value < 0) {
 780                     @compileError("operation shape count cannot be negative");
 781                 }
 782                 break :blk interfaces.CountRange.exactly(@intCast(value));
 783             },
 784             .pointer => |pointer_info| switch (pointer_info.size) {
 785                 .one => switch (@typeInfo(pointer_info.child)) {
 786                     .array => |array_info| blk: {
 787                         if (array_info.child == u8) {
 788                             @compileError("operation shape fields must use a list of component names, not one component name");
 789                         }
 790                         tryComponentNames(value.*, "shape");
 791                         break :blk interfaces.CountRange.exactly(array_info.len);
 792                     },
 793                     .@"struct" => |struct_info| blk: {
 794                         tryComponentNames(value.*, "shape");
 795                         break :blk interfaces.CountRange.exactly(struct_info.field_names.len);
 796                     },
 797                     else => @compileError("operation shape fields must be integer counts, CountRange values, or component name lists"),
 798                 },
 799                 .slice => blk: {
 800                     tryComponentNames(value, "shape");
 801                     break :blk interfaces.CountRange.exactly(value.len);
 802                 },
 803                 else => @compileError("operation shape fields must be integer counts, CountRange values, or component name lists"),
 804             },
 805             .array => |array_info| blk: {
 806                 if (array_info.child == u8) {
 807                     @compileError("operation shape fields must use a list of component names, not one component name");
 808                 }
 809                 tryComponentNames(value, "shape");
 810                 break :blk interfaces.CountRange.exactly(array_info.len);
 811             },
 812             .@"struct" => |struct_info| blk: {
 813                 tryComponentNames(value, "shape");
 814                 break :blk interfaces.CountRange.exactly(struct_info.field_names.len);
 815             },
 816             else => @compileError("operation shape fields must be integer counts or CountRange values"),
 817         };
 818     }
 819 
 820     fn validateSpec(comptime Spec: type) void {
 821         switch (@typeInfo(Spec)) {
 822             .@"struct" => |struct_info| {
 823                 inline for (struct_info.field_names) |field_name| {
 824                     if (!std.mem.eql(u8, field_name, "operands") and
 825                         !std.mem.eql(u8, field_name, "results") and
 826                         !std.mem.eql(u8, field_name, "regions") and
 827                         !std.mem.eql(u8, field_name, "successors"))
 828                     {
 829                         @compileError("unknown operation shape field: " ++ field_name);
 830                     }
 831                 }
 832             },
 833             else => @compileError("operation shape spec must be a struct literal"),
 834         }
 835     }
 836 
 837     fn validateNoNestedSpec(comptime Spec: type) void {
 838         validateSpec(Spec);
 839         if (@hasField(Spec, "regions")) @compileError("noNested operation shape cannot specify regions");
 840         if (@hasField(Spec, "successors")) @compileError("noNested operation shape cannot specify successors");
 841     }
 842 };
 843 
 844 pub const segments = struct {
 845     pub const operand_attribute_name = "operand_segment_sizes";
 846     pub const result_attribute_name = "result_segment_sizes";
 847 
 848     pub fn operands(comptime values: anytype) interfaces.OperationSegmentSpec {
 849         return sized(operand_attribute_name, values);
 850     }
 851 
 852     pub fn results(comptime values: anytype) interfaces.OperationSegmentSpec {
 853         return sized(result_attribute_name, values);
 854     }
 855 
 856     pub fn sized(comptime attribute_name: []const u8, comptime values: anytype) interfaces.OperationSegmentSpec {
 857         if (attribute_name.len == 0) @compileError("operation segment attribute name cannot be empty");
 858         const segment_ranges = ranges(values);
 859         if (segment_ranges.len == 0) @compileError("operation segment declaration cannot be empty");
 860         return .{
 861             .attribute_name = attribute_name,
 862             .segments = segment_ranges,
 863         };
 864     }
 865 
 866     fn ranges(comptime values: anytype) []const interfaces.CountRange {
 867         const Values = @TypeOf(values);
 868         if (Values == []const interfaces.CountRange) return values;
 869         return switch (@typeInfo(Values)) {
 870             .pointer => |pointer_info| switch (pointer_info.size) {
 871                 .one => switch (@typeInfo(pointer_info.child)) {
 872                     .array => rangesFromArray(values.*),
 873                     .@"struct" => |struct_info| blk: {
 874                         if (!struct_info.is_tuple) @compileError("operation segment values must be a comptime slice, array, or tuple");
 875                         break :blk rangesFromTuple(values.*);
 876                     },
 877                     else => @compileError("operation segment values must be a comptime slice, array, or tuple"),
 878                 },
 879                 .slice => rangesFromSlice(values),
 880                 else => @compileError("operation segment values must be a comptime slice, array, or tuple"),
 881             },
 882             .array => rangesFromArray(values),
 883             .@"struct" => |struct_info| blk: {
 884                 if (!struct_info.is_tuple) @compileError("operation segment values must be a comptime slice, array, or tuple");
 885                 break :blk rangesFromTuple(values);
 886             },
 887             else => @compileError("operation segment values must be a comptime slice, array, or tuple"),
 888         };
 889     }
 890 
 891     fn rangesFromSlice(comptime values: anytype) []const interfaces.CountRange {
 892         const value = comptime blk: {
 893             var out: [values.len]interfaces.CountRange = undefined;
 894             for (values, 0..) |segment, index| {
 895                 out[index] = shape.range(segment);
 896             }
 897             break :blk out;
 898         };
 899         return &value;
 900     }
 901 
 902     fn rangesFromArray(comptime values: anytype) []const interfaces.CountRange {
 903         const value = comptime blk: {
 904             var out: [values.len]interfaces.CountRange = undefined;
 905             for (values, 0..) |segment, index| {
 906                 out[index] = shape.range(segment);
 907             }
 908             break :blk out;
 909         };
 910         return &value;
 911     }
 912 
 913     fn rangesFromTuple(comptime values: anytype) []const interfaces.CountRange {
 914         const struct_info = @typeInfo(@TypeOf(values)).@"struct";
 915         const value = comptime blk: {
 916             var out: [struct_info.field_names.len]interfaces.CountRange = undefined;
 917             for (struct_info.field_names, 0..) |name, index| {
 918                 out[index] = shape.range(@field(values, name));
 919             }
 920             break :blk out;
 921         };
 922         return &value;
 923     }
 924 };
 925 
 926 pub const opSpec = struct {
 927     pub const Options = struct {
 928         traits: interfaces.OperationTraits = .{},
 929         attrs: []const []const u8 = &.{},
 930         required_attrs: []const []const u8 = &.{},
 931         attribute_specs: []const AttributeSpec = &.{},
 932         operand_segments: ?interfaces.OperationSegmentSpec = null,
 933         result_segments: ?interfaces.OperationSegmentSpec = null,
 934         operand_types: []const interfaces.OperationTypeConstraint = &.{},
 935         result_types: []const interfaces.OperationTypeConstraint = &.{},
 936         properties: ?interfaces.OperationPropertiesModel = null,
 937         interfaces: []const interfaces.InterfaceEntry = &.{},
 938         dynamic_traits: []const OperationTraitSpec = &.{},
 939     };
 940 
 941     pub fn dialect(comptime Target: type) type {
 942         const Names = namesFor(Target);
 943         return struct {
 944             pub fn name(comptime mnemonic: []const u8) []const u8 {
 945                 return Names.name(mnemonic);
 946             }
 947 
 948             pub fn state(comptime OpType: type, loc: Location) Operation.State {
 949                 return Names.state(OpType, loc);
 950             }
 951 
 952             pub fn define(comptime spec: anytype) OperationSpec {
 953                 comptime validateDecl(@TypeOf(spec), "mnemonic");
 954                 return shapedWithLayout(Names.name(@field(spec, "mnemonic")), shapeFromDecl(spec), layoutFromDecl(spec), optionsFromDecl(spec));
 955             }
 956 
 957             pub fn leaf(comptime spec: anytype) OperationSpec {
 958                 comptime validateDecl(@TypeOf(spec), "mnemonic");
 959                 return shapedWithLayout(Names.name(@field(spec, "mnemonic")), leafShapeFromDecl(spec), layoutFromDecl(spec), optionsFromDecl(spec));
 960             }
 961 
 962             pub fn terminator(comptime spec: anytype) OperationSpec {
 963                 comptime validateDecl(@TypeOf(spec), "mnemonic");
 964                 return shapedWithLayout(Names.name(@field(spec, "mnemonic")), terminatorShapeFromDecl(spec), layoutFromDecl(spec), terminatorOptionsFromDecl(spec));
 965             }
 966         };
 967     }
 968 
 969     pub fn define(comptime spec: anytype) OperationSpec {
 970         comptime validateDecl(@TypeOf(spec), "name");
 971         return shapedWithLayout(@field(spec, "name"), shapeFromDecl(spec), layoutFromDecl(spec), optionsFromDecl(spec));
 972     }
 973 
 974     pub fn terminator(comptime spec: anytype) OperationSpec {
 975         comptime validateDecl(@TypeOf(spec), "name");
 976         return shapedWithLayout(@field(spec, "name"), terminatorShapeFromDecl(spec), layoutFromDecl(spec), terminatorOptionsFromDecl(spec));
 977     }
 978 
 979     pub fn shaped(comptime full_name: []const u8, comptime op_shape: interfaces.OperationShape, comptime options: Options) OperationSpec {
 980         return shapedWithLayout(full_name, op_shape, .{}, options);
 981     }
 982 
 983     pub fn dynamicTraits(comptime values: anytype) []const OperationTraitSpec {
 984         return asSlice(OperationTraitSpec, values, "dynamic_traits");
 985     }
 986 
 987     pub fn verifier(comptime verify_fn: *const fn (op_ptr: *const anyopaque) anyerror!void) interfaces.InterfaceEntry {
 988         return VerifyOpInterface.entryFor(verify_fn);
 989     }
 990 
 991     fn shapedWithLayout(comptime full_name: []const u8, comptime op_shape: interfaces.OperationShape, comptime op_layout: OperationLayout, comptime options: Options) OperationSpec {
 992         if (full_name.len == 0) @compileError("operation name cannot be empty");
 993         validateLayout(op_shape, op_layout);
 994         return .{
 995             .name = full_name,
 996             .shape = op_shape,
 997             .operand_names = op_layout.operands,
 998             .result_names = op_layout.results,
 999             .region_names = op_layout.regions,
1000             .successor_names = op_layout.successors,
1001             .traits = options.traits,
1002             .inherent_attribute_names = attributeNamesWithRequired(options.attrs, options.required_attrs),
1003             .required_attribute_names = options.required_attrs,
1004             .attribute_specs = attributeSpecsWithNames(options.attribute_specs, attributeNamesWithRequired(options.attrs, options.required_attrs)),
1005             .operand_segments = options.operand_segments,
1006             .result_segments = options.result_segments,
1007             .operand_type_constraints = options.operand_types,
1008             .result_type_constraints = options.result_types,
1009             .properties_model = options.properties,
1010             .interfaces = options.interfaces,
1011             .dynamic_traits = options.dynamic_traits,
1012         };
1013     }
1014 
1015     fn layoutFromDecl(comptime spec: anytype) OperationLayout {
1016         return .{
1017             .operands = layoutField(spec, "operands", "operand_names"),
1018             .results = layoutField(spec, "results", "result_names"),
1019             .regions = layoutField(spec, "regions", "region_names"),
1020             .successors = layoutField(spec, "successors", "successor_names"),
1021         };
1022     }
1023 
1024     fn shapeFromDecl(comptime spec: anytype) interfaces.OperationShape {
1025         const Spec = @TypeOf(spec);
1026         if (comptime @hasField(Spec, "shape")) {
1027             if (@hasField(Spec, "operands")) @compileError("operation declaration cannot specify both shape and operands");
1028             if (@hasField(Spec, "results")) @compileError("operation declaration cannot specify both shape and results");
1029             if (@hasField(Spec, "regions")) @compileError("operation declaration cannot specify both shape and regions");
1030             if (@hasField(Spec, "successors")) @compileError("operation declaration cannot specify both shape and successors");
1031             return shapeFromValue(@field(spec, "shape"));
1032         }
1033         return .{
1034             .operands = shapeField(spec, "operands"),
1035             .results = shapeField(spec, "results"),
1036             .regions = shapeField(spec, "regions"),
1037             .successors = shapeField(spec, "successors"),
1038         };
1039     }
1040 
1041     fn terminatorShapeFromDecl(comptime spec: anytype) interfaces.OperationShape {
1042         const Spec = @TypeOf(spec);
1043         if (@hasField(Spec, "shape")) @compileError("terminator operation declaration cannot specify shape");
1044         return .{
1045             .operands = shapeField(spec, "operands"),
1046             .results = shapeFieldOrExact(spec, "results", 0),
1047             .regions = shapeFieldOrExact(spec, "regions", 0),
1048             .successors = shapeFieldOrExact(spec, "successors", 0),
1049         };
1050     }
1051 
1052     fn shapeFromValue(comptime value: anytype) interfaces.OperationShape {
1053         const Value = @TypeOf(value);
1054         if (Value == interfaces.OperationShape) return value;
1055         return shape.of(value);
1056     }
1057 
1058     fn leafShapeFromDecl(comptime spec: anytype) interfaces.OperationShape {
1059         const Spec = @TypeOf(spec);
1060         if (@hasField(Spec, "shape")) @compileError("leaf operation declaration cannot specify shape");
1061         if (@hasField(Spec, "regions")) @compileError("leaf operation declaration cannot specify regions");
1062         if (@hasField(Spec, "successors")) @compileError("leaf operation declaration cannot specify successors");
1063         return .{
1064             .operands = shapeField(spec, "operands"),
1065             .results = shapeField(spec, "results"),
1066             .regions = interfaces.CountRange.exactly(0),
1067             .successors = interfaces.CountRange.exactly(0),
1068         };
1069     }
1070 
1071     fn shapeField(comptime spec: anytype, comptime name: []const u8) interfaces.CountRange {
1072         if (comptime @hasField(@TypeOf(spec), name)) return shape.rangeFrom(@field(spec, name));
1073         return .{};
1074     }
1075 
1076     fn layoutField(comptime spec: anytype, comptime shape_name: []const u8, comptime names_name: []const u8) []const []const u8 {
1077         const inferred = if (comptime @hasField(@TypeOf(spec), shape_name))
1078             componentNamesFromValue(@field(spec, shape_name), shape_name)
1079         else
1080             &.{};
1081         if (comptime @hasField(@TypeOf(spec), names_name)) {
1082             const explicit = componentNamesFromValue(@field(spec, names_name), names_name);
1083             if (inferred.len != 0) {
1084                 @compileError("operation declaration cannot specify both named " ++ shape_name ++ " and " ++ names_name);
1085             }
1086             return explicit;
1087         }
1088         return inferred;
1089     }
1090 
1091     fn validateLayout(comptime op_shape: interfaces.OperationShape, comptime op_layout: OperationLayout) void {
1092         validateLayoutRange(op_shape.operands, op_layout.operands, "operand");
1093         validateLayoutRange(op_shape.results, op_layout.results, "result");
1094         validateLayoutRange(op_shape.regions, op_layout.regions, "region");
1095         validateLayoutRange(op_shape.successors, op_layout.successors, "successor");
1096     }
1097 
1098     fn validateLayoutRange(comptime range: interfaces.CountRange, comptime names: []const []const u8, comptime kind: []const u8) void {
1099         if (range.max) |max| {
1100             if (names.len > max) {
1101                 @compileError("operation " ++ kind ++ " names cannot exceed the operation shape maximum");
1102             }
1103         }
1104     }
1105 
1106     fn shapeFieldOrExact(comptime spec: anytype, comptime name: []const u8, comptime count: usize) interfaces.CountRange {
1107         if (comptime @hasField(@TypeOf(spec), name)) return shape.rangeFrom(@field(spec, name));
1108         return interfaces.CountRange.exactly(count);
1109     }
1110 
1111     fn optionsFromDecl(comptime spec: anytype) Options {
1112         const attr_specs = attributeSpecsFromDecl(spec, "attrs");
1113         const required_attr_specs = attributeSpecsFromDecl(spec, "required_attrs");
1114         return .{
1115             .traits = declField(spec, "traits", interfaces.OperationTraits{}),
1116             .attrs = attributeNamesFromSpecs(attr_specs),
1117             .required_attrs = attributeNamesFromSpecs(required_attr_specs),
1118             .attribute_specs = attributeSpecsWithRequired(attr_specs, required_attr_specs),
1119             .operand_segments = segmentDecl(spec, "operand_segments"),
1120             .result_segments = segmentDecl(spec, "result_segments"),
1121             .operand_types = declSlice(interfaces.OperationTypeConstraint, spec, "operand_types"),
1122             .result_types = declSlice(interfaces.OperationTypeConstraint, spec, "result_types"),
1123             .properties = declField(spec, "properties", @as(?interfaces.OperationPropertiesModel, null)),
1124             .interfaces = declSlice(interfaces.InterfaceEntry, spec, "interfaces"),
1125             .dynamic_traits = if (comptime @hasField(@TypeOf(spec), "dynamic_traits")) dynamicTraits(@field(spec, "dynamic_traits")) else &.{},
1126         };
1127     }
1128 
1129     fn terminatorOptionsFromDecl(comptime spec: anytype) Options {
1130         return optionsWithDynamicTrait(optionsFromDecl(spec), trait(core_traits.Terminator));
1131     }
1132 
1133     fn declField(comptime spec: anytype, comptime name: []const u8, comptime default: anytype) @TypeOf(default) {
1134         if (comptime @hasField(@TypeOf(spec), name)) return @field(spec, name);
1135         return default;
1136     }
1137 
1138     fn declSlice(comptime Element: type, comptime spec: anytype, comptime name: []const u8) []const Element {
1139         if (comptime !@hasField(@TypeOf(spec), name)) return &.{};
1140         return asSlice(Element, @field(spec, name), name);
1141     }
1142 
1143     fn segmentDecl(comptime spec: anytype, comptime name: []const u8) ?interfaces.OperationSegmentSpec {
1144         if (comptime !@hasField(@TypeOf(spec), name)) return null;
1145         const value = @field(spec, name);
1146         if (@TypeOf(value) != interfaces.OperationSegmentSpec) {
1147             @compileError("operation declaration field " ++ name ++ " must be an OperationSegmentSpec");
1148         }
1149         return value;
1150     }
1151 
1152     fn asSlice(comptime Element: type, comptime values: anytype, comptime name: []const u8) []const Element {
1153         const Values = @TypeOf(values);
1154         if (Values == []const Element) return values;
1155         switch (@typeInfo(Values)) {
1156             .pointer => |pointer_info| {
1157                 switch (pointer_info.size) {
1158                     .one => switch (@typeInfo(pointer_info.child)) {
1159                         .array => |array_info| {
1160                             const typed = comptime blk: {
1161                                 var out: [array_info.len]Element = undefined;
1162                                 for (values.*, 0..) |value, index| {
1163                                     out[index] = asElement(Element, value);
1164                                 }
1165                                 break :blk out;
1166                             };
1167                             return &typed;
1168                         },
1169                         .@"struct" => |struct_info| {
1170                             if (!struct_info.is_tuple) @compileError("operation declaration field " ++ name ++ " must be a comptime slice, array, or tuple");
1171                             const typed = comptime blk: {
1172                                 var out: [struct_info.field_names.len]Element = undefined;
1173                                 for (struct_info.field_names, 0..) |field_name, index| {
1174                                     out[index] = asElement(Element, @field(values.*, field_name));
1175                                 }
1176                                 break :blk out;
1177                             };
1178                             return &typed;
1179                         },
1180                         else => @compileError("operation declaration field " ++ name ++ " must be a comptime slice or array pointer"),
1181                     },
1182                     .slice => {
1183                         const typed = comptime blk: {
1184                             var out: [values.len]Element = undefined;
1185                             for (values, 0..) |value, index| {
1186                                 out[index] = asElement(Element, value);
1187                             }
1188                             break :blk out;
1189                         };
1190                         return &typed;
1191                     },
1192                     else => @compileError("operation declaration field " ++ name ++ " must be a comptime slice or array pointer"),
1193                 }
1194             },
1195             .array => |array_info| {
1196                 const typed = comptime blk: {
1197                     var out: [array_info.len]Element = undefined;
1198                     for (values, 0..) |value, index| {
1199                         out[index] = asElement(Element, value);
1200                     }
1201                     break :blk out;
1202                 };
1203                 return &typed;
1204             },
1205             .@"struct" => |struct_info| {
1206                 if (!struct_info.is_tuple) @compileError("operation declaration field " ++ name ++ " must be a comptime slice, array, or tuple");
1207                 const typed = comptime blk: {
1208                     var out: [struct_info.field_names.len]Element = undefined;
1209                     for (struct_info.field_names, 0..) |field_name, index| {
1210                         out[index] = asElement(Element, @field(values, field_name));
1211                     }
1212                     break :blk out;
1213                 };
1214                 return &typed;
1215             },
1216             else => @compileError("operation declaration field " ++ name ++ " must be a comptime slice or array pointer"),
1217         }
1218     }
1219 
1220     fn asElement(comptime Element: type, comptime value: anytype) Element {
1221         const Value = @TypeOf(value);
1222         if (Element == OperationTraitSpec and Value == type) return trait(value);
1223         if (Value == Element) return value;
1224         return switch (@typeInfo(Element)) {
1225             .@"struct" => |element_info| blk: {
1226                 var out: Element = undefined;
1227                 for (element_info.field_names) |field_name| {
1228                     @field(out, field_name) = @field(value, field_name);
1229                 }
1230                 break :blk out;
1231             },
1232             else => value,
1233         };
1234     }
1235 
1236     fn optionsWithDynamicTrait(comptime options: Options, comptime trait_spec: OperationTraitSpec) Options {
1237         return .{
1238             .traits = options.traits.merge(trait_spec.traits),
1239             .attrs = options.attrs,
1240             .required_attrs = options.required_attrs,
1241             .attribute_specs = options.attribute_specs,
1242             .operand_segments = options.operand_segments,
1243             .result_segments = options.result_segments,
1244             .operand_types = options.operand_types,
1245             .result_types = options.result_types,
1246             .properties = options.properties,
1247             .interfaces = options.interfaces,
1248             .dynamic_traits = dynamicTraitsWith(options.dynamic_traits, trait_spec),
1249         };
1250     }
1251 
1252     fn dynamicTraitsWith(comptime dynamic_traits: []const OperationTraitSpec, comptime trait_spec: OperationTraitSpec) []const OperationTraitSpec {
1253         for (dynamic_traits) |existing| {
1254             if (existing.id == trait_spec.id) @compileError("operation dynamic trait is duplicated");
1255         }
1256         const values = comptime blk: {
1257             var out: [dynamic_traits.len + 1]OperationTraitSpec = undefined;
1258             for (dynamic_traits, 0..) |existing, index| {
1259                 out[index] = existing;
1260             }
1261             out[dynamic_traits.len] = trait_spec;
1262             break :blk out;
1263         };
1264         return &values;
1265     }
1266 
1267     fn validateDecl(comptime Spec: type, comptime name_field: []const u8) void {
1268         switch (@typeInfo(Spec)) {
1269             .@"struct" => |struct_info| {
1270                 if (!@hasField(Spec, name_field)) @compileError("operation declaration must declare " ++ name_field);
1271                 inline for (struct_info.field_names) |field_name| {
1272                     if (!isDeclField(field_name, name_field)) {
1273                         @compileError("unknown operation declaration field: " ++ field_name);
1274                     }
1275                 }
1276             },
1277             else => @compileError("operation declaration must be a struct literal"),
1278         }
1279     }
1280 
1281     fn isDeclField(comptime field_name: []const u8, comptime name_field: []const u8) bool {
1282         return std.mem.eql(u8, field_name, name_field) or
1283             std.mem.eql(u8, field_name, "shape") or
1284             std.mem.eql(u8, field_name, "operands") or
1285             std.mem.eql(u8, field_name, "operand_names") or
1286             std.mem.eql(u8, field_name, "results") or
1287             std.mem.eql(u8, field_name, "result_names") or
1288             std.mem.eql(u8, field_name, "regions") or
1289             std.mem.eql(u8, field_name, "region_names") or
1290             std.mem.eql(u8, field_name, "successors") or
1291             std.mem.eql(u8, field_name, "successor_names") or
1292             std.mem.eql(u8, field_name, "traits") or
1293             std.mem.eql(u8, field_name, "attrs") or
1294             std.mem.eql(u8, field_name, "required_attrs") or
1295             std.mem.eql(u8, field_name, "operand_segments") or
1296             std.mem.eql(u8, field_name, "result_segments") or
1297             std.mem.eql(u8, field_name, "operand_types") or
1298             std.mem.eql(u8, field_name, "result_types") or
1299             std.mem.eql(u8, field_name, "properties") or
1300             std.mem.eql(u8, field_name, "interfaces") or
1301             std.mem.eql(u8, field_name, "dynamic_traits");
1302     }
1303 
1304     fn namesFor(comptime Target: type) type {
1305         if (comptime isDialectType(Target)) return operationNames(Target);
1306         if (comptime isOperationNamesType(Target)) return Target;
1307         @compileError("opSpec.dialect expects a dialect type or operationNames result");
1308     }
1309 
1310     fn isDialectType(comptime Target: type) bool {
1311         if (!@hasDecl(Target, "name")) return false;
1312         return switch (@typeInfo(@TypeOf(@field(Target, "name")))) {
1313             .pointer => true,
1314             else => false,
1315         };
1316     }
1317 
1318     fn isOperationNamesType(comptime Target: type) bool {
1319         if (!@hasDecl(Target, "name")) return false;
1320         if (!@hasDecl(Target, "state")) return false;
1321         return switch (@typeInfo(@TypeOf(@field(Target, "name")))) {
1322             .@"fn" => true,
1323             else => false,
1324         };
1325     }
1326 };
1327 
1328 pub const typeConstraint = struct {
1329     pub fn exact(comptime index: usize, comptime type_name: []const u8) interfaces.OperationTypeConstraint {
1330         return .{
1331             .index = index,
1332             .type_name = type_name,
1333         };
1334     }
1335 
1336     pub fn parameterized(comptime index: usize, comptime type_name: []const u8) interfaces.OperationTypeConstraint {
1337         return .{
1338             .index = index,
1339             .type_name = type_name,
1340             .allow_parameterized = true,
1341         };
1342     }
1343 };
1344 
1345 pub fn operationAttribute(comptime spec: OperationSpec, comptime attr_name: []const u8) AttributeSpec {
1346     inline for (spec.attribute_specs) |attribute_spec| {
1347         if (comptime std.mem.eql(u8, attribute_spec.name, attr_name)) return attribute_spec;
1348     }
1349     @compileError("unknown operation attribute: " ++ attr_name);
1350 }
1351 
1352 pub fn operationDialectAttributeName(comptime spec: OperationSpec, comptime attr_name: []const u8) []const u8 {
1353     const attribute_spec = comptime operationAttribute(spec, attr_name);
1354     return switch (attribute_spec.storage) {
1355         .dialect => |dialect_attr_name| dialect_attr_name,
1356         .any => @compileError("operation attribute does not declare dialect storage: " ++ attr_name),
1357         else => @compileError("operation attribute is not dialect-backed: " ++ attr_name),
1358     };
1359 }
1360 
1361 fn validateOperationAttributeStorage(comptime spec: OperationSpec, comptime attr_name: []const u8, comptime expected: std.meta.Tag(AttributeStorage)) void {
1362     const attribute_spec = comptime operationAttribute(spec, attr_name);
1363     const actual = comptime std.meta.activeTag(attribute_spec.storage);
1364     if (actual == expected) return;
1365     if (actual == .any) @compileError("operation attribute does not declare storage kind: " ++ attr_name);
1366     @compileError("operation attribute storage kind does not match accessor: " ++ attr_name);
1367 }
1368 
1369 fn operationAttributeAccessors(comptime OpType: type, comptime spec: OperationSpec) type {
1370     return struct {
1371         pub fn setI64Attr(self: OpType, comptime attr_name: []const u8, value: i64) !void {
1372             validateOperationAttributeStorage(spec, attr_name, .i64);
1373             try self.op.setAttr(attr_name, try self.op.getContext().getI64Attr(value));
1374         }
1375 
1376         pub fn getI64Attr(self: OpType, comptime attr_name: []const u8) ?i64 {
1377             validateOperationAttributeStorage(spec, attr_name, .i64);
1378             const attribute_value = self.op.getAttrAs(Attribute.IntegerAttr, attr_name) orelse return null;
1379             return attribute_value.getValue();
1380         }
1381 
1382         pub fn setBoolAttr(self: OpType, comptime attr_name: []const u8, value: bool) !void {
1383             validateOperationAttributeStorage(spec, attr_name, .bool);
1384             try self.op.setAttr(attr_name, try self.op.getContext().getBoolAttr(value));
1385         }
1386 
1387         pub fn getBoolAttr(self: OpType, comptime attr_name: []const u8) ?bool {
1388             validateOperationAttributeStorage(spec, attr_name, .bool);
1389             const attribute_value = self.op.getAttrAs(Attribute.BoolAttr, attr_name) orelse return null;
1390             return attribute_value.getValue();
1391         }
1392 
1393         pub fn setStringAttr(self: OpType, comptime attr_name: []const u8, value: []const u8) !void {
1394             validateOperationAttributeStorage(spec, attr_name, .string);
1395             try self.op.setAttr(attr_name, try self.op.getContext().getStringAttr(value));
1396         }
1397 
1398         pub fn getStringAttr(self: OpType, comptime attr_name: []const u8) ?[]const u8 {
1399             validateOperationAttributeStorage(spec, attr_name, .string);
1400             const attribute_value = self.op.getAttrAs(Attribute.StringAttr, attr_name) orelse return null;
1401             return attribute_value.getValue();
1402         }
1403 
1404         pub fn setDialectAttrPayload(self: OpType, comptime attr_name: []const u8, payload: []const u8) !void {
1405             try self.op.setAttr(attr_name, try self.op.getContext().getDialectAttr(operationDialectAttributeName(spec, attr_name), payload));
1406         }
1407 
1408         pub fn getDialectAttrPayload(self: OpType, comptime attr_name: []const u8) ?[]const u8 {
1409             const attribute_value = self.op.getAttr(attr_name) orelse return null;
1410             if (!std.mem.eql(u8, attribute_value.abstract.name, operationDialectAttributeName(spec, attr_name))) return null;
1411             const dialect_attr = attribute_value.cast(Attribute.DialectAttr) orelse return null;
1412             return dialect_attr.payload;
1413         }
1414 
1415         pub fn dialectAttrName(comptime attr_name: []const u8) []const u8 {
1416             return operationDialectAttributeName(spec, attr_name);
1417         }
1418     };
1419 }
1420 
1421 pub const operationTemplate = struct {
1422     pub const FoldFn = *const fn (
1423         op: *const anyopaque,
1424         results: *interfaces.FoldResults,
1425     ) anyerror!void;
1426 
1427     pub const FixedResultTypeFn = *const fn (ctx: *Context) anyerror!Type;
1428 
1429     pub fn dialect(comptime DialectType: type) type {
1430         return struct {
1431             pub fn binarySameType(
1432                 comptime mnemonic: []const u8,
1433                 comptime options: opSpec.Options,
1434             ) type {
1435                 return operationTemplate.binarySameType(DialectType, mnemonic, options);
1436             }
1437 
1438             pub fn binarySameTypeFold(
1439                 comptime mnemonic: []const u8,
1440                 comptime options: opSpec.Options,
1441                 comptime fold_fn: FoldFn,
1442             ) type {
1443                 return operationTemplate.binarySameTypeFold(DialectType, mnemonic, options, fold_fn);
1444             }
1445 
1446             pub fn binaryFixedResult(
1447                 comptime mnemonic: []const u8,
1448                 comptime options: opSpec.Options,
1449                 comptime result_type_fn: FixedResultTypeFn,
1450             ) type {
1451                 return operationTemplate.binaryFixedResult(DialectType, mnemonic, options, result_type_fn);
1452             }
1453 
1454             pub fn unarySameType(
1455                 comptime mnemonic: []const u8,
1456                 comptime options: opSpec.Options,
1457             ) type {
1458                 return operationTemplate.unarySameType(DialectType, mnemonic, options);
1459             }
1460 
1461             pub fn unarySameTypeFold(
1462                 comptime mnemonic: []const u8,
1463                 comptime options: opSpec.Options,
1464                 comptime fold_fn: FoldFn,
1465             ) type {
1466                 return operationTemplate.unarySameTypeFold(DialectType, mnemonic, options, fold_fn);
1467             }
1468 
1469             pub fn unaryFixedResult(
1470                 comptime mnemonic: []const u8,
1471                 comptime options: opSpec.Options,
1472                 comptime result_type_fn: FixedResultTypeFn,
1473             ) type {
1474                 return operationTemplate.unaryFixedResult(DialectType, mnemonic, options, result_type_fn);
1475             }
1476 
1477             pub fn unarySameTypeStringAttr(
1478                 comptime mnemonic: []const u8,
1479                 comptime attr_name: []const u8,
1480                 comptime attr_value: []const u8,
1481                 comptime options: opSpec.Options,
1482             ) type {
1483                 return operationTemplate.unarySameTypeStringAttr(DialectType, mnemonic, attr_name, attr_value, options);
1484             }
1485 
1486             pub fn unaryExplicitTypeFold(
1487                 comptime mnemonic: []const u8,
1488                 comptime options: opSpec.Options,
1489                 comptime fold_fn: FoldFn,
1490             ) type {
1491                 return operationTemplate.unaryExplicitTypeFold(DialectType, mnemonic, options, fold_fn);
1492             }
1493 
1494             pub fn unaryExplicitType(
1495                 comptime mnemonic: []const u8,
1496                 comptime options: opSpec.Options,
1497             ) type {
1498                 return operationTemplate.unaryExplicitType(DialectType, mnemonic, options);
1499             }
1500 
1501             pub fn unaryNoResult(
1502                 comptime mnemonic: []const u8,
1503                 comptime options: opSpec.Options,
1504             ) type {
1505                 return operationTemplate.unaryNoResult(DialectType, mnemonic, options);
1506             }
1507 
1508             pub fn ternarySameType(
1509                 comptime mnemonic: []const u8,
1510                 comptime options: opSpec.Options,
1511             ) type {
1512                 return operationTemplate.ternarySameType(DialectType, mnemonic, options);
1513             }
1514 
1515             pub fn ternarySameTypeFold(
1516                 comptime mnemonic: []const u8,
1517                 comptime options: opSpec.Options,
1518                 comptime fold_fn: FoldFn,
1519             ) type {
1520                 return operationTemplate.ternarySameTypeFold(DialectType, mnemonic, options, fold_fn);
1521             }
1522 
1523             pub fn selectSameType(
1524                 comptime mnemonic: []const u8,
1525                 comptime options: opSpec.Options,
1526             ) type {
1527                 return operationTemplate.selectSameType(DialectType, mnemonic, options);
1528             }
1529 
1530             pub fn selectSameTypeFold(
1531                 comptime mnemonic: []const u8,
1532                 comptime options: opSpec.Options,
1533                 comptime fold_fn: FoldFn,
1534             ) type {
1535                 return operationTemplate.selectSameTypeFold(DialectType, mnemonic, options, fold_fn);
1536             }
1537 
1538             pub fn explicitLeaf(
1539                 comptime OpType: type,
1540                 comptime spec: anytype,
1541             ) type {
1542                 return operationTemplate.explicitLeaf(DialectType, OpType, spec);
1543             }
1544 
1545             pub fn explicit(
1546                 comptime OpType: type,
1547                 comptime spec: anytype,
1548             ) type {
1549                 return operationTemplate.explicit(DialectType, OpType, spec);
1550             }
1551 
1552             pub fn explicitTerminator(
1553                 comptime OpType: type,
1554                 comptime spec: anytype,
1555             ) type {
1556                 return operationTemplate.explicitTerminator(DialectType, OpType, spec);
1557             }
1558         };
1559     }
1560 
1561     pub fn binarySameType(
1562         comptime DialectType: type,
1563         comptime mnemonic: []const u8,
1564         comptime options: opSpec.Options,
1565     ) type {
1566         return binarySameTypeImpl(DialectType, mnemonic, options, null);
1567     }
1568 
1569     pub fn binarySameTypeFold(
1570         comptime DialectType: type,
1571         comptime mnemonic: []const u8,
1572         comptime options: opSpec.Options,
1573         comptime fold_fn: FoldFn,
1574     ) type {
1575         return binarySameTypeImpl(DialectType, mnemonic, options, fold_fn);
1576     }
1577 
1578     pub fn binaryFixedResult(
1579         comptime DialectType: type,
1580         comptime mnemonic: []const u8,
1581         comptime options: opSpec.Options,
1582         comptime result_type_fn: FixedResultTypeFn,
1583     ) type {
1584         return binaryFixedResultImpl(DialectType, mnemonic, options, result_type_fn);
1585     }
1586 
1587     pub fn unarySameType(
1588         comptime DialectType: type,
1589         comptime mnemonic: []const u8,
1590         comptime options: opSpec.Options,
1591     ) type {
1592         return unarySameTypeImpl(DialectType, mnemonic, options, null);
1593     }
1594 
1595     pub fn unarySameTypeFold(
1596         comptime DialectType: type,
1597         comptime mnemonic: []const u8,
1598         comptime options: opSpec.Options,
1599         comptime fold_fn: FoldFn,
1600     ) type {
1601         return unarySameTypeImpl(DialectType, mnemonic, options, fold_fn);
1602     }
1603 
1604     pub fn unaryFixedResult(
1605         comptime DialectType: type,
1606         comptime mnemonic: []const u8,
1607         comptime options: opSpec.Options,
1608         comptime result_type_fn: FixedResultTypeFn,
1609     ) type {
1610         return unaryFixedResultImpl(DialectType, mnemonic, options, result_type_fn);
1611     }
1612 
1613     pub fn unarySameTypeStringAttr(
1614         comptime DialectType: type,
1615         comptime mnemonic: []const u8,
1616         comptime attr_name: []const u8,
1617         comptime attr_value: []const u8,
1618         comptime options: opSpec.Options,
1619     ) type {
1620         return unarySameTypeStringAttrImpl(DialectType, mnemonic, attr_name, attr_value, options);
1621     }
1622 
1623     pub fn unaryExplicitTypeFold(
1624         comptime DialectType: type,
1625         comptime mnemonic: []const u8,
1626         comptime options: opSpec.Options,
1627         comptime fold_fn: FoldFn,
1628     ) type {
1629         return unaryExplicitTypeImpl(DialectType, mnemonic, options, fold_fn);
1630     }
1631 
1632     pub fn unaryExplicitType(
1633         comptime DialectType: type,
1634         comptime mnemonic: []const u8,
1635         comptime options: opSpec.Options,
1636     ) type {
1637         return unaryExplicitTypeImpl(DialectType, mnemonic, options, null);
1638     }
1639 
1640     pub fn unaryNoResult(
1641         comptime DialectType: type,
1642         comptime mnemonic: []const u8,
1643         comptime options: opSpec.Options,
1644     ) type {
1645         return unaryNoResultImpl(DialectType, mnemonic, options);
1646     }
1647 
1648     pub fn ternarySameType(
1649         comptime DialectType: type,
1650         comptime mnemonic: []const u8,
1651         comptime options: opSpec.Options,
1652     ) type {
1653         return ternarySameTypeImpl(DialectType, mnemonic, options, null);
1654     }
1655 
1656     pub fn ternarySameTypeFold(
1657         comptime DialectType: type,
1658         comptime mnemonic: []const u8,
1659         comptime options: opSpec.Options,
1660         comptime fold_fn: FoldFn,
1661     ) type {
1662         return ternarySameTypeImpl(DialectType, mnemonic, options, fold_fn);
1663     }
1664 
1665     pub fn selectSameType(
1666         comptime DialectType: type,
1667         comptime mnemonic: []const u8,
1668         comptime options: opSpec.Options,
1669     ) type {
1670         return selectSameTypeImpl(DialectType, mnemonic, options, null);
1671     }
1672 
1673     pub fn selectSameTypeFold(
1674         comptime DialectType: type,
1675         comptime mnemonic: []const u8,
1676         comptime options: opSpec.Options,
1677         comptime fold_fn: FoldFn,
1678     ) type {
1679         return selectSameTypeImpl(DialectType, mnemonic, options, fold_fn);
1680     }
1681 
1682     pub fn explicitLeaf(
1683         comptime DialectType: type,
1684         comptime OpType: type,
1685         comptime spec: anytype,
1686     ) type {
1687         const op_specs = opSpec.dialect(DialectType);
1688         const generated_spec = op_specs.leaf(spec);
1689         const accessors = operationAttributeAccessors(OpType, generated_spec);
1690         return struct {
1691             pub const operation_spec = generated_spec;
1692             pub const operation_name = generated_spec.name;
1693             pub const setI64Attr = accessors.setI64Attr;
1694             pub const getI64Attr = accessors.getI64Attr;
1695             pub const setBoolAttr = accessors.setBoolAttr;
1696             pub const getBoolAttr = accessors.getBoolAttr;
1697             pub const setStringAttr = accessors.setStringAttr;
1698             pub const getStringAttr = accessors.getStringAttr;
1699             pub const setDialectAttrPayload = accessors.setDialectAttrPayload;
1700             pub const getDialectAttrPayload = accessors.getDialectAttrPayload;
1701             pub const dialectAttrName = accessors.dialectAttrName;
1702 
1703             pub fn createLeaf(
1704                 ctx: *Context,
1705                 loc: Location,
1706                 operands: []const *IrValue,
1707                 result_types: []const Type,
1708             ) !OpType {
1709                 var builder = OperationBuilder.init(ctx);
1710                 var state = Operation.State.init(operation_name, loc);
1711                 state.addOperands(operands);
1712                 state.addTypes(result_types);
1713                 const op = try builder.create(state);
1714                 return .{ .op = op };
1715             }
1716 
1717             pub fn getOperand(self: OpType, comptime component_name: []const u8) *IrValue {
1718                 return operand(operation_spec, self.op, component_name);
1719             }
1720 
1721             pub fn getOptionalOperand(self: OpType, comptime component_name: []const u8) ?*IrValue {
1722                 return optionalOperand(operation_spec, self.op, component_name);
1723             }
1724 
1725             pub fn getOperandSegment(self: OpType, comptime component_name: []const u8) ?[]const *IrValue {
1726                 return operandSegmentValues(operation_spec, self.op, component_name);
1727             }
1728 
1729             pub fn getOperandSegmentValue(self: OpType, comptime component_name: []const u8) ?*IrValue {
1730                 return operandSegmentValue(operation_spec, self.op, component_name);
1731             }
1732 
1733             pub fn getNamedResult(self: OpType, comptime component_name: []const u8) *IrValue {
1734                 return result(operation_spec, self.op, component_name);
1735             }
1736 
1737             pub fn getOptionalResult(self: OpType, comptime component_name: []const u8) ?*IrValue {
1738                 return optionalResult(operation_spec, self.op, component_name);
1739             }
1740 
1741             pub fn getResultSegment(self: OpType, comptime component_name: []const u8) ?[]IrValue {
1742                 return resultSegmentValues(operation_spec, self.op, component_name);
1743             }
1744 
1745             pub fn getResultSegmentValue(self: OpType, comptime component_name: []const u8) ?*IrValue {
1746                 return resultSegmentValue(operation_spec, self.op, component_name);
1747             }
1748 
1749             pub fn getResult(self: OpType) *IrValue {
1750                 if (operation_spec.result_names.len != 1) {
1751                     @compileError("operationTemplate.explicitLeaf getResult requires exactly one named result");
1752                 }
1753                 return result(operation_spec, self.op, operation_spec.result_names[0]);
1754             }
1755         };
1756     }
1757 
1758     pub fn explicit(
1759         comptime DialectType: type,
1760         comptime OpType: type,
1761         comptime spec: anytype,
1762     ) type {
1763         const op_specs = opSpec.dialect(DialectType);
1764         const generated_spec = op_specs.define(spec);
1765         const accessors = operationAttributeAccessors(OpType, generated_spec);
1766         return struct {
1767             pub const operation_spec = generated_spec;
1768             pub const operation_name = generated_spec.name;
1769             pub const setI64Attr = accessors.setI64Attr;
1770             pub const getI64Attr = accessors.getI64Attr;
1771             pub const setBoolAttr = accessors.setBoolAttr;
1772             pub const getBoolAttr = accessors.getBoolAttr;
1773             pub const setStringAttr = accessors.setStringAttr;
1774             pub const getStringAttr = accessors.getStringAttr;
1775             pub const setDialectAttrPayload = accessors.setDialectAttrPayload;
1776             pub const getDialectAttrPayload = accessors.getDialectAttrPayload;
1777             pub const dialectAttrName = accessors.dialectAttrName;
1778 
1779             pub fn createOperation(
1780                 ctx: *Context,
1781                 loc: Location,
1782                 operands: []const *IrValue,
1783                 result_types: []const Type,
1784                 region_bodies: []const *Region,
1785                 successors: []const *Block,
1786             ) !OpType {
1787                 var builder = OperationBuilder.init(ctx);
1788                 var state = Operation.State.init(operation_name, loc);
1789                 state.addOperands(operands);
1790                 state.addTypes(result_types);
1791                 state.addRegionBodies(region_bodies);
1792                 state.addSuccessors(successors);
1793                 const op = try builder.create(state);
1794                 return .{ .op = op };
1795             }
1796 
1797             pub fn getOperand(self: OpType, comptime component_name: []const u8) *IrValue {
1798                 return operand(operation_spec, self.op, component_name);
1799             }
1800 
1801             pub fn getOptionalOperand(self: OpType, comptime component_name: []const u8) ?*IrValue {
1802                 return optionalOperand(operation_spec, self.op, component_name);
1803             }
1804 
1805             pub fn getOperandSegment(self: OpType, comptime component_name: []const u8) ?[]const *IrValue {
1806                 return operandSegmentValues(operation_spec, self.op, component_name);
1807             }
1808 
1809             pub fn getOperandSegmentValue(self: OpType, comptime component_name: []const u8) ?*IrValue {
1810                 return operandSegmentValue(operation_spec, self.op, component_name);
1811             }
1812 
1813             pub fn getNamedResult(self: OpType, comptime component_name: []const u8) *IrValue {
1814                 return result(operation_spec, self.op, component_name);
1815             }
1816 
1817             pub fn getOptionalResult(self: OpType, comptime component_name: []const u8) ?*IrValue {
1818                 return optionalResult(operation_spec, self.op, component_name);
1819             }
1820 
1821             pub fn getResultSegment(self: OpType, comptime component_name: []const u8) ?[]IrValue {
1822                 return resultSegmentValues(operation_spec, self.op, component_name);
1823             }
1824 
1825             pub fn getResultSegmentValue(self: OpType, comptime component_name: []const u8) ?*IrValue {
1826                 return resultSegmentValue(operation_spec, self.op, component_name);
1827             }
1828 
1829             pub fn getRegion(self: OpType, comptime component_name: []const u8) *Region {
1830                 return region(operation_spec, self.op, component_name);
1831             }
1832 
1833             pub fn getOptionalRegion(self: OpType, comptime component_name: []const u8) ?*Region {
1834                 return optionalRegion(operation_spec, self.op, component_name);
1835             }
1836 
1837             pub fn getSuccessor(self: OpType, comptime component_name: []const u8) *Block {
1838                 return successor(operation_spec, self.op, component_name);
1839             }
1840 
1841             pub fn getOptionalSuccessor(self: OpType, comptime component_name: []const u8) ?*Block {
1842                 return optionalSuccessor(operation_spec, self.op, component_name);
1843             }
1844         };
1845     }
1846 
1847     pub fn explicitTerminator(
1848         comptime DialectType: type,
1849         comptime OpType: type,
1850         comptime spec: anytype,
1851     ) type {
1852         const op_specs = opSpec.dialect(DialectType);
1853         const generated_spec = op_specs.terminator(spec);
1854         const accessors = operationAttributeAccessors(OpType, generated_spec);
1855         return struct {
1856             pub const operation_spec = generated_spec;
1857             pub const operation_name = generated_spec.name;
1858             pub const setI64Attr = accessors.setI64Attr;
1859             pub const getI64Attr = accessors.getI64Attr;
1860             pub const setBoolAttr = accessors.setBoolAttr;
1861             pub const getBoolAttr = accessors.getBoolAttr;
1862             pub const setStringAttr = accessors.setStringAttr;
1863             pub const getStringAttr = accessors.getStringAttr;
1864             pub const setDialectAttrPayload = accessors.setDialectAttrPayload;
1865             pub const getDialectAttrPayload = accessors.getDialectAttrPayload;
1866             pub const dialectAttrName = accessors.dialectAttrName;
1867 
1868             pub fn createTerminator(
1869                 ctx: *Context,
1870                 loc: Location,
1871                 operands: []const *IrValue,
1872                 successors: []const *Block,
1873             ) !OpType {
1874                 var builder = OperationBuilder.init(ctx);
1875                 var state = Operation.State.init(operation_name, loc);
1876                 state.addOperands(operands);
1877                 state.addSuccessors(successors);
1878                 const op = try builder.create(state);
1879                 return .{ .op = op };
1880             }
1881 
1882             pub fn getOperand(self: OpType, comptime component_name: []const u8) *IrValue {
1883                 return operand(operation_spec, self.op, component_name);
1884             }
1885 
1886             pub fn getOptionalOperand(self: OpType, comptime component_name: []const u8) ?*IrValue {
1887                 return optionalOperand(operation_spec, self.op, component_name);
1888             }
1889 
1890             pub fn getOperandSegment(self: OpType, comptime component_name: []const u8) ?[]const *IrValue {
1891                 return operandSegmentValues(operation_spec, self.op, component_name);
1892             }
1893 
1894             pub fn getOperandSegmentValue(self: OpType, comptime component_name: []const u8) ?*IrValue {
1895                 return operandSegmentValue(operation_spec, self.op, component_name);
1896             }
1897 
1898             pub fn getSuccessor(self: OpType, comptime component_name: []const u8) *Block {
1899                 return successor(operation_spec, self.op, component_name);
1900             }
1901 
1902             pub fn getOptionalSuccessor(self: OpType, comptime component_name: []const u8) ?*Block {
1903                 return optionalSuccessor(operation_spec, self.op, component_name);
1904             }
1905         };
1906     }
1907 
1908     fn binarySameTypeImpl(
1909         comptime DialectType: type,
1910         comptime mnemonic: []const u8,
1911         comptime options: opSpec.Options,
1912         comptime fold_fn: ?FoldFn,
1913     ) type {
1914         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(2, 1), options);
1915         if (fold_fn) |fold_impl| {
1916             return struct {
1917                 op: *Operation,
1918 
1919                 pub const operation_spec = generated_spec;
1920                 pub const operation_name = operation_spec.name;
1921                 pub const fold = fold_impl;
1922 
1923                 pub fn create(ctx: *Context, loc: Location, lhs: *IrValue, rhs: *IrValue) !@This() {
1924                     var builder = OperationBuilder.init(ctx);
1925                     var state = Operation.State.init(operation_name, loc);
1926                     state.addOperands(&.{ lhs, rhs });
1927                     state.addTypes(&.{lhs.type});
1928                     const op = try builder.create(state);
1929                     return .{ .op = op };
1930                 }
1931 
1932                 pub fn getResult(self: *const @This()) *IrValue {
1933                     return self.op.getResult(0).?;
1934                 }
1935 
1936                 pub fn getLhs(self: @This()) *IrValue {
1937                     return self.op.getOperand(0).?;
1938                 }
1939 
1940                 pub fn getRhs(self: @This()) *IrValue {
1941                     return self.op.getOperand(1).?;
1942                 }
1943             };
1944         }
1945 
1946         return struct {
1947             op: *Operation,
1948 
1949             pub const operation_spec = generated_spec;
1950             pub const operation_name = operation_spec.name;
1951 
1952             pub fn create(ctx: *Context, loc: Location, lhs: *IrValue, rhs: *IrValue) !@This() {
1953                 var builder = OperationBuilder.init(ctx);
1954                 var state = Operation.State.init(operation_name, loc);
1955                 state.addOperands(&.{ lhs, rhs });
1956                 state.addTypes(&.{lhs.type});
1957                 const op = try builder.create(state);
1958                 return .{ .op = op };
1959             }
1960 
1961             pub fn getResult(self: *const @This()) *IrValue {
1962                 return self.op.getResult(0).?;
1963             }
1964 
1965             pub fn getLhs(self: @This()) *IrValue {
1966                 return self.op.getOperand(0).?;
1967             }
1968 
1969             pub fn getRhs(self: @This()) *IrValue {
1970                 return self.op.getOperand(1).?;
1971             }
1972         };
1973     }
1974 
1975     fn binaryFixedResultImpl(
1976         comptime DialectType: type,
1977         comptime mnemonic: []const u8,
1978         comptime options: opSpec.Options,
1979         comptime result_type_fn: FixedResultTypeFn,
1980     ) type {
1981         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(2, 1), options);
1982         return struct {
1983             op: *Operation,
1984 
1985             pub const operation_spec = generated_spec;
1986             pub const operation_name = operation_spec.name;
1987 
1988             pub fn create(ctx: *Context, loc: Location, lhs: *IrValue, rhs: *IrValue) !@This() {
1989                 var builder = OperationBuilder.init(ctx);
1990                 var state = Operation.State.init(operation_name, loc);
1991                 state.addOperands(&.{ lhs, rhs });
1992                 state.addTypes(&.{try result_type_fn(ctx)});
1993                 const op = try builder.create(state);
1994                 return .{ .op = op };
1995             }
1996 
1997             pub fn getResult(self: *const @This()) *IrValue {
1998                 return self.op.getResult(0).?;
1999             }
2000 
2001             pub fn getLhs(self: @This()) *IrValue {
2002                 return self.op.getOperand(0).?;
2003             }
2004 
2005             pub fn getRhs(self: @This()) *IrValue {
2006                 return self.op.getOperand(1).?;
2007             }
2008         };
2009     }
2010 
2011     fn unarySameTypeImpl(
2012         comptime DialectType: type,
2013         comptime mnemonic: []const u8,
2014         comptime options: opSpec.Options,
2015         comptime fold_fn: ?FoldFn,
2016     ) type {
2017         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(1, 1), options);
2018         if (fold_fn) |fold_impl| {
2019             return struct {
2020                 op: *Operation,
2021 
2022                 pub const operation_spec = generated_spec;
2023                 pub const operation_name = operation_spec.name;
2024                 pub const fold = fold_impl;
2025 
2026                 pub fn create(ctx: *Context, loc: Location, input: *IrValue) !@This() {
2027                     var builder = OperationBuilder.init(ctx);
2028                     var state = Operation.State.init(operation_name, loc);
2029                     state.addOperands(&.{input});
2030                     state.addTypes(&.{input.type});
2031                     const op = try builder.create(state);
2032                     return .{ .op = op };
2033                 }
2034 
2035                 pub fn getResult(self: *const @This()) *IrValue {
2036                     return self.op.getResult(0).?;
2037                 }
2038 
2039                 pub fn getInput(self: @This()) *IrValue {
2040                     return self.op.getOperand(0).?;
2041                 }
2042             };
2043         }
2044 
2045         return struct {
2046             op: *Operation,
2047 
2048             pub const operation_spec = generated_spec;
2049             pub const operation_name = operation_spec.name;
2050 
2051             pub fn create(ctx: *Context, loc: Location, input: *IrValue) !@This() {
2052                 var builder = OperationBuilder.init(ctx);
2053                 var state = Operation.State.init(operation_name, loc);
2054                 state.addOperands(&.{input});
2055                 state.addTypes(&.{input.type});
2056                 const op = try builder.create(state);
2057                 return .{ .op = op };
2058             }
2059 
2060             pub fn getResult(self: *const @This()) *IrValue {
2061                 return self.op.getResult(0).?;
2062             }
2063 
2064             pub fn getInput(self: @This()) *IrValue {
2065                 return self.op.getOperand(0).?;
2066             }
2067         };
2068     }
2069 
2070     fn unaryFixedResultImpl(
2071         comptime DialectType: type,
2072         comptime mnemonic: []const u8,
2073         comptime options: opSpec.Options,
2074         comptime result_type_fn: FixedResultTypeFn,
2075     ) type {
2076         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(1, 1), options);
2077         return struct {
2078             op: *Operation,
2079 
2080             pub const operation_spec = generated_spec;
2081             pub const operation_name = operation_spec.name;
2082 
2083             pub fn create(ctx: *Context, loc: Location, input: *IrValue) !@This() {
2084                 var builder = OperationBuilder.init(ctx);
2085                 var state = Operation.State.init(operation_name, loc);
2086                 state.addOperands(&.{input});
2087                 state.addTypes(&.{try result_type_fn(ctx)});
2088                 const op = try builder.create(state);
2089                 return .{ .op = op };
2090             }
2091 
2092             pub fn getResult(self: *const @This()) *IrValue {
2093                 return self.op.getResult(0).?;
2094             }
2095 
2096             pub fn getInput(self: @This()) *IrValue {
2097                 return self.op.getOperand(0).?;
2098             }
2099         };
2100     }
2101 
2102     fn unarySameTypeStringAttrImpl(
2103         comptime DialectType: type,
2104         comptime mnemonic: []const u8,
2105         comptime attr_name: []const u8,
2106         comptime attr_value: []const u8,
2107         comptime options: opSpec.Options,
2108     ) type {
2109         const generated_options = optionsWithAttr(options, attr_name);
2110         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(1, 1), generated_options);
2111         return struct {
2112             op: *Operation,
2113 
2114             pub const operation_spec = generated_spec;
2115             pub const operation_name = operation_spec.name;
2116 
2117             pub fn create(ctx: *Context, loc: Location, input: *IrValue) !@This() {
2118                 var builder = OperationBuilder.init(ctx);
2119                 var state = Operation.State.init(operation_name, loc);
2120                 state.addOperands(&.{input});
2121                 state.addTypes(&.{input.type});
2122                 const op = try builder.create(state);
2123                 try op.setAttr(attr_name, try ctx.getStringAttr(attr_value));
2124                 return .{ .op = op };
2125             }
2126 
2127             pub fn getResult(self: *const @This()) *IrValue {
2128                 return self.op.getResult(0).?;
2129             }
2130 
2131             pub fn getInput(self: @This()) *IrValue {
2132                 return self.op.getOperand(0).?;
2133             }
2134 
2135             pub fn getStringAttr(self: @This()) ?[]const u8 {
2136                 const attr = self.op.getAttrAs(Attribute.StringAttr, attr_name) orelse return null;
2137                 return attr.getValue();
2138             }
2139         };
2140     }
2141 
2142     fn unaryExplicitTypeImpl(
2143         comptime DialectType: type,
2144         comptime mnemonic: []const u8,
2145         comptime options: opSpec.Options,
2146         comptime fold_fn: ?FoldFn,
2147     ) type {
2148         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(1, 1), options);
2149         if (fold_fn) |fold_impl| {
2150             return struct {
2151                 op: *Operation,
2152 
2153                 pub const operation_spec = generated_spec;
2154                 pub const operation_name = operation_spec.name;
2155                 pub const fold = fold_impl;
2156 
2157                 pub fn create(ctx: *Context, loc: Location, input: *IrValue, result_type: Type) !@This() {
2158                     var builder = OperationBuilder.init(ctx);
2159                     var state = Operation.State.init(operation_name, loc);
2160                     state.addOperands(&.{input});
2161                     state.addTypes(&.{result_type});
2162                     const op = try builder.create(state);
2163                     return .{ .op = op };
2164                 }
2165 
2166                 pub fn getResult(self: *const @This()) *IrValue {
2167                     return self.op.getResult(0).?;
2168                 }
2169 
2170                 pub fn getInput(self: @This()) *IrValue {
2171                     return self.op.getOperand(0).?;
2172                 }
2173             };
2174         }
2175 
2176         return struct {
2177             op: *Operation,
2178 
2179             pub const operation_spec = generated_spec;
2180             pub const operation_name = operation_spec.name;
2181 
2182             pub fn create(ctx: *Context, loc: Location, input: *IrValue, result_type: Type) !@This() {
2183                 var builder = OperationBuilder.init(ctx);
2184                 var state = Operation.State.init(operation_name, loc);
2185                 state.addOperands(&.{input});
2186                 state.addTypes(&.{result_type});
2187                 const op = try builder.create(state);
2188                 return .{ .op = op };
2189             }
2190 
2191             pub fn getResult(self: *const @This()) *IrValue {
2192                 return self.op.getResult(0).?;
2193             }
2194 
2195             pub fn getInput(self: @This()) *IrValue {
2196                 return self.op.getOperand(0).?;
2197             }
2198         };
2199     }
2200 
2201     fn unaryNoResultImpl(
2202         comptime DialectType: type,
2203         comptime mnemonic: []const u8,
2204         comptime options: opSpec.Options,
2205     ) type {
2206         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(1, 0), options);
2207         return struct {
2208             op: *Operation,
2209 
2210             pub const operation_spec = generated_spec;
2211             pub const operation_name = operation_spec.name;
2212 
2213             pub fn create(ctx: *Context, loc: Location, input: *IrValue) !@This() {
2214                 var builder = OperationBuilder.init(ctx);
2215                 var state = Operation.State.init(operation_name, loc);
2216                 state.addOperands(&.{input});
2217                 const op = try builder.create(state);
2218                 return .{ .op = op };
2219             }
2220 
2221             pub fn getInput(self: @This()) *IrValue {
2222                 return self.op.getOperand(0).?;
2223             }
2224         };
2225     }
2226 
2227     fn ternarySameTypeImpl(
2228         comptime DialectType: type,
2229         comptime mnemonic: []const u8,
2230         comptime options: opSpec.Options,
2231         comptime fold_fn: ?FoldFn,
2232     ) type {
2233         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(3, 1), options);
2234         if (fold_fn) |fold_impl| {
2235             return struct {
2236                 op: *Operation,
2237 
2238                 pub const operation_spec = generated_spec;
2239                 pub const operation_name = operation_spec.name;
2240                 pub const fold = fold_impl;
2241 
2242                 pub fn create(ctx: *Context, loc: Location, a: *IrValue, b: *IrValue, c: *IrValue) !@This() {
2243                     var builder = OperationBuilder.init(ctx);
2244                     var state = Operation.State.init(operation_name, loc);
2245                     state.addOperands(&.{ a, b, c });
2246                     state.addTypes(&.{a.type});
2247                     const op = try builder.create(state);
2248                     return .{ .op = op };
2249                 }
2250 
2251                 pub fn getResult(self: *const @This()) *IrValue {
2252                     return self.op.getResult(0).?;
2253                 }
2254 
2255                 pub fn getA(self: @This()) *IrValue {
2256                     return self.op.getOperand(0).?;
2257                 }
2258 
2259                 pub fn getB(self: @This()) *IrValue {
2260                     return self.op.getOperand(1).?;
2261                 }
2262 
2263                 pub fn getC(self: @This()) *IrValue {
2264                     return self.op.getOperand(2).?;
2265                 }
2266             };
2267         }
2268 
2269         return struct {
2270             op: *Operation,
2271 
2272             pub const operation_spec = generated_spec;
2273             pub const operation_name = operation_spec.name;
2274 
2275             pub fn create(ctx: *Context, loc: Location, a: *IrValue, b: *IrValue, c: *IrValue) !@This() {
2276                 var builder = OperationBuilder.init(ctx);
2277                 var state = Operation.State.init(operation_name, loc);
2278                 state.addOperands(&.{ a, b, c });
2279                 state.addTypes(&.{a.type});
2280                 const op = try builder.create(state);
2281                 return .{ .op = op };
2282             }
2283 
2284             pub fn getResult(self: *const @This()) *IrValue {
2285                 return self.op.getResult(0).?;
2286             }
2287 
2288             pub fn getA(self: @This()) *IrValue {
2289                 return self.op.getOperand(0).?;
2290             }
2291 
2292             pub fn getB(self: @This()) *IrValue {
2293                 return self.op.getOperand(1).?;
2294             }
2295 
2296             pub fn getC(self: @This()) *IrValue {
2297                 return self.op.getOperand(2).?;
2298             }
2299         };
2300     }
2301 
2302     fn selectSameTypeImpl(
2303         comptime DialectType: type,
2304         comptime mnemonic: []const u8,
2305         comptime options: opSpec.Options,
2306         comptime fold_fn: ?FoldFn,
2307     ) type {
2308         const generated_spec = opSpec.shaped(operationName(DialectType.name, mnemonic), shape.leaf(3, 1), options);
2309         if (fold_fn) |fold_impl| {
2310             return struct {
2311                 op: *Operation,
2312 
2313                 pub const operation_spec = generated_spec;
2314                 pub const operation_name = operation_spec.name;
2315                 pub const fold = fold_impl;
2316 
2317                 pub fn create(ctx: *Context, loc: Location, condition: *IrValue, true_value: *IrValue, false_value: *IrValue) !@This() {
2318                     var builder = OperationBuilder.init(ctx);
2319                     var state = Operation.State.init(operation_name, loc);
2320                     state.addOperands(&.{ condition, true_value, false_value });
2321                     state.addTypes(&.{true_value.type});
2322                     const op = try builder.create(state);
2323                     return .{ .op = op };
2324                 }
2325 
2326                 pub fn getResult(self: *const @This()) *IrValue {
2327                     return self.op.getResult(0).?;
2328                 }
2329 
2330                 pub fn getCondition(self: @This()) *IrValue {
2331                     return self.op.getOperand(0).?;
2332                 }
2333 
2334                 pub fn getTrueValue(self: @This()) *IrValue {
2335                     return self.op.getOperand(1).?;
2336                 }
2337 
2338                 pub fn getFalseValue(self: @This()) *IrValue {
2339                     return self.op.getOperand(2).?;
2340                 }
2341             };
2342         }
2343 
2344         return struct {
2345             op: *Operation,
2346 
2347             pub const operation_spec = generated_spec;
2348             pub const operation_name = operation_spec.name;
2349 
2350             pub fn create(ctx: *Context, loc: Location, condition: *IrValue, true_value: *IrValue, false_value: *IrValue) !@This() {
2351                 var builder = OperationBuilder.init(ctx);
2352                 var state = Operation.State.init(operation_name, loc);
2353                 state.addOperands(&.{ condition, true_value, false_value });
2354                 state.addTypes(&.{true_value.type});
2355                 const op = try builder.create(state);
2356                 return .{ .op = op };
2357             }
2358 
2359             pub fn getResult(self: *const @This()) *IrValue {
2360                 return self.op.getResult(0).?;
2361             }
2362 
2363             pub fn getCondition(self: @This()) *IrValue {
2364                 return self.op.getOperand(0).?;
2365             }
2366 
2367             pub fn getTrueValue(self: @This()) *IrValue {
2368                 return self.op.getOperand(1).?;
2369             }
2370 
2371             pub fn getFalseValue(self: @This()) *IrValue {
2372                 return self.op.getOperand(2).?;
2373             }
2374         };
2375     }
2376 
2377     fn optionsWithAttr(comptime options: opSpec.Options, comptime attr_name: []const u8) opSpec.Options {
2378         return .{
2379             .traits = options.traits,
2380             .attrs = attrsWithName(options.attrs, attr_name),
2381             .required_attrs = attrsWithName(options.required_attrs, attr_name),
2382             .attribute_specs = attributeSpecsWithNames(options.attribute_specs, attrsWithName(options.attrs, attr_name)),
2383             .operand_segments = options.operand_segments,
2384             .result_segments = options.result_segments,
2385             .operand_types = options.operand_types,
2386             .result_types = options.result_types,
2387             .properties = options.properties,
2388             .interfaces = options.interfaces,
2389             .dynamic_traits = options.dynamic_traits,
2390         };
2391     }
2392 
2393     fn attrsWithName(comptime attrs: []const []const u8, comptime attr_name: []const u8) []const []const u8 {
2394         if (attr_name.len == 0) @compileError("operation attribute name cannot be empty");
2395         for (attrs) |existing| {
2396             if (std.mem.eql(u8, existing, attr_name)) @compileError("operation attribute name is duplicated");
2397         }
2398         const values = comptime blk: {
2399             var out: [attrs.len + 1][]const u8 = undefined;
2400             for (attrs, 0..) |attr, index| {
2401                 out[index] = attr;
2402             }
2403             out[attrs.len] = attr_name;
2404             break :blk out;
2405         };
2406         return &values;
2407     }
2408 };
2409 
2410 pub fn operationNames(comptime DialectType: type) type {
2411     return struct {
2412         pub fn name(comptime mnemonic: []const u8) []const u8 {
2413             return operationName(DialectType.name, mnemonic);
2414         }
2415 
2416         pub fn state(comptime OpType: type, loc: Location) Operation.State {
2417             return Operation.State.init(operationNameFor(OpType), loc);
2418         }
2419     };
2420 }
2421 
2422 pub fn operationNameFor(comptime OpType: type) []const u8 {
2423     if (@hasDecl(OpType, "operation_name")) return OpType.operation_name;
2424     if (@hasDecl(OpType, "operation_spec")) return OpType.operation_spec.name;
2425     if (nestedOperationSpec(OpType)) |spec| return spec.name;
2426     @compileError("operation type must declare operation_name, operation_spec, or a nested operation template");
2427 }
2428 
2429 pub fn trait(comptime Trait: type) OperationTraitSpec {
2430     return .{
2431         .id = Trait.id,
2432         .entry = Trait.entry(),
2433         .traits = if (@hasDecl(Trait, "traits")) Trait.traits else .{},
2434     };
2435 }
2436 
2437 pub fn operation(comptime OpType: type) OperationSpec {
2438     return operationSpecWithName(operationNameFor(OpType), OpType);
2439 }
2440 
2441 fn operationSpecWithName(comptime name: []const u8, comptime OpType: type) OperationSpec {
2442     if (comptime @hasDecl(OpType, "operation_spec")) {
2443         validateOperationSpecSource(OpType);
2444         const metadata = OpType.operation_spec;
2445         if (!std.mem.eql(u8, metadata.name, name)) {
2446             @compileError("operation_spec.name must match operation_name");
2447         }
2448         return operationSpecFromMetadata(name, metadata, OpType);
2449     }
2450 
2451     if (comptime nestedOperationSpec(OpType)) |metadata| {
2452         validateNestedOperationSpecSource(OpType);
2453         if (!std.mem.eql(u8, metadata.name, name)) {
2454             @compileError("nested operation template name must match operation_name");
2455         }
2456         return operationSpecFromMetadata(name, metadata, OpType);
2457     }
2458 
2459     @compileError("operation type must declare operation_spec or a nested operation template");
2460 }
2461 
2462 fn operationSpecFromMetadata(comptime name: []const u8, comptime metadata: OperationSpec, comptime OpType: type) OperationSpec {
2463     return .{
2464         .name = name,
2465         .traits = metadata.traits,
2466         .shape = metadata.shape,
2467         .operand_names = metadata.operand_names,
2468         .result_names = metadata.result_names,
2469         .region_names = metadata.region_names,
2470         .successor_names = metadata.successor_names,
2471         .inherent_attribute_names = attributeNamesWithRequired(metadata.inherent_attribute_names, metadata.required_attribute_names),
2472         .required_attribute_names = metadata.required_attribute_names,
2473         .attribute_specs = attributeSpecsWithNames(metadata.attribute_specs, attributeNamesWithRequired(metadata.inherent_attribute_names, metadata.required_attribute_names)),
2474         .operand_segments = metadata.operand_segments,
2475         .result_segments = metadata.result_segments,
2476         .operand_type_constraints = metadata.operand_type_constraints,
2477         .result_type_constraints = metadata.result_type_constraints,
2478         .properties_model = metadata.properties_model,
2479         .interfaces = operationInterfaces(OpType, metadata.interfaces),
2480         .dynamic_traits = metadata.dynamic_traits,
2481     };
2482 }
2483 
2484 fn validateOperationSpecSource(comptime OpType: type) void {
2485     if (@hasDecl(OpType, "traits")) @compileError("operation_spec operations must not declare traits");
2486     if (@hasDecl(OpType, "shape")) @compileError("operation_spec operations must not declare shape");
2487     if (@hasDecl(OpType, "operand_names")) @compileError("operation_spec operations must not declare operand_names");
2488     if (@hasDecl(OpType, "result_names")) @compileError("operation_spec operations must not declare result_names");
2489     if (@hasDecl(OpType, "region_names")) @compileError("operation_spec operations must not declare region_names");
2490     if (@hasDecl(OpType, "successor_names")) @compileError("operation_spec operations must not declare successor_names");
2491     if (@hasDecl(OpType, "inherent_attribute_names")) @compileError("operation_spec operations must not declare inherent_attribute_names");
2492     if (@hasDecl(OpType, "required_attribute_names")) @compileError("operation_spec operations must not declare required_attribute_names");
2493     if (@hasDecl(OpType, "attribute_specs")) @compileError("operation_spec operations must not declare attribute_specs");
2494     if (@hasDecl(OpType, "operand_segments")) @compileError("operation_spec operations must not declare operand_segments");
2495     if (@hasDecl(OpType, "result_segments")) @compileError("operation_spec operations must not declare result_segments");
2496     if (@hasDecl(OpType, "operand_type_constraints")) @compileError("operation_spec operations must not declare operand_type_constraints");
2497     if (@hasDecl(OpType, "result_type_constraints")) @compileError("operation_spec operations must not declare result_type_constraints");
2498     if (@hasDecl(OpType, "properties_model")) @compileError("operation_spec operations must not declare properties_model");
2499     if (@hasDecl(OpType, "operation_interfaces")) @compileError("operation_spec operations must not declare operation_interfaces");
2500     if (@hasDecl(OpType, "dynamic_traits")) @compileError("operation_spec operations must not declare dynamic_traits");
2501 }
2502 
2503 fn validateNestedOperationSpecSource(comptime OpType: type) void {
2504     if (@hasDecl(OpType, "operation_spec")) @compileError("nested operation template operations must not declare operation_spec");
2505     if (@hasDecl(OpType, "traits")) @compileError("nested operation template operations must not declare traits");
2506     if (@hasDecl(OpType, "shape")) @compileError("nested operation template operations must not declare shape");
2507     if (@hasDecl(OpType, "operand_names")) @compileError("nested operation template operations must not declare operand_names");
2508     if (@hasDecl(OpType, "result_names")) @compileError("nested operation template operations must not declare result_names");
2509     if (@hasDecl(OpType, "region_names")) @compileError("nested operation template operations must not declare region_names");
2510     if (@hasDecl(OpType, "successor_names")) @compileError("nested operation template operations must not declare successor_names");
2511     if (@hasDecl(OpType, "inherent_attribute_names")) @compileError("nested operation template operations must not declare inherent_attribute_names");
2512     if (@hasDecl(OpType, "required_attribute_names")) @compileError("nested operation template operations must not declare required_attribute_names");
2513     if (@hasDecl(OpType, "attribute_specs")) @compileError("nested operation template operations must not declare attribute_specs");
2514     if (@hasDecl(OpType, "operand_segments")) @compileError("nested operation template operations must not declare operand_segments");
2515     if (@hasDecl(OpType, "result_segments")) @compileError("nested operation template operations must not declare result_segments");
2516     if (@hasDecl(OpType, "operand_type_constraints")) @compileError("nested operation template operations must not declare operand_type_constraints");
2517     if (@hasDecl(OpType, "result_type_constraints")) @compileError("nested operation template operations must not declare result_type_constraints");
2518     if (@hasDecl(OpType, "properties_model")) @compileError("nested operation template operations must not declare properties_model");
2519     if (@hasDecl(OpType, "operation_interfaces")) @compileError("nested operation template operations must not declare operation_interfaces");
2520     if (@hasDecl(OpType, "dynamic_traits")) @compileError("nested operation template operations must not declare dynamic_traits");
2521 }
2522 
2523 fn operation_spec_storage(comptime DialectType: type, comptime count: usize) type {
2524     const decls = comptime std.meta.declarations(DialectType);
2525     return struct {
2526         const values = blk: {
2527             var out: [count]OperationSpec = undefined;
2528             var index: usize = 0;
2529             for (decls) |decl_name| {
2530                 if (std.mem.eql(u8, decl_name, "spec")) continue;
2531                 const decl_value = @field(DialectType, decl_name);
2532                 if (isDialectOperationValue(decl_value)) {
2533                     out[index] = operation(decl_value);
2534                     index += 1;
2535                 }
2536             }
2537             break :blk out;
2538         };
2539     };
2540 }
2541 
2542 pub fn operations(comptime DialectType: type) []const OperationSpec {
2543     const decls = comptime std.meta.declarations(DialectType);
2544     comptime var count: usize = 0;
2545     inline for (decls) |decl_name| {
2546         if (comptime std.mem.eql(u8, decl_name, "spec")) continue;
2547         const decl_value = @field(DialectType, decl_name);
2548         if (comptime isDialectOperationValue(decl_value)) count += 1;
2549     }
2550     return &operation_spec_storage(DialectType, count).values;
2551 }
2552 
2553 pub fn typeName(name: []const u8) TypeSpec {
2554     return .{ .name = name };
2555 }
2556 
2557 pub fn typeNames(comptime Names: type) []const TypeSpec {
2558     const decls = comptime std.meta.declarations(Names);
2559     comptime var count: usize = 0;
2560     inline for (decls) |decl_name| {
2561         const decl_value = @field(Names, decl_name);
2562         if (comptime typeSpecFromValue(decl_value) != null) count += 1;
2563     }
2564 
2565     const values = comptime blk: {
2566         var out: [count]TypeSpec = undefined;
2567         var index: usize = 0;
2568         for (decls) |decl_name| {
2569             const decl_value = @field(Names, decl_name);
2570             if (typeSpecFromValue(decl_value)) |spec| {
2571                 out[index] = spec;
2572                 index += 1;
2573             }
2574         }
2575         break :blk out;
2576     };
2577     return &values;
2578 }
2579 
2580 pub fn loadDialectSpec(ctx: *Context, spec: DialectSpec) !void {
2581     if (dialectSpecLoaded(ctx, spec)) return;
2582     try ensureDialectSpecLoaded(ctx, spec);
2583     try ctx.prepareDialectLoadRecordStorage(spec.name, spec.operations.len, spec.types.len);
2584     try registerDialectAttributes(ctx, spec.dialect_attributes);
2585     try ctx.registerOperationBatch(spec.operations);
2586 
2587     for (spec.operations) |op| try registerOperationDetails(ctx, op);
2588 
2589     try ctx.registerTypeBatch(spec.types);
2590     for (spec.types) |typ| {
2591         for (typ.interfaces) |entry| {
2592             ctx.registerTypeInterface(typ.name, entry) catch |err| switch (err) {
2593                 error.DuplicateInterface => {},
2594                 else => return err,
2595             };
2596         }
2597     }
2598 
2599     for (spec.interfaces) |entry| {
2600         ctx.registerDialectInterface(spec.name, entry) catch |err| switch (err) {
2601             error.DuplicateInterface => {},
2602             else => return err,
2603         };
2604     }
2605 
2606     for (spec.op_interface_fallbacks) |entry| {
2607         ctx.registerDialectOpInterfaceFallback(spec.name, entry.id, entry.fallback) catch |err| switch (err) {
2608             error.DuplicateInterface => {},
2609             else => return err,
2610         };
2611     }
2612 
2613     for (spec.type_interface_fallbacks) |entry| {
2614         ctx.registerDialectTypeInterfaceFallback(spec.name, entry.id, entry.fallback) catch |err| switch (err) {
2615             error.DuplicateInterface => {},
2616             else => return err,
2617         };
2618     }
2619 }
2620 
2621 fn registerOperationDetails(ctx: *Context, op: OperationSpec) !void {
2622     if (op.shape.hasConstraints()) {
2623         try ctx.registerOperationShape(op.name, op.shape);
2624     }
2625     if (op.operand_segments) |segment_spec| {
2626         try ctx.registerOperationOperandSegments(op.name, segment_spec);
2627     }
2628     if (op.result_segments) |segment_spec| {
2629         try ctx.registerOperationResultSegments(op.name, segment_spec);
2630     }
2631     for (op.operand_type_constraints) |constraint| {
2632         ctx.registerOperationOperandTypeConstraint(op.name, constraint) catch |err| switch (err) {
2633             error.DuplicateOperandTypeConstraint => {},
2634             else => return err,
2635         };
2636     }
2637     for (op.result_type_constraints) |constraint| {
2638         ctx.registerOperationResultTypeConstraint(op.name, constraint) catch |err| switch (err) {
2639             error.DuplicateResultTypeConstraint => {},
2640             else => return err,
2641         };
2642     }
2643     for (op.inherent_attribute_names) |attr_name| {
2644         ctx.registerOperationInherentAttributeName(op.name, attr_name) catch |err| switch (err) {
2645             error.DuplicateInherentAttributeName => {},
2646             else => return err,
2647         };
2648     }
2649     for (op.required_attribute_names) |attr_name| {
2650         ctx.registerOperationRequiredAttributeName(op.name, attr_name) catch |err| switch (err) {
2651             error.DuplicateRequiredAttributeName => {},
2652             error.DuplicateInherentAttributeName => {},
2653             else => return err,
2654         };
2655     }
2656     if (op.properties_model) |model| {
2657         ctx.registerOperationPropertiesModel(op.name, model) catch |err| switch (err) {
2658             error.DuplicateOperationProperties => {},
2659             else => return err,
2660         };
2661     }
2662     for (op.interfaces) |entry| {
2663         ctx.registerOperationInterface(op.name, entry) catch |err| switch (err) {
2664             error.DuplicateInterface => {},
2665             else => return err,
2666         };
2667     }
2668     for (op.dynamic_traits) |trait_spec| {
2669         _ = try ctx.registerOperation(op.name, trait_spec.traits);
2670         if (trait_spec.entry) |entry| {
2671             ctx.registerTraitDefinition(entry) catch |err| switch (err) {
2672                 error.DuplicateTrait => {},
2673                 else => return err,
2674             };
2675         }
2676         ctx.registerOperationTraitId(op.name, trait_spec.id) catch |err| switch (err) {
2677             error.DuplicateTrait => {},
2678             else => return err,
2679         };
2680     }
2681 }
2682 
2683 fn registerDialectAttributes(ctx: *Context, names: []const []const u8) !void {
2684     const equality = interfaces.AttributeEqualInterface.entry(&core.attribute.dialect_attr_eql_vtable);
2685     for (names) |name| {
2686         _ = try ctx.registerAttributeType(name, &.{equality});
2687     }
2688 }
2689 
2690 fn ensureDialectSpecLoaded(ctx: *Context, spec: DialectSpec) !void {
2691     if (ctx.dialect_registry.loaded.get(spec.name) != null) return;
2692     if (ctx.dialect_registry.load_state.get(spec.name)) |state| switch (state) {
2693         .loading => return,
2694         .loaded => {},
2695     };
2696 
2697     const table_allocator = core.context.configurationTableAllocator(ctx);
2698     const name_allocator = core.context.configurationNameAllocator(ctx);
2699     const interned = try ctx.dialect_registry.internDialectName(
2700         table_allocator,
2701         name_allocator,
2702         spec.name,
2703     );
2704     const state = try ctx.dialect_registry.load_state.getOrPut(table_allocator, interned);
2705     state.key_ptr.* = interned;
2706     const created_state = !state.found_existing;
2707     if (created_state) state.value_ptr.* = .loading;
2708     errdefer {
2709         if (created_state) _ = ctx.dialect_registry.load_state.remove(interned);
2710     }
2711 
2712     const dialect = try table_allocator.create(Dialect);
2713     errdefer table_allocator.destroy(dialect);
2714     dialect.* = Dialect.init(interned, ctx);
2715 
2716     const loaded = try ctx.dialect_registry.loaded.getOrPut(interned);
2717     loaded.key_ptr.* = interned;
2718     if (loaded.found_existing) {
2719         table_allocator.destroy(dialect);
2720     } else {
2721         loaded.value_ptr.* = dialect;
2722     }
2723 
2724     state.value_ptr.* = .loaded;
2725 }
2726 
2727 fn dialectSpecLoaded(ctx: *Context, spec: DialectSpec) bool {
2728     for (spec.dialect_attributes) |name| {
2729         if (ctx.lookupAttributeType(name) == null) return false;
2730     }
2731     if (spec.operations.len != 0) return ctx.lookupOperation(spec.operations[0].name) != null;
2732     if (spec.types.len != 0) return ctx.lookupType(spec.types[0].name) != null;
2733     if (ctx.dialect_registry.load_state.get(spec.name)) |state| return state == .loaded;
2734     return ctx.dialect_registry.loaded.get(spec.name) != null;
2735 }
2736 
2737 test "loadDialectSpec registers dialect attributes before context activation" {
2738     const testing = std.testing;
2739     const DialectType = struct {
2740         pub const name = "sealed_attrs";
2741         pub const spec = dialectSpec(@This(), .{
2742             .dialect_attributes = &.{"sealed_attrs.mode"},
2743         });
2744     };
2745     var failing = testing.FailingAllocator.init(testing.allocator, .{});
2746     var ctx = try Context.init(failing.allocator(), Context.Limits.testing);
2747     defer ctx.deinit(failing.allocator());
2748     try loadDialectSpec(&ctx, DialectType.spec);
2749     ctx.activate();
2750     failing.fail_index = failing.alloc_index;
2751     failing.resize_fail_index = failing.resize_index;
2752 
2753     const before = ctx.capacityUsage();
2754     try loadDialectSpec(&ctx, DialectType.spec);
2755     const first = try ctx.getDialectAttr("sealed_attrs.mode", "first");
2756     const second = try ctx.getDialectAttr("sealed_attrs.mode", "second");
2757     try testing.expect(!first.eql(second));
2758     try testing.expect(first.eql(try ctx.getDialectAttr("sealed_attrs.mode", "first")));
2759     try testing.expect(ctx.isFrozen());
2760     try testing.expectEqualDeep(before.configuration_tables, ctx.capacityUsage().configuration_tables);
2761     try testing.expect(!failing.has_induced_failure);
2762     try testing.expectError(error.ContextFrozen, ctx.getDialectAttr("sealed_attrs.undeclared", ""));
2763 }
2764 
2765 test "loadDialectSpec is read-only after representative operation registration" {
2766     const testing = std.testing;
2767     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2768     defer ctx.deinit(testing.allocator);
2769     try ctx.allowUnregistered();
2770 
2771     const ops = [_]OperationSpec{.{
2772         .name = "loaded_spec.op",
2773         .traits = .{ .is_idempotent = true },
2774     }};
2775     const spec = DialectSpec{
2776         .name = "loaded_spec",
2777         .operations = &ops,
2778     };
2779 
2780     try loadDialectSpec(&ctx, spec);
2781     ctx.freeze();
2782     try loadDialectSpec(&ctx, spec);
2783 
2784     const info = ctx.lookupOperation("loaded_spec.op") orelse return error.OperationMissing;
2785     try testing.expect(info.traits.is_idempotent);
2786 }
2787 
2788 test "loadDialectSpec marks direct spec loads as loaded dialects" {
2789     const testing = std.testing;
2790 
2791     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2792     defer ctx.deinit(testing.allocator);
2793 
2794     const ops = [_]OperationSpec{.{
2795         .name = "direct_loaded.op",
2796         .traits = .{},
2797     }};
2798 
2799     try loadDialectSpec(&ctx, .{
2800         .name = "direct_loaded",
2801         .operations = &ops,
2802     });
2803     try loadDialectSpec(&ctx, .{ .name = "direct_empty" });
2804 
2805     try testing.expect(ctx.isDialectLoaded("direct_loaded"));
2806     try testing.expect(ctx.isDialectLoaded("direct_empty"));
2807 
2808     const state = Operation.State.init("direct_loaded.op", .unknown);
2809     const op = try ctx.createOperation(state);
2810     try testing.expectEqualStrings("direct_loaded.op", op.name.name);
2811 
2812     _ = try ctx.getOrLoadDialect("direct_empty");
2813 }
2814 
2815 test "direct dialect spec loading cleans every allocation failure" {
2816     const Harness = struct {
2817         fn run(allocator: std.mem.Allocator) !void {
2818             var ctx = try Context.init(allocator, Context.Limits.testing);
2819             defer ctx.deinit(allocator);
2820             try loadDialectSpec(&ctx, .{ .name = "allocation_direct" });
2821         }
2822     };
2823 
2824     try std.testing.checkAllAllocationFailures(std.testing.allocator, Harness.run, .{});
2825 }
2826 
2827 test "direct dialect type batch cleans every allocation failure" {
2828     const Harness = struct {
2829         const type_specs = [_]TypeSpec{
2830             .{ .name = "allocation_types.first" },
2831             .{ .name = "allocation_types.second" },
2832         };
2833 
2834         fn run(allocator: std.mem.Allocator) !void {
2835             var ctx = try Context.init(allocator, Context.Limits.testing);
2836             defer ctx.deinit(allocator);
2837             try loadDialectSpec(&ctx, .{
2838                 .name = "allocation_types",
2839                 .types = &type_specs,
2840             });
2841         }
2842     };
2843 
2844     try std.testing.checkAllAllocationFailures(std.testing.allocator, Harness.run, .{});
2845 }
2846 
2847 test "direct dialect operation batch cleans every allocation failure" {
2848     const Harness = struct {
2849         const operation_specs = [_]OperationSpec{
2850             .{
2851                 .name = "allocation_operations.first",
2852                 .traits = .{ .is_idempotent = true },
2853                 .inherent_attribute_names = &.{ "alpha", "required" },
2854                 .required_attribute_names = &.{"required"},
2855             },
2856             .{
2857                 .name = "allocation_operations.second",
2858                 .traits = .{ .is_commutative = true },
2859             },
2860         };
2861 
2862         fn run(allocator: std.mem.Allocator) !void {
2863             var ctx = try Context.init(allocator, Context.Limits.testing);
2864             defer ctx.deinit(allocator);
2865             try loadDialectSpec(&ctx, .{
2866                 .name = "allocation_operations",
2867                 .operations = &operation_specs,
2868             });
2869         }
2870     };
2871 
2872     try std.testing.checkAllAllocationFailures(std.testing.allocator, Harness.run, .{});
2873 }
2874 
2875 test "dialect operation batch merges duplicate traits into one stable entry" {
2876     const testing = std.testing;
2877     const operation_specs = [_]OperationSpec{
2878         .{
2879             .name = "duplicate_operations.same",
2880             .traits = .{ .is_idempotent = true },
2881         },
2882         .{
2883             .name = "duplicate_operations.same",
2884             .traits = .{ .is_commutative = true },
2885         },
2886     };
2887 
2888     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
2889     defer ctx.deinit(testing.allocator);
2890     try loadDialectSpec(&ctx, .{
2891         .name = "duplicate_operations",
2892         .operations = &operation_specs,
2893     });
2894 
2895     const info = ctx.lookupOperation(operation_specs[0].name) orelse return error.OperationMissing;
2896     try testing.expect(info.traits.is_idempotent);
2897     try testing.expect(info.traits.is_commutative);
2898     try testing.expectEqual(@as(usize, 1), ctx.getOperationRegistry().count());
2899 }
2900 
2901 fn operationInterfaces(comptime OpType: type, comptime metadata_interfaces: []const interfaces.InterfaceEntry) []const interfaces.InterfaceEntry {
2902     const explicit_interfaces = comptime if (@hasDecl(OpType, "operation_interfaces")) OpType.operation_interfaces else metadata_interfaces;
2903     const has_verify = comptime @hasDecl(OpType, "verify");
2904     const has_verify_regions = comptime @hasDecl(OpType, "verifyRegions");
2905     const has_verify_symbol_uses = comptime @hasDecl(OpType, "verifySymbolUses");
2906     const has_cse = comptime @hasDecl(OpType, "cseIncludeAttr");
2907     const has_fold = comptime @hasDecl(OpType, "fold");
2908     const include_verify = has_verify and !interfaceListHasId(explicit_interfaces, VerifyOpInterface.id);
2909     const include_verify_regions = has_verify_regions and !interfaceListHasId(explicit_interfaces, VerifyRegionOpInterface.id);
2910     const include_verify_symbol_uses = has_verify_symbol_uses and !interfaceListHasId(explicit_interfaces, interfaces.SymbolUserOpInterface.id);
2911     const include_cse = has_cse and !interfaceListHasId(explicit_interfaces, interfaces.CseOpInterface.id);
2912     const include_fold = has_fold and !interfaceListHasId(explicit_interfaces, interfaces.FoldOpInterface.id);
2913 
2914     comptime var count: usize = explicit_interfaces.len;
2915     if (include_verify) count += 1;
2916     if (include_verify_regions) count += 1;
2917     if (include_verify_symbol_uses) count += 1;
2918     if (include_cse) count += 1;
2919     if (include_fold) count += 1;
2920 
2921     const values = comptime blk: {
2922         var out: [count]interfaces.InterfaceEntry = undefined;
2923         var index: usize = 0;
2924         for (explicit_interfaces) |entry| {
2925             out[index] = entry;
2926             index += 1;
2927         }
2928         if (include_verify) {
2929             out[index] = VerifyOpInterface.entryFor(OpType.verify);
2930             index += 1;
2931         }
2932         if (include_verify_regions) {
2933             out[index] = VerifyRegionOpInterface.entryFor(OpType.verifyRegions);
2934             index += 1;
2935         }
2936         if (include_verify_symbol_uses) {
2937             out[index] = interfaces.SymbolUserOpInterface.entryFor(OpType.verifySymbolUses);
2938             index += 1;
2939         }
2940         if (include_cse) {
2941             out[index] = cseEntry(OpType);
2942             index += 1;
2943         }
2944         if (include_fold) {
2945             out[index] = interfaces.FoldOpInterface.entryFor(OpType.fold);
2946             index += 1;
2947         }
2948         break :blk out;
2949     };
2950     return &values;
2951 }
2952 
2953 fn interfaceListHasId(comptime entries: []const interfaces.InterfaceEntry, comptime id: interfaces.InterfaceId) bool {
2954     for (entries) |entry| {
2955         if (entry.id == id) return true;
2956     }
2957     return false;
2958 }
2959 
2960 fn cseEntry(comptime OpType: type) interfaces.InterfaceEntry {
2961     if (comptime @hasDecl(OpType, "cseCommuteOperands")) {
2962         return interfaces.CseOpInterface.keyPolicyEntryFor(
2963             OpType.cseIncludeAttr,
2964             OpType.cseCommuteOperands,
2965         );
2966     }
2967     return interfaces.CseOpInterface.entryFor(OpType.cseIncludeAttr);
2968 }
2969 
2970 fn isDialectOperationValue(comptime value: anytype) bool {
2971     if (@typeInfo(@TypeOf(value)) != .type) return false;
2972     switch (@typeInfo(value)) {
2973         .@"struct", .@"enum", .@"union", .@"opaque" => {},
2974         else => return false,
2975     }
2976     return @hasDecl(value, "operation_name") or
2977         @hasDecl(value, "operation_spec") or
2978         nestedOperationSpec(value) != null;
2979 }
2980 
2981 fn nestedOperationSpec(comptime OpType: type) ?OperationSpec {
2982     inline for (.{ "def", "leaf", "term", "template" }) |decl_name| {
2983         if (comptime @hasDecl(OpType, decl_name)) {
2984             if (nestedOperationSpecFromValue(@field(OpType, decl_name))) |spec| return spec;
2985         }
2986     }
2987     return null;
2988 }
2989 
2990 fn nestedOperationSpecFromValue(comptime value: anytype) ?OperationSpec {
2991     if (@typeInfo(@TypeOf(value)) != .type) return null;
2992     switch (@typeInfo(value)) {
2993         .@"struct", .@"enum", .@"union", .@"opaque" => {},
2994         else => return null,
2995     }
2996     if (!@hasDecl(value, "operation_spec")) return null;
2997     return value.operation_spec;
2998 }
2999 
3000 fn isStringValue(comptime value: anytype) bool {
3001     return asString(value).len != 0;
3002 }
3003 
3004 fn typeSpecFromValue(comptime value: anytype) ?TypeSpec {
3005     if (@TypeOf(value) == TypeSpec) return value;
3006     const name = asString(value);
3007     if (name.len == 0) return null;
3008     return typeName(name);
3009 }
3010 
3011 fn asString(comptime value: anytype) []const u8 {
3012     const value_type = @TypeOf(value);
3013     return switch (@typeInfo(value_type)) {
3014         .pointer => |ptr_info| switch (ptr_info.size) {
3015             .slice => if (ptr_info.child == u8) value else "",
3016             .one => switch (@typeInfo(ptr_info.child)) {
3017                 .array => |arr_info| if (arr_info.child == u8) value[0..] else "",
3018                 else => "",
3019             },
3020             else => "",
3021         },
3022         else => "",
3023     };
3024 }
3025 
3026 test "operationName composes dialect and mnemonic" {
3027     const testing = std.testing;
3028 
3029     const ExampleDialect = struct {
3030         pub const name = "example";
3031         const operation_names = operationNames(@This());
3032         pub const ExampleOp = struct {
3033             pub const operation_name = operation_names.name("thing");
3034         };
3035     };
3036 
3037     try testing.expectEqualStrings("example.thing", ExampleDialect.ExampleOp.operation_name);
3038 }
3039 
3040 test "shape DSL maps integers and count ranges into operation shapes" {
3041     const testing = std.testing;
3042 
3043     const op_shape = shape.of(.{
3044         .operands = 2,
3045         .results = shape.atLeast(1),
3046         .regions = shape.atMost(1),
3047         .successors = shape.between(0, 2),
3048     });
3049 
3050     try testing.expect(op_shape.operands.allows(2));
3051     try testing.expect(!op_shape.operands.allows(1));
3052     try testing.expect(op_shape.results.allows(3));
3053     try testing.expect(!op_shape.results.allows(0));
3054     try testing.expect(op_shape.regions.allows(0));
3055     try testing.expect(op_shape.regions.allows(1));
3056     try testing.expect(!op_shape.regions.allows(2));
3057     try testing.expect(op_shape.successors.allows(2));
3058     try testing.expect(!op_shape.successors.allows(3));
3059 }
3060 
3061 test "shape DSL leaf constrains nested IR to zero" {
3062     const testing = std.testing;
3063 
3064     const leaf_shape = shape.leaf(shape.atMost(1), 1);
3065 
3066     try testing.expect(leaf_shape.operands.allows(0));
3067     try testing.expect(leaf_shape.operands.allows(1));
3068     try testing.expect(!leaf_shape.operands.allows(2));
3069     try testing.expect(leaf_shape.results.allows(1));
3070     try testing.expect(!leaf_shape.results.allows(0));
3071     try testing.expect(leaf_shape.regions.allows(0));
3072     try testing.expect(!leaf_shape.regions.allows(1));
3073     try testing.expect(leaf_shape.successors.allows(0));
3074     try testing.expect(!leaf_shape.successors.allows(1));
3075 }
3076 
3077 test "operations derives operation specs from descriptors" {
3078     const testing = std.testing;
3079 
3080     const ExampleDialect = struct {
3081         pub const name = "derived";
3082         const op_specs = opSpec.dialect(@This());
3083         pub const ExampleOp = struct {
3084             pub const operation_spec = op_specs.define(.{
3085                 .mnemonic = "thing",
3086                 .traits = interfaces.OperationTraits{ .is_idempotent = true },
3087             });
3088         };
3089         pub const spec = dialectSpec(@This(), .{});
3090     };
3091 
3092     try testing.expectEqual(@as(usize, 1), ExampleDialect.spec.operations.len);
3093     try testing.expectEqualStrings("derived.thing", ExampleDialect.spec.operations[0].name);
3094     try testing.expect(ExampleDialect.spec.operations[0].traits.is_idempotent);
3095 }
3096 
3097 test "operation descriptor DSL exports compact operation metadata" {
3098     const testing = std.testing;
3099 
3100     const MarkerInterface = struct {
3101         pub const id = interfaces.interfaceId("opdsl.marker");
3102         pub const VTable = struct { marker: u8 };
3103         const table = VTable{ .marker = 3 };
3104     };
3105 
3106     const ExampleProperties = struct {
3107         value: u8 = 0,
3108 
3109         fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
3110             const self: *@This() = @ptrCast(@alignCast(storage));
3111             self.* = .{};
3112         }
3113 
3114         fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
3115 
3116         fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void {
3117             const dest_self: *@This() = @ptrCast(@alignCast(dest));
3118             const source_self: *const @This() = @ptrCast(@alignCast(source));
3119             dest_self.* = source_self.*;
3120         }
3121 
3122         const model = interfaces.OperationPropertiesModel{
3123             .name = "opdsl.example.properties",
3124             .size = @sizeOf(@This()),
3125             .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())),
3126             .init = init,
3127             .deinit = deinit,
3128             .copyProperties = copyProperties,
3129         };
3130     };
3131 
3132     const Trait = struct {
3133         pub const trait_name = "opdsl.trait";
3134         pub const id = interfaces.traitId(trait_name);
3135         fn verify(_: *const anyopaque) anyerror!void {}
3136         pub const vtable = interfaces.TraitVTable{ .verify = verify };
3137         pub fn entry() interfaces.TraitEntry {
3138             return .{ .id = id, .vtable = &vtable };
3139         }
3140     };
3141 
3142     const DialectForTest = struct {
3143         pub const name = "opdsl";
3144         const operation_names = operationNames(@This());
3145         const op_specs = opSpec.dialect(operation_names);
3146         pub const ExampleOp = struct {
3147             pub const operation_spec = op_specs.define(.{
3148                 .mnemonic = "example",
3149                 .operands = .{ "lhs", "rhs" },
3150                 .results = .{"out"},
3151                 .regions = 0,
3152                 .successors = 0,
3153                 .operand_segments = segments.operands(.{ 1, shape.atMost(1) }),
3154                 .result_segments = segments.results(.{1}),
3155                 .traits = interfaces.OperationTraits{ .is_idempotent = true },
3156                 .attrs = &.{ "value", "predicate" },
3157                 .required_attrs = &.{"value"},
3158                 .properties = ExampleProperties.model,
3159                 .interfaces = &.{
3160                     .{ .id = MarkerInterface.id, .vtable = &MarkerInterface.table },
3161                 },
3162                 .dynamic_traits = .{Trait},
3163             });
3164             pub const operation_name = operation_spec.name;
3165         };
3166         pub const spec = dialectSpec(@This(), .{});
3167     };
3168 
3169     const spec = DialectForTest.ExampleOp.operation_spec;
3170     const registered_spec = DialectForTest.spec.operations[0];
3171     try testing.expectEqualStrings("opdsl.example", DialectForTest.ExampleOp.operation_name);
3172     try testing.expectEqualStrings("opdsl.example", spec.name);
3173     try testing.expectEqualStrings("opdsl.example", registered_spec.name);
3174     try testing.expect(spec.shape.operands.allows(2));
3175     try testing.expect(!spec.shape.operands.allows(1));
3176     try testing.expect(spec.shape.results.allows(1));
3177     try testing.expectEqual(@as(usize, 0), operandIndex(spec, "lhs"));
3178     try testing.expectEqual(@as(usize, 1), operandIndex(spec, "rhs"));
3179     try testing.expectEqual(@as(usize, 0), resultIndex(spec, "out"));
3180     try testing.expectEqualStrings("lhs", spec.operand_names[0]);
3181     try testing.expectEqualStrings("out", spec.result_names[0]);
3182     try testing.expectEqualStrings("lhs", registered_spec.operand_names[0]);
3183     try testing.expectEqualStrings("operand_segment_sizes", spec.operand_segments.?.attribute_name);
3184     try testing.expectEqual(@as(usize, 2), spec.operand_segments.?.segments.len);
3185     try testing.expect(spec.operand_segments.?.segments[1].allows(1));
3186     try testing.expect(!spec.operand_segments.?.segments[1].allows(2));
3187     try testing.expectEqualStrings("result_segment_sizes", registered_spec.result_segments.?.attribute_name);
3188     try testing.expectEqual(@as(usize, 1), registered_spec.result_segments.?.segments.len);
3189     try testing.expect(spec.traits.is_idempotent);
3190     try testing.expectEqual(@as(usize, 2), spec.inherent_attribute_names.len);
3191     try testing.expectEqualStrings("predicate", spec.inherent_attribute_names[1]);
3192     try testing.expectEqual(@as(usize, 1), spec.required_attribute_names.len);
3193     try testing.expectEqualStrings("value", spec.required_attribute_names[0]);
3194     try testing.expect(spec.properties_model != null);
3195     try testing.expectEqual(@as(usize, 1), spec.interfaces.len);
3196     try testing.expectEqual(MarkerInterface.id, spec.interfaces[0].id);
3197     try testing.expectEqual(@as(usize, 1), spec.dynamic_traits.len);
3198     try testing.expectEqual(Trait.id, spec.dynamic_traits[0].id);
3199 }
3200 
3201 test "operation segment helpers set attributes and read named segments" {
3202     const testing = std.testing;
3203 
3204     const DialectForTest = struct {
3205         pub const name = "segment_helpers";
3206         const op_specs = opSpec.dialect(@This());
3207         pub const ExampleOp = struct {
3208             pub const operation_spec = op_specs.leaf(.{
3209                 .mnemonic = "example",
3210                 .operands = shape.between(1, 2),
3211                 .operand_names = .{ "required", "optional" },
3212                 .results = shape.between(1, 2),
3213                 .result_names = .{ "primary", "extra" },
3214                 .operand_segments = segments.operands(.{ 1, shape.atMost(1) }),
3215                 .result_segments = segments.results(.{ 1, shape.atMost(1) }),
3216             });
3217             pub const operation_name = operation_spec.name;
3218         };
3219         pub const spec = dialectSpec(@This(), .{});
3220     };
3221 
3222     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3223     defer ctx.deinit(testing.allocator);
3224     try ctx.allowUnregistered();
3225     try loadDialectSpec(&ctx, DialectForTest.spec);
3226 
3227     const value_type = try ctx.getDialectTypeFromName("segment_helpers.ty");
3228     var producer_state = Operation.State.init("segment_helpers.producer", .unknown);
3229     producer_state.addTypes(&.{ value_type, value_type });
3230     const producer = try ctx.createOperation(producer_state);
3231     const required = producer.getResult(0).?;
3232     const optional = producer.getResult(1).?;
3233 
3234     var full_state = Operation.State.init(DialectForTest.ExampleOp.operation_name, .unknown);
3235     full_state.addOperands(&.{ required, optional });
3236     full_state.addTypes(&.{ value_type, value_type });
3237     const full = try ctx.createOperation(full_state);
3238     try setOperandSegmentSizes(DialectForTest.ExampleOp.operation_spec, full, &[_]usize{ 1, 1 });
3239     try setResultSegmentSizes(DialectForTest.ExampleOp.operation_spec, full, &[_]usize{ 1, 1 });
3240 
3241     try testing.expectEqual(required, operandSegmentValue(DialectForTest.ExampleOp.operation_spec, full, "required").?);
3242     try testing.expectEqual(optional, operandSegmentValue(DialectForTest.ExampleOp.operation_spec, full, "optional").?);
3243     try testing.expectEqual(full.getResult(0).?, resultSegmentValue(DialectForTest.ExampleOp.operation_spec, full, "primary").?);
3244     try testing.expectEqual(full.getResult(1).?, resultSegmentValue(DialectForTest.ExampleOp.operation_spec, full, "extra").?);
3245 
3246     var partial_state = Operation.State.init(DialectForTest.ExampleOp.operation_name, .unknown);
3247     partial_state.addOperands(&.{required});
3248     partial_state.addTypes(&.{value_type});
3249     const partial = try ctx.createOperation(partial_state);
3250     try setOperandSegmentSizes(DialectForTest.ExampleOp.operation_spec, partial, &[_]usize{ 1, 0 });
3251     try setResultSegmentSizes(DialectForTest.ExampleOp.operation_spec, partial, &[_]usize{ 1, 0 });
3252 
3253     const optional_operands = operandSegmentValues(DialectForTest.ExampleOp.operation_spec, partial, "optional") orelse return error.TestExpectedOperandSegment;
3254     const extra_results = resultSegmentValues(DialectForTest.ExampleOp.operation_spec, partial, "extra") orelse return error.TestExpectedResultSegment;
3255     try testing.expectEqual(@as(usize, 0), optional_operands.len);
3256     try testing.expectEqual(@as(usize, 0), extra_results.len);
3257     try testing.expect(operandSegmentValue(DialectForTest.ExampleOp.operation_spec, partial, "optional") == null);
3258     try testing.expect(resultSegmentValue(DialectForTest.ExampleOp.operation_spec, partial, "extra") == null);
3259 }
3260 
3261 test "operation descriptor required attrs are inherent attrs" {
3262     const testing = std.testing;
3263 
3264     const DialectForTest = struct {
3265         pub const name = "required";
3266         const op_specs = opSpec.dialect(@This());
3267         pub const ExampleOp = struct {
3268             pub const operation_spec = op_specs.leaf(.{
3269                 .mnemonic = "example",
3270                 .required_attrs = &.{"value"},
3271             });
3272             pub const operation_name = operation_spec.name;
3273         };
3274         pub const spec = dialectSpec(@This(), .{});
3275     };
3276 
3277     const spec = DialectForTest.ExampleOp.operation_spec;
3278     try testing.expectEqual(@as(usize, 1), spec.inherent_attribute_names.len);
3279     try testing.expectEqualStrings("value", spec.inherent_attribute_names[0]);
3280     try testing.expectEqual(@as(usize, 1), spec.required_attribute_names.len);
3281     try testing.expectEqualStrings("value", spec.required_attribute_names[0]);
3282 
3283     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3284     defer ctx.deinit(testing.allocator);
3285     try ctx.allowUnregistered();
3286     try loadDialectSpec(&ctx, DialectForTest.spec);
3287     const info = ctx.lookupOperation(DialectForTest.ExampleOp.operation_name) orelse return error.TestExpectedRequiredAttrOp;
3288     try testing.expect(info.hasInherentAttributeName("value"));
3289     try testing.expect(info.hasRequiredAttributeName("value"));
3290 }
3291 
3292 test "operation descriptor DSL carries typed attribute specs" {
3293     const testing = std.testing;
3294 
3295     const spec = comptime opSpec.define(.{
3296         .name = "attrs.example",
3297         .attrs = .{attribute.string("label")},
3298         .required_attrs = .{
3299             attribute.integer("value"),
3300             attribute.dialect("payload", "attrs.payload"),
3301         },
3302     });
3303 
3304     try testing.expectEqual(@as(usize, 3), spec.inherent_attribute_names.len);
3305     try testing.expectEqualStrings("label", spec.inherent_attribute_names[0]);
3306     try testing.expectEqualStrings("value", spec.required_attribute_names[0]);
3307     try testing.expectEqualStrings("payload", spec.required_attribute_names[1]);
3308     try testing.expectEqual(@as(usize, 3), spec.attribute_specs.len);
3309     try testing.expectEqual(.string, std.meta.activeTag(operationAttribute(spec, "label").storage));
3310     try testing.expectEqual(.i64, std.meta.activeTag(operationAttribute(spec, "value").storage));
3311     try testing.expectEqualStrings("attrs.payload", operationDialectAttributeName(spec, "payload"));
3312 
3313     const required_upgrade = comptime opSpec.define(.{
3314         .name = "attrs.required_upgrade",
3315         .attrs = .{"payload"},
3316         .required_attrs = .{attribute.dialect("payload", "attrs.payload")},
3317     });
3318     try testing.expectEqual(@as(usize, 1), required_upgrade.attribute_specs.len);
3319     try testing.expectEqualStrings("attrs.payload", operationDialectAttributeName(required_upgrade, "payload"));
3320 }
3321 
3322 test "operation descriptor DSL accepts trait types in dynamic trait lists" {
3323     const testing = std.testing;
3324 
3325     const Trait = struct {
3326         pub const trait_name = "opdsl.direct_trait";
3327         pub const id = interfaces.traitId(trait_name);
3328         pub const traits = interfaces.OperationTraits{ .is_symbol_table = true };
3329         pub const vtable = interfaces.TraitVTable{};
3330         pub fn entry() interfaces.TraitEntry {
3331             return .{ .id = id, .vtable = &vtable };
3332         }
3333     };
3334 
3335     const ExplicitTrait = struct {
3336         pub const trait_name = "opdsl.explicit_trait";
3337         pub const id = interfaces.traitId(trait_name);
3338         pub const vtable = interfaces.TraitVTable{};
3339         pub fn entry() interfaces.TraitEntry {
3340             return .{ .id = id, .vtable = &vtable };
3341         }
3342     };
3343 
3344     const spec = opSpec.define(.{
3345         .name = "opdsl.direct_traits",
3346         .dynamic_traits = .{ Trait, trait(ExplicitTrait) },
3347     });
3348 
3349     try testing.expectEqual(@as(usize, 2), spec.dynamic_traits.len);
3350     try testing.expectEqual(Trait.id, spec.dynamic_traits[0].id);
3351     try testing.expectEqual(ExplicitTrait.id, spec.dynamic_traits[1].id);
3352 
3353     const option_traits = opSpec.dynamicTraits(.{Trait});
3354     try testing.expectEqual(@as(usize, 1), option_traits.len);
3355     try testing.expectEqual(Trait.id, option_traits[0].id);
3356     try testing.expect(option_traits[0].traits.is_symbol_table);
3357 }
3358 
3359 test "operation descriptor DSL accepts full operation declaration records" {
3360     const testing = std.testing;
3361 
3362     const spec = comptime opSpec.define(.{
3363         .name = "record.example",
3364         .operands = .{ "condition", "value" },
3365         .results = .{"result"},
3366         .regions = .{"body"},
3367         .successors = .{"exit"},
3368         .traits = interfaces.OperationTraits{ .is_idempotent = true },
3369         .attrs = &.{"value"},
3370     });
3371 
3372     try testing.expectEqualStrings("record.example", spec.name);
3373     try testing.expect(spec.shape.operands.allows(2));
3374     try testing.expect(!spec.shape.operands.allows(1));
3375     try testing.expect(!spec.shape.operands.allows(3));
3376     try testing.expect(spec.shape.results.allows(1));
3377     try testing.expect(spec.shape.regions.allows(1));
3378     try testing.expect(!spec.shape.regions.allows(0));
3379     try testing.expect(spec.shape.successors.allows(1));
3380     try testing.expect(!spec.shape.successors.allows(0));
3381     try testing.expectEqual(@as(usize, 0), operandIndex(spec, "condition"));
3382     try testing.expectEqual(@as(usize, 1), operandIndex(spec, "value"));
3383     try testing.expectEqual(@as(usize, 0), resultIndex(spec, "result"));
3384     try testing.expectEqual(@as(usize, 0), regionIndex(spec, "body"));
3385     try testing.expectEqual(@as(usize, 0), successorIndex(spec, "exit"));
3386     try testing.expect(spec.traits.is_idempotent);
3387     try testing.expectEqual(@as(usize, 1), spec.inherent_attribute_names.len);
3388     try testing.expectEqualStrings("value", spec.inherent_attribute_names[0]);
3389 }
3390 
3391 test "operation descriptor DSL derives terminator declarations" {
3392     const testing = std.testing;
3393 
3394     const spec = opSpec.terminator(.{
3395         .name = "record.return",
3396         .operands = shape.atLeast(1),
3397     });
3398 
3399     try testing.expectEqualStrings("record.return", spec.name);
3400     try testing.expect(spec.shape.operands.allows(1));
3401     try testing.expect(spec.shape.operands.allows(3));
3402     try testing.expect(!spec.shape.operands.allows(0));
3403     try testing.expect(spec.shape.results.allows(0));
3404     try testing.expect(!spec.shape.results.allows(1));
3405     try testing.expect(spec.shape.regions.allows(0));
3406     try testing.expect(!spec.shape.regions.allows(1));
3407     try testing.expect(spec.shape.successors.allows(0));
3408     try testing.expect(!spec.shape.successors.allows(1));
3409     try testing.expect(spec.traits.is_terminator);
3410     try testing.expectEqual(@as(usize, 1), spec.dynamic_traits.len);
3411     try testing.expectEqual(core_traits.Terminator.id, spec.dynamic_traits[0].id);
3412 }
3413 
3414 test "operation descriptor DSL builds dialect operation sets" {
3415     const testing = std.testing;
3416 
3417     const DialectForTest = struct {
3418         pub const name = "opset";
3419         const op_specs = opSpec.dialect(@This());
3420 
3421         pub const LeafOp = struct {
3422             pub const operation_spec = op_specs.leaf(.{
3423                 .mnemonic = "leaf",
3424                 .operands = shape.atMost(2),
3425                 .results = 1,
3426                 .attrs = &.{"value"},
3427             });
3428             pub const operation_name = operation_spec.name;
3429         };
3430 
3431         pub const spec = dialectSpec(@This(), .{});
3432     };
3433 
3434     try testing.expectEqualStrings("opset.leaf", DialectForTest.op_specs.name("leaf"));
3435     try testing.expectEqualStrings("opset.leaf", DialectForTest.LeafOp.operation_name);
3436     const state = DialectForTest.op_specs.state(DialectForTest.LeafOp, Location.getUnknown());
3437     try testing.expectEqualStrings("opset.leaf", state.name.name);
3438     const spec = DialectForTest.spec.operations[0];
3439     try testing.expect(spec.shape.operands.allows(2));
3440     try testing.expect(!spec.shape.operands.allows(3));
3441     try testing.expect(spec.shape.results.allows(1));
3442     try testing.expect(spec.shape.regions.allows(0));
3443     try testing.expect(!spec.shape.regions.allows(1));
3444     try testing.expect(spec.shape.successors.allows(0));
3445     try testing.expect(!spec.shape.successors.allows(1));
3446 
3447     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3448     defer ctx.deinit(testing.allocator);
3449     try ctx.allowUnregistered();
3450     try loadDialectSpec(&ctx, DialectForTest.spec);
3451     const info = ctx.lookupOperation(DialectForTest.LeafOp.operation_name) orelse return error.TestExpectedLeafOp;
3452     try testing.expect(info.shape.regions.allows(0));
3453     try testing.expect(!info.shape.regions.allows(1));
3454     try testing.expect(info.hasInherentAttributeName("value"));
3455 }
3456 
3457 fn operationTemplateNoopFold(
3458     _: *const anyopaque,
3459     _: *interfaces.FoldResults,
3460 ) anyerror!void {}
3461 
3462 fn operationTemplateNoopVerify(_: *const anyopaque) anyerror!void {}
3463 
3464 fn operationTemplateFixedBoolType(ctx: *Context) anyerror!Type {
3465     return ctx.getDialectTypeFromName("template_fixed.bool");
3466 }
3467 
3468 test "operationTemplate nested definitions register custom wrappers" {
3469     const testing = std.testing;
3470 
3471     const ExampleDialect = struct {
3472         pub const name = "template_nested";
3473         const ops = operationTemplate.dialect(@This());
3474 
3475         pub const ScaleOp = struct {
3476             op: *Operation,
3477 
3478             pub const leaf = ops.explicitLeaf(@This(), .{
3479                 .mnemonic = "scale",
3480                 .operands = .{"input"},
3481                 .results = .{"output"},
3482                 .required_attrs = .{attribute.integer("factor")},
3483                 .traits = interfaces.OperationTraits{ .is_idempotent = true },
3484                 .interfaces = &.{opSpec.verifier(operationTemplateNoopVerify)},
3485             });
3486             pub const operation_name = leaf.operation_name;
3487             pub const verify = operationTemplateNoopVerify;
3488 
3489             pub fn create(ctx: *Context, loc: Location, input: *IrValue, output_type: Type, factor: i64) !@This() {
3490                 const self = try leaf.createLeaf(ctx, loc, &.{input}, &.{output_type});
3491                 try leaf.setI64Attr(self, "factor", factor);
3492                 return self;
3493             }
3494 
3495             pub fn getInput(self: @This()) *IrValue {
3496                 return leaf.getOperand(self, "input");
3497             }
3498 
3499             pub fn getResult(self: @This()) *IrValue {
3500                 return leaf.getResult(self);
3501             }
3502         };
3503 
3504         pub const spec = dialectSpec(@This(), .{});
3505     };
3506 
3507     try testing.expect(!@hasDecl(ExampleDialect.ScaleOp, "operation_spec"));
3508     try testing.expectEqualStrings("template_nested.scale", ExampleDialect.ScaleOp.operation_name);
3509     try testing.expectEqualStrings("template_nested.scale", operationNameFor(ExampleDialect.ScaleOp));
3510     const spec = ExampleDialect.spec.operations[0];
3511     try testing.expectEqualStrings("template_nested.scale", spec.name);
3512     try testing.expect(spec.traits.is_idempotent);
3513     try testing.expectEqual(@as(usize, 1), spec.required_attribute_names.len);
3514     try testing.expectEqualStrings("factor", spec.required_attribute_names[0]);
3515     try testing.expectEqual(@as(usize, 1), spec.interfaces.len);
3516 
3517     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3518     defer ctx.deinit(testing.allocator);
3519     try ctx.allowUnregistered();
3520     try loadDialectSpec(&ctx, ExampleDialect.spec);
3521 
3522     const loc = Location.getUnknown();
3523     const input_type = try ctx.getDialectTypeFromName("template_nested.i32");
3524     const output_type = try ctx.getDialectTypeFromName("template_nested.f32");
3525 
3526     var input_state = Operation.State.init("template_nested.seed", loc);
3527     input_state.addTypes(&.{input_type});
3528     const input_op = try ctx.createOperation(input_state);
3529     const input = input_op.getResult(0).?;
3530 
3531     const scale = try ExampleDialect.ScaleOp.create(&ctx, loc, input, output_type, 11);
3532     try testing.expectEqual(input, scale.getInput());
3533     try testing.expect(scale.getResult().type.eql(output_type));
3534     try testing.expectEqual(@as(i64, 11), scale.op.getAttrAs(Attribute.IntegerAttr, "factor").?.value);
3535 
3536     const info = ctx.lookupOperation(ExampleDialect.ScaleOp.operation_name) orelse return error.TestExpectedScaleOp;
3537     try testing.expect(info.hasInterface(VerifyOpInterface.id));
3538     try testing.expect(info.hasRequiredAttributeName("factor"));
3539 }
3540 
3541 test "operationTemplate explicit leaf mixin derives construction from one spec" {
3542     const testing = std.testing;
3543 
3544     const ExampleDialect = struct {
3545         pub const name = "template_explicit";
3546         const ops = operationTemplate.dialect(@This());
3547 
3548         pub const ScaleOp = struct {
3549             op: *Operation,
3550 
3551             const leaf = ops.explicitLeaf(@This(), .{
3552                 .mnemonic = "scale",
3553                 .operands = .{"input"},
3554                 .results = .{"output"},
3555                 .required_attrs = &.{"factor"},
3556                 .traits = interfaces.OperationTraits{ .is_idempotent = true },
3557             });
3558             pub const operation_spec = leaf.operation_spec;
3559             pub const operation_name = leaf.operation_name;
3560             pub const createLeaf = leaf.createLeaf;
3561             pub const getOperand = leaf.getOperand;
3562             pub const getNamedResult = leaf.getNamedResult;
3563             pub const getResult = leaf.getResult;
3564 
3565             pub fn create(ctx: *Context, loc: Location, input: *IrValue, output_type: Type, factor: i64) !@This() {
3566                 const self = try @This().createLeaf(ctx, loc, &.{input}, &.{output_type});
3567                 try self.op.setAttr("factor", try ctx.getI64Attr(factor));
3568                 return self;
3569             }
3570         };
3571 
3572         pub const spec = dialectSpec(@This(), .{});
3573     };
3574 
3575     try testing.expectEqualStrings("template_explicit.scale", ExampleDialect.ScaleOp.operation_name);
3576     try testing.expectEqualStrings("input", ExampleDialect.ScaleOp.operation_spec.operand_names[0]);
3577     try testing.expectEqualStrings("output", ExampleDialect.ScaleOp.operation_spec.result_names[0]);
3578     try testing.expect(ExampleDialect.ScaleOp.operation_spec.shape.operands.allows(1));
3579     try testing.expect(!ExampleDialect.ScaleOp.operation_spec.shape.operands.allows(2));
3580     try testing.expect(ExampleDialect.ScaleOp.operation_spec.shape.regions.allows(0));
3581     try testing.expect(!ExampleDialect.ScaleOp.operation_spec.shape.regions.allows(1));
3582 
3583     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3584     defer ctx.deinit(testing.allocator);
3585     try ctx.allowUnregistered();
3586     try loadDialectSpec(&ctx, ExampleDialect.spec);
3587 
3588     const loc = Location.getUnknown();
3589     const input_type = try ctx.getDialectTypeFromName("template_explicit.i32");
3590     const output_type = try ctx.getDialectTypeFromName("template_explicit.f32");
3591 
3592     var input_state = Operation.State.init("template_explicit.seed", loc);
3593     input_state.addTypes(&.{input_type});
3594     const input_op = try ctx.createOperation(input_state);
3595     const input = input_op.getResult(0).?;
3596 
3597     const scale = try ExampleDialect.ScaleOp.create(&ctx, loc, input, output_type, 7);
3598     try testing.expectEqual(input, scale.getOperand("input"));
3599     try testing.expect(scale.getResult().type.eql(output_type));
3600     try testing.expectEqual(scale.getResult(), scale.getNamedResult("output"));
3601     try testing.expectEqual(@as(i64, 7), scale.op.getAttrAs(Attribute.IntegerAttr, "factor").?.value);
3602 }
3603 
3604 test "operationTemplate explicit leaf mixin derives typed attribute accessors" {
3605     const testing = std.testing;
3606 
3607     const ExampleDialect = struct {
3608         pub const name = "template_attrs";
3609         const ops = operationTemplate.dialect(@This());
3610 
3611         pub const PayloadOp = struct {
3612             op: *Operation,
3613 
3614             const leaf = ops.explicitLeaf(@This(), .{
3615                 .mnemonic = "payload",
3616                 .results = .{"result"},
3617                 .required_attrs = .{
3618                     attribute.integer("count"),
3619                     attribute.boolean("enabled"),
3620                     attribute.string("label"),
3621                     attribute.dialect("payload", "template_attrs.payload"),
3622                 },
3623             });
3624             pub const operation_spec = leaf.operation_spec;
3625             pub const operation_name = leaf.operation_name;
3626             pub const createLeaf = leaf.createLeaf;
3627             pub const getResult = leaf.getResult;
3628             pub const setI64Attr = leaf.setI64Attr;
3629             pub const getI64Attr = leaf.getI64Attr;
3630             pub const setBoolAttr = leaf.setBoolAttr;
3631             pub const getBoolAttr = leaf.getBoolAttr;
3632             pub const setStringAttr = leaf.setStringAttr;
3633             pub const getStringAttr = leaf.getStringAttr;
3634             pub const setDialectAttrPayload = leaf.setDialectAttrPayload;
3635             pub const getDialectAttrPayload = leaf.getDialectAttrPayload;
3636             pub const dialectAttrName = leaf.dialectAttrName;
3637         };
3638 
3639         pub const spec = dialectSpec(@This(), .{});
3640     };
3641 
3642     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3643     defer ctx.deinit(testing.allocator);
3644     try ctx.allowUnregistered();
3645     try loadDialectSpec(&ctx, ExampleDialect.spec);
3646 
3647     const loc = Location.getUnknown();
3648     const result_type = try ctx.getDialectTypeFromName("template_attrs.i32");
3649     var created = try ExampleDialect.PayloadOp.createLeaf(&ctx, loc, &.{}, &.{result_type});
3650     try created.setI64Attr("count", 42);
3651     try created.setBoolAttr("enabled", true);
3652     try created.setStringAttr("label", "sample");
3653     try created.setDialectAttrPayload("payload", "bytes");
3654 
3655     try testing.expectEqual(@as(i64, 42), created.getI64Attr("count").?);
3656     try testing.expect(created.getBoolAttr("enabled").?);
3657     try testing.expectEqualStrings("sample", created.getStringAttr("label").?);
3658     try testing.expectEqualStrings("bytes", created.getDialectAttrPayload("payload").?);
3659     try testing.expectEqualStrings("template_attrs.payload", ExampleDialect.PayloadOp.dialectAttrName("payload"));
3660 
3661     const info = ctx.lookupOperation(ExampleDialect.PayloadOp.operation_name) orelse return error.TestExpectedOperation;
3662     try testing.expect(info.hasInherentAttributeName("payload"));
3663     try testing.expect(info.hasRequiredAttributeName("payload"));
3664 }
3665 
3666 test "operationTemplate explicit leaf mixin derives optional and segment accessors" {
3667     const testing = std.testing;
3668 
3669     const ExampleDialect = struct {
3670         pub const name = "template_components";
3671         const ops = operationTemplate.dialect(@This());
3672 
3673         pub const OptionalOp = struct {
3674             op: *Operation,
3675 
3676             const leaf = ops.explicitLeaf(@This(), .{
3677                 .mnemonic = "optional",
3678                 .operands = shape.atMost(1),
3679                 .operand_names = .{"input"},
3680                 .results = shape.atMost(1),
3681                 .result_names = .{"output"},
3682             });
3683             pub const operation_spec = leaf.operation_spec;
3684             pub const operation_name = leaf.operation_name;
3685             pub const createLeaf = leaf.createLeaf;
3686             pub const getOptionalOperand = leaf.getOptionalOperand;
3687             pub const getOptionalResult = leaf.getOptionalResult;
3688         };
3689 
3690         pub const SegmentedOp = struct {
3691             op: *Operation,
3692 
3693             const leaf = ops.explicitLeaf(@This(), .{
3694                 .mnemonic = "segmented",
3695                 .operands = shape.atLeast(1),
3696                 .operand_names = .{ "head", "tail" },
3697                 .results = shape.atLeast(1),
3698                 .result_names = .{ "primary", "extra" },
3699                 .operand_segments = segments.operands(.{ 1, shape.any() }),
3700                 .result_segments = segments.results(.{ 1, shape.any() }),
3701             });
3702             pub const operation_spec = leaf.operation_spec;
3703             pub const operation_name = leaf.operation_name;
3704             pub const createLeaf = leaf.createLeaf;
3705             pub const getOperandSegment = leaf.getOperandSegment;
3706             pub const getOperandSegmentValue = leaf.getOperandSegmentValue;
3707             pub const getResultSegment = leaf.getResultSegment;
3708             pub const getResultSegmentValue = leaf.getResultSegmentValue;
3709         };
3710 
3711         pub const spec = dialectSpec(@This(), .{});
3712     };
3713 
3714     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3715     defer ctx.deinit(testing.allocator);
3716     try ctx.allowUnregistered();
3717     try loadDialectSpec(&ctx, ExampleDialect.spec);
3718 
3719     const loc = Location.getUnknown();
3720     const i32_type = try ctx.getDialectTypeFromName("template_components.i32");
3721 
3722     var seed_state = Operation.State.init("template_components.seed", loc);
3723     seed_state.addTypes(&.{i32_type});
3724     const seed_a_op = try ctx.createOperation(seed_state);
3725     const seed_b_op = try ctx.createOperation(seed_state);
3726     const seed_c_op = try ctx.createOperation(seed_state);
3727     const seed_a = seed_a_op.getResult(0).?;
3728     const seed_b = seed_b_op.getResult(0).?;
3729     const seed_c = seed_c_op.getResult(0).?;
3730 
3731     const empty_optional = try ExampleDialect.OptionalOp.createLeaf(&ctx, loc, &.{}, &.{});
3732     try testing.expect(empty_optional.getOptionalOperand("input") == null);
3733     try testing.expect(empty_optional.getOptionalResult("output") == null);
3734 
3735     const full_optional = try ExampleDialect.OptionalOp.createLeaf(&ctx, loc, &.{seed_a}, &.{i32_type});
3736     try testing.expectEqual(seed_a, full_optional.getOptionalOperand("input").?);
3737     try testing.expect(full_optional.getOptionalResult("output").?.type.eql(i32_type));
3738 
3739     const segmented = try ExampleDialect.SegmentedOp.createLeaf(
3740         &ctx,
3741         loc,
3742         &.{ seed_a, seed_b, seed_c },
3743         &.{ i32_type, i32_type },
3744     );
3745     try setOperandSegmentSizes(ExampleDialect.SegmentedOp.operation_spec, segmented.op, &.{ 1, 2 });
3746     try setResultSegmentSizes(ExampleDialect.SegmentedOp.operation_spec, segmented.op, &.{ 1, 1 });
3747 
3748     try testing.expectEqual(seed_a, segmented.getOperandSegmentValue("head").?);
3749     const tail = segmented.getOperandSegment("tail") orelse return error.TestExpectedOperandSegment;
3750     try testing.expectEqual(@as(usize, 2), tail.len);
3751     try testing.expectEqual(seed_b, tail[0]);
3752     try testing.expectEqual(seed_c, tail[1]);
3753 
3754     try testing.expect(segmented.getResultSegmentValue("primary").?.type.eql(i32_type));
3755     const extra = segmented.getResultSegment("extra") orelse return error.TestExpectedResultSegment;
3756     try testing.expectEqual(@as(usize, 1), extra.len);
3757     try testing.expect(extra[0].type.eql(i32_type));
3758 }
3759 
3760 test "opSpec names components independently from shape ranges" {
3761     const testing = std.testing;
3762 
3763     const spec = comptime opSpec.define(.{
3764         .name = "template_named.branch",
3765         .operands = shape.atLeast(1),
3766         .operand_names = .{"condition"},
3767         .results = shape.atMost(2),
3768         .result_names = .{"primary"},
3769         .regions = shape.atLeast(1),
3770         .region_names = .{"body"},
3771         .successors = shape.between(1, 2),
3772         .successor_names = .{"target"},
3773     });
3774 
3775     try testing.expect(spec.shape.operands.allows(4));
3776     try testing.expect(spec.shape.results.allows(0));
3777     try testing.expect(spec.shape.results.allows(2));
3778     try testing.expect(!spec.shape.results.allows(3));
3779     try testing.expect(spec.shape.regions.allows(3));
3780     try testing.expect(spec.shape.successors.allows(1));
3781     try testing.expectEqualStrings("condition", spec.operand_names[0]);
3782     try testing.expectEqualStrings("primary", spec.result_names[0]);
3783     try testing.expectEqualStrings("body", spec.region_names[0]);
3784     try testing.expectEqualStrings("target", spec.successor_names[0]);
3785     try testing.expectEqual(@as(usize, 0), operandIndex(spec, "condition"));
3786     try testing.expectEqual(@as(usize, 0), resultIndex(spec, "primary"));
3787     try testing.expectEqual(@as(usize, 0), regionIndex(spec, "body"));
3788     try testing.expectEqual(@as(usize, 0), successorIndex(spec, "target"));
3789 }
3790 
3791 test "operationTemplate explicit mixin derives non-leaf construction from one spec" {
3792     const testing = std.testing;
3793 
3794     const ExampleDialect = struct {
3795         pub const name = "template_explicit_region";
3796         const ops = operationTemplate.dialect(@This());
3797 
3798         pub const ContainerOp = struct {
3799             op: *Operation,
3800 
3801             const def = ops.explicit(@This(), .{
3802                 .mnemonic = "container",
3803                 .operands = .{"input"},
3804                 .results = .{"output"},
3805                 .regions = .{"body"},
3806                 .successors = .{"target"},
3807             });
3808             pub const operation_spec = def.operation_spec;
3809             pub const operation_name = def.operation_name;
3810             pub const createOperation = def.createOperation;
3811             pub const getOperand = def.getOperand;
3812             pub const getNamedResult = def.getNamedResult;
3813             pub const getRegion = def.getRegion;
3814             pub const getSuccessor = def.getSuccessor;
3815         };
3816 
3817         pub const spec = dialectSpec(@This(), .{});
3818     };
3819 
3820     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3821     defer ctx.deinit(testing.allocator);
3822     try ctx.allowUnregistered();
3823     try loadDialectSpec(&ctx, ExampleDialect.spec);
3824 
3825     const loc = Location.getUnknown();
3826     const input_type = try ctx.getDialectTypeFromName("template_explicit_region.i32");
3827     const output_type = try ctx.getDialectTypeFromName("template_explicit_region.f32");
3828 
3829     var input_state = Operation.State.init("template_explicit_region.seed", loc);
3830     input_state.addTypes(&.{input_type});
3831     const input_op = try ctx.createOperation(input_state);
3832     const input = input_op.getResult(0).?;
3833 
3834     var body = core.context.initRegion(&ctx);
3835     defer body.deinit();
3836     _ = try body.addBlock();
3837 
3838     var target_region = core.context.initRegion(&ctx);
3839     defer target_region.deinit();
3840     const target = try target_region.addBlock();
3841 
3842     const container = try ExampleDialect.ContainerOp.createOperation(
3843         &ctx,
3844         loc,
3845         &.{input},
3846         &.{output_type},
3847         &.{&body},
3848         &.{target},
3849     );
3850 
3851     try testing.expectEqual(input, container.getOperand("input"));
3852     try testing.expect(container.getNamedResult("output").type.eql(output_type));
3853     try testing.expect(container.getRegion("body").getEntryBlock() != null);
3854     try testing.expectEqual(target, container.getSuccessor("target"));
3855 }
3856 
3857 test "operationTemplate explicit terminator mixin derives construction from one spec" {
3858     const testing = std.testing;
3859 
3860     const ExampleDialect = struct {
3861         pub const name = "template_explicit_terminator";
3862         const ops = operationTemplate.dialect(@This());
3863 
3864         pub const BranchOp = struct {
3865             op: *Operation,
3866 
3867             const term = ops.explicitTerminator(@This(), .{
3868                 .mnemonic = "branch",
3869                 .operands = shape.atLeast(1),
3870                 .operand_names = .{"condition"},
3871                 .successors = shape.atLeast(1),
3872                 .successor_names = .{"target"},
3873             });
3874             pub const operation_spec = term.operation_spec;
3875             pub const operation_name = term.operation_name;
3876             pub const createTerminator = term.createTerminator;
3877             pub const getOperand = term.getOperand;
3878             pub const getSuccessor = term.getSuccessor;
3879         };
3880 
3881         pub const spec = dialectSpec(@This(), .{});
3882     };
3883 
3884     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3885     defer ctx.deinit(testing.allocator);
3886     try ctx.allowUnregistered();
3887     try loadDialectSpec(&ctx, ExampleDialect.spec);
3888 
3889     const loc = Location.getUnknown();
3890     const bool_type = try ctx.getDialectTypeFromName("template_explicit_terminator.bool");
3891     var condition_state = Operation.State.init("template_explicit_terminator.seed", loc);
3892     condition_state.addTypes(&.{bool_type});
3893     const condition_op = try ctx.createOperation(condition_state);
3894     const condition = condition_op.getResult(0).?;
3895 
3896     var target_region = core.context.initRegion(&ctx);
3897     defer target_region.deinit();
3898     const target = try target_region.addBlock();
3899 
3900     const branch = try ExampleDialect.BranchOp.createTerminator(&ctx, loc, &.{condition}, &.{target});
3901 
3902     try testing.expectEqual(condition, branch.getOperand("condition"));
3903     try testing.expectEqual(target, branch.getSuccessor("target"));
3904     try testing.expect(branch.op.hasTraitId(core_traits.Terminator.id));
3905     const info = ctx.lookupOperation(ExampleDialect.BranchOp.operation_name) orelse return error.TestExpectedBranchOp;
3906     try testing.expect(info.traits.is_terminator);
3907     try testing.expect(info.hasTraitId(core_traits.Terminator.id));
3908 }
3909 
3910 test "operationTemplate generates same-type leaf operation wrappers" {
3911     const testing = std.testing;
3912 
3913     const ExampleDialect = struct {
3914         pub const name = "template";
3915         const ops = operationTemplate.dialect(@This());
3916 
3917         pub const AddOp: type = ops.binarySameTypeFold("add", .{
3918             .traits = .{ .is_idempotent = true },
3919         }, operationTemplateNoopFold);
3920 
3921         pub const NegOp: type = ops.unarySameType("neg", .{
3922             .traits = .{ .is_idempotent = true },
3923         });
3924     };
3925 
3926     try testing.expectEqualStrings("template.add", ExampleDialect.AddOp.operation_name);
3927     try testing.expectEqualStrings("template.neg", ExampleDialect.NegOp.operation_name);
3928     try testing.expect(@hasDecl(ExampleDialect.AddOp, "fold"));
3929     try testing.expect(!@hasDecl(ExampleDialect.NegOp, "fold"));
3930     try testing.expect(ExampleDialect.AddOp.operation_spec.shape.operands.allows(2));
3931     try testing.expect(!ExampleDialect.AddOp.operation_spec.shape.operands.allows(1));
3932     try testing.expect(ExampleDialect.NegOp.operation_spec.shape.results.allows(1));
3933 
3934     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3935     defer ctx.deinit(testing.allocator);
3936     try ctx.allowUnregistered();
3937 
3938     const loc = Location.getUnknown();
3939     const i32_type = try ctx.getDialectTypeFromName("template.i32");
3940 
3941     var lhs_state = Operation.State.init("template.seed", loc);
3942     lhs_state.addTypes(&.{i32_type});
3943     const lhs_op = try ctx.createOperation(lhs_state);
3944 
3945     var rhs_state = Operation.State.init("template.seed", loc);
3946     rhs_state.addTypes(&.{i32_type});
3947     const rhs_op = try ctx.createOperation(rhs_state);
3948 
3949     const lhs = lhs_op.getResult(0).?;
3950     const rhs = rhs_op.getResult(0).?;
3951     const add = try ExampleDialect.AddOp.create(&ctx, loc, lhs, rhs);
3952     try testing.expectEqual(lhs, add.getLhs());
3953     try testing.expectEqual(rhs, add.getRhs());
3954     try testing.expect(add.getResult().type.eql(i32_type));
3955 
3956     const neg = try ExampleDialect.NegOp.create(&ctx, loc, add.getResult());
3957     try testing.expectEqual(add.getResult(), neg.getInput());
3958     try testing.expect(neg.getResult().type.eql(i32_type));
3959 }
3960 
3961 test "operationTemplate generates fixed-result leaf operation wrappers" {
3962     const testing = std.testing;
3963 
3964     const ExampleDialect = struct {
3965         pub const name = "template_fixed";
3966         const ops = operationTemplate.dialect(@This());
3967 
3968         pub const NotOp: type = ops.unaryFixedResult("not", .{
3969             .traits = .{ .is_idempotent = true },
3970             .operand_types = &.{typeConstraint.exact(0, "template_fixed.bool")},
3971             .result_types = &.{typeConstraint.exact(0, "template_fixed.bool")},
3972         }, operationTemplateFixedBoolType);
3973 
3974         pub const EqOp: type = ops.binaryFixedResult("eq", .{
3975             .traits = .{ .is_idempotent = true, .is_commutative = true },
3976             .result_types = &.{typeConstraint.exact(0, "template_fixed.bool")},
3977             .dynamic_traits = opSpec.dynamicTraits(.{core_traits.SameTypeOperands}),
3978         }, operationTemplateFixedBoolType);
3979 
3980         pub const spec = dialectSpec(@This(), .{});
3981     };
3982 
3983     try testing.expectEqualStrings("template_fixed.not", ExampleDialect.NotOp.operation_name);
3984     try testing.expectEqualStrings("template_fixed.eq", ExampleDialect.EqOp.operation_name);
3985     try testing.expect(ExampleDialect.NotOp.operation_spec.shape.operands.allows(1));
3986     try testing.expect(ExampleDialect.EqOp.operation_spec.shape.operands.allows(2));
3987     try testing.expect(ExampleDialect.EqOp.operation_spec.shape.results.allows(1));
3988 
3989     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
3990     defer ctx.deinit(testing.allocator);
3991     try ctx.allowUnregistered();
3992     try loadDialectSpec(&ctx, ExampleDialect.spec);
3993 
3994     const loc = Location.getUnknown();
3995     const bool_type = try ctx.getDialectTypeFromName("template_fixed.bool");
3996     const i32_type = try ctx.getDialectTypeFromName("template_fixed.i32");
3997 
3998     var bool_state = Operation.State.init("template_fixed.seed", loc);
3999     bool_state.addTypes(&.{bool_type});
4000     const bool_op = try ctx.createOperation(bool_state);
4001 
4002     var lhs_state = Operation.State.init("template_fixed.seed", loc);
4003     lhs_state.addTypes(&.{i32_type});
4004     const lhs_op = try ctx.createOperation(lhs_state);
4005 
4006     var rhs_state = Operation.State.init("template_fixed.seed", loc);
4007     rhs_state.addTypes(&.{i32_type});
4008     const rhs_op = try ctx.createOperation(rhs_state);
4009 
4010     const not = try ExampleDialect.NotOp.create(&ctx, loc, bool_op.getResult(0).?);
4011     try testing.expectEqual(bool_op.getResult(0).?, not.getInput());
4012     try testing.expect(not.getResult().type.eql(bool_type));
4013 
4014     const eq = try ExampleDialect.EqOp.create(&ctx, loc, lhs_op.getResult(0).?, rhs_op.getResult(0).?);
4015     try testing.expectEqual(lhs_op.getResult(0).?, eq.getLhs());
4016     try testing.expectEqual(rhs_op.getResult(0).?, eq.getRhs());
4017     try testing.expect(eq.getResult().type.eql(bool_type));
4018 
4019     const not_info = ctx.lookupOperation(ExampleDialect.NotOp.operation_name) orelse return error.TestExpectedNotOp;
4020     try testing.expectEqual(@as(usize, 1), not_info.getOperandTypeConstraints().len);
4021     try testing.expectEqual(@as(usize, 1), not_info.getResultTypeConstraints().len);
4022     const eq_info = ctx.lookupOperation(ExampleDialect.EqOp.operation_name) orelse return error.TestExpectedEqOp;
4023     try testing.expect(eq_info.traits.is_commutative);
4024     try testing.expectEqual(@as(usize, 1), eq_info.getResultTypeConstraints().len);
4025 }
4026 
4027 test "operationTemplate carries verifier helper metadata" {
4028     const testing = std.testing;
4029 
4030     const ExampleDialect = struct {
4031         pub const name = "template_verify";
4032         const ops = operationTemplate.dialect(@This());
4033 
4034         pub const AddOp: type = ops.binarySameType("add", .{
4035             .interfaces = &.{opSpec.verifier(operationTemplateNoopVerify)},
4036         });
4037 
4038         pub const spec = dialectSpec(@This(), .{});
4039     };
4040 
4041     try testing.expectEqualStrings("template_verify.add", ExampleDialect.AddOp.operation_name);
4042     try testing.expectEqual(@as(usize, 1), ExampleDialect.AddOp.operation_spec.interfaces.len);
4043 
4044     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4045     defer ctx.deinit(testing.allocator);
4046     try ctx.allowUnregistered();
4047 
4048     try loadDialectSpec(&ctx, ExampleDialect.spec);
4049     const info = ctx.lookupOperation(ExampleDialect.AddOp.operation_name) orelse return error.TestExpectedAddOp;
4050     try testing.expect(info.hasInterface(VerifyOpInterface.id));
4051 }
4052 
4053 test "operationTemplate generates ternary same-type wrappers" {
4054     const testing = std.testing;
4055 
4056     const ExampleDialect = struct {
4057         pub const name = "template_ternary";
4058         const ops = operationTemplate.dialect(@This());
4059 
4060         pub const FmaOp: type = ops.ternarySameType("fma", .{
4061             .traits = .{ .is_idempotent = true },
4062         });
4063     };
4064 
4065     try testing.expectEqualStrings("template_ternary.fma", ExampleDialect.FmaOp.operation_name);
4066     try testing.expect(ExampleDialect.FmaOp.operation_spec.shape.operands.allows(3));
4067     try testing.expect(!ExampleDialect.FmaOp.operation_spec.shape.operands.allows(2));
4068     try testing.expect(ExampleDialect.FmaOp.operation_spec.shape.results.allows(1));
4069 
4070     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4071     defer ctx.deinit(testing.allocator);
4072     try ctx.allowUnregistered();
4073 
4074     const loc = Location.getUnknown();
4075     const f32_type = try ctx.getDialectTypeFromName("template_ternary.f32");
4076 
4077     var a_state = Operation.State.init("template_ternary.seed", loc);
4078     a_state.addTypes(&.{f32_type});
4079     const a_op = try ctx.createOperation(a_state);
4080 
4081     var b_state = Operation.State.init("template_ternary.seed", loc);
4082     b_state.addTypes(&.{f32_type});
4083     const b_op = try ctx.createOperation(b_state);
4084 
4085     var c_state = Operation.State.init("template_ternary.seed", loc);
4086     c_state.addTypes(&.{f32_type});
4087     const c_op = try ctx.createOperation(c_state);
4088 
4089     const fma = try ExampleDialect.FmaOp.create(
4090         &ctx,
4091         loc,
4092         a_op.getResult(0).?,
4093         b_op.getResult(0).?,
4094         c_op.getResult(0).?,
4095     );
4096     try testing.expectEqual(a_op.getResult(0).?, fma.getA());
4097     try testing.expectEqual(b_op.getResult(0).?, fma.getB());
4098     try testing.expectEqual(c_op.getResult(0).?, fma.getC());
4099     try testing.expect(fma.getResult().type.eql(f32_type));
4100 }
4101 
4102 test "operationTemplate generates select same-type wrappers" {
4103     const testing = std.testing;
4104 
4105     const ExampleDialect = struct {
4106         pub const name = "template_select";
4107         const ops = operationTemplate.dialect(@This());
4108 
4109         pub const SelectOp: type = ops.selectSameTypeFold("select", .{
4110             .traits = .{ .is_idempotent = true },
4111         }, operationTemplateNoopFold);
4112     };
4113 
4114     try testing.expectEqualStrings("template_select.select", ExampleDialect.SelectOp.operation_name);
4115     try testing.expect(@hasDecl(ExampleDialect.SelectOp, "fold"));
4116     try testing.expect(ExampleDialect.SelectOp.operation_spec.shape.operands.allows(3));
4117     try testing.expect(ExampleDialect.SelectOp.operation_spec.shape.results.allows(1));
4118 
4119     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4120     defer ctx.deinit(testing.allocator);
4121     try ctx.allowUnregistered();
4122 
4123     const loc = Location.getUnknown();
4124     const bool_type = try ctx.getDialectTypeFromName("template_select.bool");
4125     const i32_type = try ctx.getDialectTypeFromName("template_select.i32");
4126 
4127     var cond_state = Operation.State.init("template_select.seed", loc);
4128     cond_state.addTypes(&.{bool_type});
4129     const cond_op = try ctx.createOperation(cond_state);
4130 
4131     var true_state = Operation.State.init("template_select.seed", loc);
4132     true_state.addTypes(&.{i32_type});
4133     const true_op = try ctx.createOperation(true_state);
4134 
4135     var false_state = Operation.State.init("template_select.seed", loc);
4136     false_state.addTypes(&.{i32_type});
4137     const false_op = try ctx.createOperation(false_state);
4138 
4139     const select = try ExampleDialect.SelectOp.create(
4140         &ctx,
4141         loc,
4142         cond_op.getResult(0).?,
4143         true_op.getResult(0).?,
4144         false_op.getResult(0).?,
4145     );
4146     try testing.expectEqual(cond_op.getResult(0).?, select.getCondition());
4147     try testing.expectEqual(true_op.getResult(0).?, select.getTrueValue());
4148     try testing.expectEqual(false_op.getResult(0).?, select.getFalseValue());
4149     try testing.expect(select.getResult().type.eql(i32_type));
4150 }
4151 
4152 test "operationTemplate generates explicit-result unary wrappers" {
4153     const testing = std.testing;
4154 
4155     const ExampleDialect = struct {
4156         pub const name = "template_cast";
4157         const ops = operationTemplate.dialect(@This());
4158 
4159         pub const CastOp: type = ops.unaryExplicitTypeFold("cast", .{
4160             .traits = .{ .is_idempotent = true },
4161         }, operationTemplateNoopFold);
4162 
4163         pub const SplatOp: type = ops.unaryExplicitType("splat", .{
4164             .traits = .{ .is_idempotent = true },
4165         });
4166     };
4167 
4168     try testing.expectEqualStrings("template_cast.cast", ExampleDialect.CastOp.operation_name);
4169     try testing.expectEqualStrings("template_cast.splat", ExampleDialect.SplatOp.operation_name);
4170     try testing.expect(@hasDecl(ExampleDialect.CastOp, "fold"));
4171     try testing.expect(!@hasDecl(ExampleDialect.SplatOp, "fold"));
4172     try testing.expect(ExampleDialect.SplatOp.operation_spec.shape.operands.allows(1));
4173     try testing.expect(ExampleDialect.SplatOp.operation_spec.shape.results.allows(1));
4174 
4175     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4176     defer ctx.deinit(testing.allocator);
4177     try ctx.allowUnregistered();
4178 
4179     const loc = Location.getUnknown();
4180     const input_type = try ctx.getDialectTypeFromName("template_cast.i32");
4181     const output_type = try ctx.getDialectTypeFromName("template_cast.f32");
4182 
4183     var input_state = Operation.State.init("template_cast.seed", loc);
4184     input_state.addTypes(&.{input_type});
4185     const input_op = try ctx.createOperation(input_state);
4186     const input = input_op.getResult(0).?;
4187 
4188     const cast = try ExampleDialect.CastOp.create(&ctx, loc, input, output_type);
4189     try testing.expectEqual(input, cast.getInput());
4190     try testing.expect(cast.getResult().type.eql(output_type));
4191 
4192     const splat = try ExampleDialect.SplatOp.create(&ctx, loc, input, output_type);
4193     try testing.expectEqual(input, splat.getInput());
4194     try testing.expect(splat.getResult().type.eql(output_type));
4195 }
4196 
4197 test "operationTemplate generates unary attr and no-result wrappers" {
4198     const testing = std.testing;
4199 
4200     const ExampleDialect = struct {
4201         pub const name = "template_unary_attr";
4202         const ops = operationTemplate.dialect(@This());
4203 
4204         pub const BorrowOp: type = ops.unarySameTypeStringAttr(
4205             "borrow",
4206             "ownership",
4207             "borrowed",
4208             .{},
4209         );
4210 
4211         pub const SegmentedBorrowOp: type = ops.unarySameTypeStringAttr("segmented_borrow", "ownership", "borrowed", .{
4212             .operand_segments = segments.operands(.{1}),
4213         });
4214 
4215         pub const ReleaseOp: type = ops.unaryNoResult("release", .{});
4216 
4217         pub const spec = dialectSpec(@This(), .{});
4218     };
4219 
4220     try testing.expectEqualStrings("template_unary_attr.borrow", ExampleDialect.BorrowOp.operation_name);
4221     try testing.expectEqualStrings("template_unary_attr.release", ExampleDialect.ReleaseOp.operation_name);
4222     try testing.expect(ExampleDialect.BorrowOp.operation_spec.shape.operands.allows(1));
4223     try testing.expect(ExampleDialect.BorrowOp.operation_spec.shape.results.allows(1));
4224     try testing.expect(ExampleDialect.ReleaseOp.operation_spec.shape.operands.allows(1));
4225     try testing.expect(ExampleDialect.ReleaseOp.operation_spec.shape.results.allows(0));
4226     try testing.expectEqual(@as(usize, 1), ExampleDialect.BorrowOp.operation_spec.inherent_attribute_names.len);
4227     try testing.expectEqualStrings("ownership", ExampleDialect.BorrowOp.operation_spec.inherent_attribute_names[0]);
4228     try testing.expectEqualStrings(
4229         "operand_segment_sizes",
4230         ExampleDialect.SegmentedBorrowOp.operation_spec.operand_segments.?.attribute_name,
4231     );
4232     try testing.expectEqual(@as(usize, 1), ExampleDialect.SegmentedBorrowOp.operation_spec.operand_segments.?.segments.len);
4233 
4234     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4235     defer ctx.deinit(testing.allocator);
4236     try ctx.allowUnregistered();
4237     try loadDialectSpec(&ctx, ExampleDialect.spec);
4238 
4239     const loc = Location.getUnknown();
4240     const i32_type = try ctx.getDialectTypeFromName("template_unary_attr.i32");
4241 
4242     var input_state = Operation.State.init("template_unary_attr.seed", loc);
4243     input_state.addTypes(&.{i32_type});
4244     const input_op = try ctx.createOperation(input_state);
4245     const input = input_op.getResult(0).?;
4246 
4247     const borrow = try ExampleDialect.BorrowOp.create(&ctx, loc, input);
4248     try testing.expectEqual(input, borrow.getInput());
4249     try testing.expect(borrow.getResult().type.eql(i32_type));
4250     try testing.expectEqualStrings("borrowed", borrow.getStringAttr().?);
4251 
4252     const release = try ExampleDialect.ReleaseOp.create(&ctx, loc, borrow.getResult());
4253     try testing.expectEqual(borrow.getResult(), release.getInput());
4254     try testing.expectEqual(@as(usize, 0), release.op.results.items.len);
4255 
4256     const info = ctx.lookupOperation(ExampleDialect.BorrowOp.operation_name) orelse return error.TestExpectedBorrowOp;
4257     try testing.expect(info.hasInherentAttributeName("ownership"));
4258 }
4259 
4260 test "DialectSpec registers operations, interfaces, traits, types, and dialect interfaces" {
4261     const testing = std.testing;
4262 
4263     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4264     defer ctx.deinit(testing.allocator);
4265     try ctx.allowUnregistered();
4266 
4267     const MarkerInterface = struct {
4268         const id = interfaces.interfaceId("spec.marker");
4269         const VTable = struct { marker: u8 };
4270         const table = VTable{ .marker = 7 };
4271     };
4272 
4273     const TypeMarkerInterface = struct {
4274         const id = interfaces.interfaceId("spec.type.marker");
4275         const VTable = struct { marker: u8 };
4276         const table = VTable{ .marker = 9 };
4277     };
4278 
4279     const OpFallbackInterface = struct {
4280         pub const id = interfaces.interfaceId("spec.op.fallback");
4281         pub const VTable = struct { marker: u8 };
4282         const table = VTable{ .marker = 11 };
4283     };
4284 
4285     const ExplicitOpInterface = struct {
4286         pub const id = interfaces.interfaceId("spec.op.explicit");
4287         pub const VTable = struct { marker: u8 };
4288         const table = VTable{ .marker = 12 };
4289     };
4290 
4291     const TypeFallbackInterface = struct {
4292         pub const id = interfaces.interfaceId("spec.type.fallback");
4293         pub const VTable = struct { marker: u8 };
4294         const table = VTable{ .marker = 13 };
4295     };
4296 
4297     const ExampleProperties = struct {
4298         value: u8 = 0,
4299 
4300         fn init(storage: *anyopaque, _: std.mem.Allocator) anyerror!void {
4301             const self: *@This() = @ptrCast(@alignCast(storage));
4302             self.* = .{};
4303         }
4304 
4305         fn deinit(_: *anyopaque, _: std.mem.Allocator) void {}
4306 
4307         fn copyProperties(dest: *anyopaque, source: *const anyopaque) anyerror!void {
4308             const dest_self: *@This() = @ptrCast(@alignCast(dest));
4309             const source_self: *const @This() = @ptrCast(@alignCast(source));
4310             dest_self.* = source_self.*;
4311         }
4312 
4313         const model = interfaces.OperationPropertiesModel{
4314             .name = "spec.example.properties",
4315             .size = @sizeOf(@This()),
4316             .alignment = std.mem.Alignment.fromByteUnits(@alignOf(@This())),
4317             .init = init,
4318             .deinit = deinit,
4319             .copyProperties = copyProperties,
4320         };
4321     };
4322 
4323     const Trait = struct {
4324         pub const trait_name = "spec.trait";
4325         pub const id = interfaces.traitId(trait_name);
4326         fn verify(_: *const anyopaque) anyerror!void {}
4327         pub const vtable = interfaces.TraitVTable{ .verify = verify };
4328         pub fn entry() interfaces.TraitEntry {
4329             return .{ .id = id, .vtable = &vtable };
4330         }
4331     };
4332 
4333     const Fallbacks = struct {
4334         fn op(_: *const Operation) ?*const anyopaque {
4335             return @ptrCast(&OpFallbackInterface.table);
4336         }
4337 
4338         fn typ(_: *const Context, _: Type) ?*const anyopaque {
4339             return @ptrCast(&TypeFallbackInterface.table);
4340         }
4341     };
4342 
4343     const DialectForTest = struct {
4344         pub const name = "spec";
4345         const op_specs = opSpec.dialect(@This());
4346         pub const ExampleOp = struct {
4347             pub const operation_spec = op_specs.define(.{
4348                 .mnemonic = "example",
4349                 .traits = interfaces.OperationTraits{ .is_idempotent = true },
4350                 .attrs = &.{ "value", "predicate" },
4351                 .properties = ExampleProperties.model,
4352                 .interfaces = &.{
4353                     interfaces.InterfaceEntry{
4354                         .id = ExplicitOpInterface.id,
4355                         .vtable = &ExplicitOpInterface.table,
4356                     },
4357                 },
4358                 .dynamic_traits = &.{trait(Trait)},
4359             });
4360             pub const operation_name = operation_spec.name;
4361         };
4362         pub const type_names = struct {
4363             pub const value = TypeSpec{
4364                 .name = "spec.value",
4365                 .interfaces = &.{
4366                     .{ .id = TypeMarkerInterface.id, .vtable = &TypeMarkerInterface.table },
4367                 },
4368             };
4369             pub const token = "spec.token";
4370         };
4371         pub const spec = dialectSpec(@This(), .{
4372             .types = typeNames(type_names),
4373             .interfaces = &.{
4374                 .{ .id = MarkerInterface.id, .vtable = &MarkerInterface.table },
4375             },
4376             .op_interface_fallbacks = &.{
4377                 .{ .id = OpFallbackInterface.id, .fallback = Fallbacks.op },
4378             },
4379             .type_interface_fallbacks = &.{
4380                 .{ .id = TypeFallbackInterface.id, .fallback = Fallbacks.typ },
4381             },
4382         });
4383     };
4384 
4385     try loadDialectSpec(&ctx, DialectForTest.spec);
4386 
4387     const info = ctx.lookupOperation(DialectForTest.ExampleOp.operation_name) orelse return error.TestExpectedOperation;
4388     try testing.expect(info.traits.is_idempotent);
4389     try testing.expect(info.hasInherentAttributeName("predicate"));
4390     try testing.expect(info.hasInherentAttributeName("value"));
4391     try testing.expect(info.hasPropertiesModel());
4392     try testing.expect(info.hasInterface(ExplicitOpInterface.id));
4393     try testing.expect(info.hasTraitId(Trait.id));
4394     try testing.expect(ctx.lookupTrait(Trait.id) != null);
4395     const type_info = ctx.lookupType(DialectForTest.type_names.value.name) orelse return error.TestExpectedType;
4396     try testing.expect(type_info.hasInterface(TypeMarkerInterface.id));
4397     try testing.expect(ctx.lookupType(DialectForTest.type_names.token) != null);
4398     try testing.expect(ctx.getDialectInterface(DialectForTest.name, MarkerInterface.id) != null);
4399 
4400     const op_state = Operation.State.init("spec.fallback_op", .unknown);
4401     const op = try ctx.createOperation(op_state);
4402     try testing.expectEqual(@as(u8, 11), op.getInterface(OpFallbackInterface).?.marker);
4403 
4404     const typ = try ctx.getDialectTypeFromName("spec.fallback_type");
4405     try testing.expectEqual(@as(u8, 13), ctx.typeInterface(typ, TypeFallbackInterface).?.vtable.marker);
4406 }
4407 
4408 test "DialectSpec derives verifier and CSE interfaces from operation declarations" {
4409     const testing = std.testing;
4410 
4411     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
4412     defer ctx.deinit(testing.allocator);
4413     try ctx.allowUnregistered();
4414 
4415     const Hooks = struct {
4416         fn verify(_: *const anyopaque) anyerror!void {}
4417         fn verifyRegions(_: *const anyopaque) anyerror!void {}
4418         fn verifySymbolUses(
4419             _: *const anyopaque,
4420             _: *core.SymbolTable.Collection,
4421         ) anyerror!void {}
4422         fn cseIncludeAttr(_: *const anyopaque, _: []const u8, _: Attribute) bool {
4423             return true;
4424         }
4425         fn fold(
4426             _: *const anyopaque,
4427             _: *interfaces.FoldResults,
4428         ) anyerror!void {}
4429     };
4430 
4431     const DialectForTest = struct {
4432         pub const name = "spec_auto";
4433         const op_specs = opSpec.dialect(@This());
4434         pub const ExampleOp = struct {
4435             pub const operation_spec = op_specs.define(.{ .mnemonic = "example" });
4436             pub const operation_name = operation_spec.name;
4437             pub const verify = Hooks.verify;
4438             pub const verifyRegions = Hooks.verifyRegions;
4439             pub const verifySymbolUses = Hooks.verifySymbolUses;
4440             pub const cseIncludeAttr = Hooks.cseIncludeAttr;
4441             pub const fold = Hooks.fold;
4442         };
4443         pub const spec = dialectSpec(@This(), .{});
4444     };
4445 
4446     try loadDialectSpec(&ctx, DialectForTest.spec);
4447 
4448     const info = ctx.lookupOperation(DialectForTest.ExampleOp.operation_name) orelse return error.TestExpectedOperation;
4449     try testing.expect(info.hasInterface(VerifyOpInterface.id));
4450     try testing.expect(info.hasInterface(VerifyRegionOpInterface.id));
4451     try testing.expect(info.hasInterface(interfaces.SymbolUserOpInterface.id));
4452     try testing.expect(info.hasInterface(interfaces.CseOpInterface.id));
4453     try testing.expect(info.hasInterface(interfaces.FoldOpInterface.id));
4454 }