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 }