Repository: xdslproject/xdsl
Pull request #4176: dialects: (pdl_interp) Add operations
Author: login: jumerckx
Labels: name: dialects
description: Changes on the dialects
Created: 2025-04-04T08:18:25Z | Merged: 2025-04-04T09:44:44Z

The [pdl_interp dialect](https://mlir.llvm.org/docs/Dialects/PDLInterpOps/) provides a lower-level abstraction compared to pdl for executing pattern matching and rewriting. Lowering from pdl to pdl_interp is not considered in this pr.
Including `assembly_format` for the operations where this is supported, and using the generic form in tests for the implemented operations where the `assembly_format` does not work.

Only a subset of operations is implemented for now.



Diff:
diff --git a/tests/filecheck/dialects/pdl_interp/pdl_interp_ops.mlir b/tests/filecheck/dialects/pdl_interp/pdl_interp_ops.mlir
new file mode 100644
index 0000000000..e1e41b98ea
--- /dev/null
+++ b/tests/filecheck/dialects/pdl_interp/pdl_interp_ops.mlir
@@ -0,0 +1,180 @@
+// RUN: XDSL_ROUNDTRIP
+// RUN: XDSL_GENERIC_ROUNDTRIP
+
+"pdl_interp.func"() <{function_type = (!pdl.operation) -> (), sym_name = "matcher"}> ({
+^bb0(%arg5: !pdl.operation):
+  %0 = pdl_interp.get_result 0 of %arg5
+  pdl_interp.is_not_null %0 : !pdl.value -> ^bb2, ^bb1
+^bb1:  // 16 preds: ^bb0, ^bb2, ^bb3, ^bb4, ^bb5, ^bb6, ^bb7, ^bb8, ^bb9, ^bb10, ^bb11, ^bb12, ^bb13, ^bb14, ^bb15, ^bb16
+  pdl_interp.finalize
+^bb2:  // pred: ^bb0
+  %1 = pdl_interp.get_operand 0 of %arg5
+  %2 = pdl_interp.get_defining_op of %1 : !pdl.value
+  pdl_interp.is_not_null %2 : !pdl.operation -> ^bb3, ^bb1
+^bb3:  // pred: ^bb2
+  pdl_interp.check_operation_name of %arg5 is "arith.subi" -> ^bb4, ^bb1
+^bb4:  // pred: ^bb3
+  pdl_interp.check_operand_count of %arg5 is 2 -> ^bb5, ^bb1
+^bb5:  // pred: ^bb4
+  pdl_interp.check_result_count of %arg5 is 1 -> ^bb6, ^bb1
+^bb6:  // pred: ^bb5
+  pdl_interp.is_not_null %1 : !pdl.value -> ^bb7, ^bb1
+^bb7:  // pred: ^bb6
+  %3 = pdl_interp.get_operand 1 of %arg5
+  pdl_interp.is_not_null %3 : !pdl.value -> ^bb8, ^bb1
+^bb8:  // pred: ^bb7
+  pdl_interp.check_operation_name of %2 is "arith.addi" -> ^bb9, ^bb1
+^bb9:  // pred: ^bb8
+  pdl_interp.check_operand_count of %2 is 2 -> ^bb10, ^bb1
+^bb10:  // pred: ^bb9
+  pdl_interp.check_result_count of %2 is 1 -> ^bb11, ^bb1
+^bb11:  // pred: ^bb10
+  %4 = pdl_interp.get_operand 0 of %2
+  pdl_interp.is_not_null %4 : !pdl.value -> ^bb12, ^bb1
+^bb12:  // pred: ^bb11
+  %5 = pdl_interp.get_operand 1 of %2
+  pdl_interp.is_not_null %5 : !pdl.value -> ^bb13, ^bb1
+^bb13:  // pred: ^bb12
+  %6 = pdl_interp.get_result 0 of %2
+  pdl_interp.is_not_null %6 : !pdl.value -> ^bb14, ^bb1
+^bb14:  // pred: ^bb13
+  pdl_interp.are_equal %6, %1 : !pdl.value -> ^bb15, ^bb1
+^bb15:  // pred: ^bb14
+  %7 = pdl_interp.get_value_type of %6 : !pdl.type
+  %8 = pdl_interp.get_value_type of %0 : !pdl.type
+  pdl_interp.are_equal %7, %8 : !pdl.type -> ^bb16, ^bb1
+^bb16:  // pred: ^bb15
+  pdl_interp.record_match @rewriters::@pdl_generated_rewriter(%5, %3, %7, %4, %arg5 : !pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) : benefit(1), generatedOps(["arith.subi", "arith.addi"]), loc([%2, %arg5]), root("arith.subi") -> ^bb1
+}) : () -> ()
+module @rewriters {
+  "pdl_interp.func"() <{function_type = (!pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) -> (), sym_name = "pdl_generated_rewriter"}> ({
+  ^bb0(%arg0: !pdl.value, %arg1: !pdl.value, %arg2: !pdl.type, %arg3: !pdl.value, %arg4: !pdl.operation):
+    %0 = "pdl_interp.create_operation"(%arg0, %arg1, %arg2) <{inputAttributeNames = [], name = "arith.subi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+    %1 = pdl_interp.get_result 0 of %0
+    %2 = "pdl_interp.create_operation"(%arg3, %1, %arg2) <{inputAttributeNames = [], name = "arith.addi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+    %3 = pdl_interp.get_result 0 of %2
+    %4 = "pdl_interp.get_results"(%2) : (!pdl.operation) -> !pdl.range<value>
+    pdl_interp.replace %arg4 with (%4 : !pdl.range<value>)
+    pdl_interp.finalize
+  }) : () -> ()
+}
+
+// CHECK:      builtin.module {
+// CHECK-NEXT:   "pdl_interp.func"() <{function_type = (!pdl.operation) -> (), sym_name = "matcher"}> ({
+// CHECK-NEXT:   ^0(%arg5 : !pdl.operation):
+// CHECK-NEXT:     %0 = pdl_interp.get_result 0 of %arg5
+// CHECK-NEXT:     pdl_interp.is_not_null %0 : !pdl.value -> ^1, ^2
+// CHECK-NEXT:   ^2:
+// CHECK-NEXT:     pdl_interp.finalize
+// CHECK-NEXT:   ^1:
+// CHECK-NEXT:     %1 = pdl_interp.get_operand 0 of %arg5
+// CHECK-NEXT:     %2 = pdl_interp.get_defining_op of %1 : !pdl.value
+// CHECK-NEXT:     pdl_interp.is_not_null %2 : !pdl.operation -> ^3, ^2
+// CHECK-NEXT:   ^3:
+// CHECK-NEXT:     pdl_interp.check_operation_name of %arg5 is "arith.subi" -> ^4, ^2
+// CHECK-NEXT:   ^4:
+// CHECK-NEXT:     pdl_interp.check_operand_count of %arg5 is 2 -> ^5, ^2
+// CHECK-NEXT:   ^5:
+// CHECK-NEXT:     pdl_interp.check_result_count of %arg5 is 1 -> ^6, ^2
+// CHECK-NEXT:   ^6:
+// CHECK-NEXT:     pdl_interp.is_not_null %1 : !pdl.value -> ^7, ^2
+// CHECK-NEXT:   ^7:
+// CHECK-NEXT:     %3 = pdl_interp.get_operand 1 of %arg5
+// CHECK-NEXT:     pdl_interp.is_not_null %3 : !pdl.value -> ^8, ^2
+// CHECK-NEXT:   ^8:
+// CHECK-NEXT:     pdl_interp.check_operation_name of %2 is "arith.addi" -> ^9, ^2
+// CHECK-NEXT:   ^9:
+// CHECK-NEXT:     pdl_interp.check_operand_count of %2 is 2 -> ^10, ^2
+// CHECK-NEXT:   ^10:
+// CHECK-NEXT:     pdl_interp.check_result_count of %2 is 1 -> ^11, ^2
+// CHECK-NEXT:   ^11:
+// CHECK-NEXT:     %4 = pdl_interp.get_operand 0 of %2
+// CHECK-NEXT:     pdl_interp.is_not_null %4 : !pdl.value -> ^12, ^2
+// CHECK-NEXT:   ^12:
+// CHECK-NEXT:     %5 = pdl_interp.get_operand 1 of %2
+// CHECK-NEXT:     pdl_interp.is_not_null %5 : !pdl.value -> ^13, ^2
+// CHECK-NEXT:   ^13:
+// CHECK-NEXT:     %6 = pdl_interp.get_result 0 of %2
+// CHECK-NEXT:     pdl_interp.is_not_null %6 : !pdl.value -> ^14, ^2
+// CHECK-NEXT:   ^14:
+// CHECK-NEXT:     pdl_interp.are_equal %6, %1 : !pdl.value -> ^15, ^2
+// CHECK-NEXT:   ^15:
+// CHECK-NEXT:     %7 = pdl_interp.get_value_type of %6 : !pdl.type
+// CHECK-NEXT:     %8 = pdl_interp.get_value_type of %0 : !pdl.type
+// CHECK-NEXT:     pdl_interp.are_equal %7, %8 : !pdl.type -> ^16, ^2
+// CHECK-NEXT:   ^16:
+// CHECK-NEXT:     pdl_interp.record_match @rewriters::@pdl_generated_rewriter(%5, %3, %7, %4, %arg5 : !pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) : benefit(1), generatedOps(["arith.subi", "arith.addi"]), loc([%2, %arg5]), root("arith.subi") -> ^2
+// CHECK-NEXT:   }) : () -> ()
+// CHECK-NEXT:   builtin.module @rewriters {
+// CHECK-NEXT:     "pdl_interp.func"() <{function_type = (!pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) -> (), sym_name = "pdl_generated_rewriter"}> ({
+// CHECK-NEXT:     ^0(%arg0 : !pdl.value, %arg1 : !pdl.value, %arg2 : !pdl.type, %arg3 : !pdl.value, %arg4 : !pdl.operation):
+// CHECK-NEXT:       %0 = "pdl_interp.create_operation"(%arg0, %arg1, %arg2) <{inputAttributeNames = [], name = "arith.subi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+// CHECK-NEXT:       %1 = pdl_interp.get_result 0 of %0
+// CHECK-NEXT:       %2 = "pdl_interp.create_operation"(%arg3, %1, %arg2) <{inputAttributeNames = [], name = "arith.addi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+// CHECK-NEXT:       %3 = pdl_interp.get_result 0 of %2
+// CHECK-NEXT:       %4 = "pdl_interp.get_results"(%2) : (!pdl.operation) -> !pdl.range<value>
+// CHECK-NEXT:       pdl_interp.replace %arg4 with (%4 : !pdl.range<value>)
+// CHECK-NEXT:       pdl_interp.finalize
+// CHECK-NEXT:     }) : () -> ()
+// CHECK-NEXT:   }
+// CHECK-NEXT: }
+
+// CHECK-GENERIC:     "builtin.module"() ({
+// CHECK-GENERIC-NEXT:     "pdl_interp.func"() <{function_type = (!pdl.operation) -> (), sym_name = "matcher"}> ({
+// CHECK-GENERIC-NEXT:     ^0(%arg5 : !pdl.operation):
+// CHECK-GENERIC-NEXT:       %0 = "pdl_interp.get_result"(%arg5) <{index = 0 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%0) [^1, ^2] : (!pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^2:
+// CHECK-GENERIC-NEXT:       "pdl_interp.finalize"() : () -> ()
+// CHECK-GENERIC-NEXT:     ^1:
+// CHECK-GENERIC-NEXT:       %1 = "pdl_interp.get_operand"(%arg5) <{index = 0 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:       %2 = "pdl_interp.get_defining_op"(%1) : (!pdl.value) -> !pdl.operation
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%2) [^3, ^2] : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^3:
+// CHECK-GENERIC-NEXT:       "pdl_interp.check_operation_name"(%arg5) [^4, ^2] <{name = "arith.subi"}> : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^4:
+// CHECK-GENERIC-NEXT:       "pdl_interp.check_operand_count"(%arg5) [^5, ^2] <{count = 2 : i32}> : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^5:
+// CHECK-GENERIC-NEXT:       "pdl_interp.check_result_count"(%arg5) [^6, ^2] <{count = 1 : i32}> : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^6:
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%1) [^7, ^2] : (!pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^7:
+// CHECK-GENERIC-NEXT:       %3 = "pdl_interp.get_operand"(%arg5) <{index = 1 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%3) [^8, ^2] : (!pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^8:
+// CHECK-GENERIC-NEXT:       "pdl_interp.check_operation_name"(%2) [^9, ^2] <{name = "arith.addi"}> : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^9:
+// CHECK-GENERIC-NEXT:       "pdl_interp.check_operand_count"(%2) [^10, ^2] <{count = 2 : i32}> : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^10:
+// CHECK-GENERIC-NEXT:       "pdl_interp.check_result_count"(%2) [^11, ^2] <{count = 1 : i32}> : (!pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     ^11:
+// CHECK-GENERIC-NEXT:       %4 = "pdl_interp.get_operand"(%2) <{index = 0 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%4) [^12, ^2] : (!pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^12:
+// CHECK-GENERIC-NEXT:       %5 = "pdl_interp.get_operand"(%2) <{index = 1 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%5) [^13, ^2] : (!pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^13:
+// CHECK-GENERIC-NEXT:       %6 = "pdl_interp.get_result"(%2) <{index = 0 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:       "pdl_interp.is_not_null"(%6) [^14, ^2] : (!pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^14:
+// CHECK-GENERIC-NEXT:       "pdl_interp.are_equal"(%6, %1) [^15, ^2] : (!pdl.value, !pdl.value) -> ()
+// CHECK-GENERIC-NEXT:     ^15:
+// CHECK-GENERIC-NEXT:       %7 = "pdl_interp.get_value_type"(%6) : (!pdl.value) -> !pdl.type
+// CHECK-GENERIC-NEXT:       %8 = "pdl_interp.get_value_type"(%0) : (!pdl.value) -> !pdl.type
+// CHECK-GENERIC-NEXT:       "pdl_interp.are_equal"(%7, %8) [^16, ^2] : (!pdl.type, !pdl.type) -> ()
+// CHECK-GENERIC-NEXT:     ^16:
+// CHECK-GENERIC-NEXT:       "pdl_interp.record_match"(%5, %3, %7, %4, %arg5, %2, %arg5) [^2] <{rewriter = @rewriters::@pdl_generated_rewriter, benefit = 1 : i16, generatedOps = ["arith.subi", "arith.addi"], rootKind = "arith.subi", operandSegmentSizes = array<i32: 5, 2>}> : (!pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation, !pdl.operation, !pdl.operation) -> ()
+// CHECK-GENERIC-NEXT:     }) : () -> ()
+// CHECK-GENERIC-NEXT:     "builtin.module"() <{sym_name = "rewriters"}> ({
+// CHECK-GENERIC-NEXT:       "pdl_interp.func"() <{function_type = (!pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) -> (), sym_name = "pdl_generated_rewriter"}> ({
+// CHECK-GENERIC-NEXT:       ^0(%arg0 : !pdl.value, %arg1 : !pdl.value, %arg2 : !pdl.type, %arg3 : !pdl.value, %arg4 : !pdl.operation):
+// CHECK-GENERIC-NEXT:         %0 = "pdl_interp.create_operation"(%arg0, %arg1, %arg2) <{inputAttributeNames = [], name = "arith.subi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+// CHECK-GENERIC-NEXT:         %1 = "pdl_interp.get_result"(%0) <{index = 0 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:         %2 = "pdl_interp.create_operation"(%arg3, %1, %arg2) <{inputAttributeNames = [], name = "arith.addi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+// CHECK-GENERIC-NEXT:         %3 = "pdl_interp.get_result"(%2) <{index = 0 : i32}> : (!pdl.operation) -> !pdl.value
+// CHECK-GENERIC-NEXT:         %4 = "pdl_interp.get_results"(%2) : (!pdl.operation) -> !pdl.range<value>
+// CHECK-GENERIC-NEXT:         "pdl_interp.replace"(%arg4, %4) : (!pdl.operation, !pdl.range<value>) -> ()
+// CHECK-GENERIC-NEXT:         "pdl_interp.finalize"() : () -> ()
+// CHECK-GENERIC-NEXT:       }) : () -> ()
+// CHECK-GENERIC-NEXT:     }) : () -> ()
+// CHECK-GENERIC-NEXT:   }) : () -> ()
diff --git a/tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir b/tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir
new file mode 100644
index 0000000000..1b56c02a94
--- /dev/null
+++ b/tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir
@@ -0,0 +1,122 @@
+// RUN: xdsl-opt %s --print-op-generic | mlir-opt --mlir-print-op-generic | xdsl-opt | filecheck %s
+// RUN: xdsl-opt %s | mlir-opt --mlir-print-op-generic | xdsl-opt | filecheck %s
+// TODO: test custom syntax parsing once all operations have their custom syntax implemented.
+
+
+"pdl_interp.func"() <{function_type = (!pdl.operation) -> (), sym_name = "matcher"}> ({
+^bb0(%arg5: !pdl.operation):
+  %0 = pdl_interp.get_result 0 of %arg5
+  pdl_interp.is_not_null %0 : !pdl.value -> ^bb2, ^bb1
+^bb1:  // 16 preds: ^bb0, ^bb2, ^bb3, ^bb4, ^bb5, ^bb6, ^bb7, ^bb8, ^bb9, ^bb10, ^bb11, ^bb12, ^bb13, ^bb14, ^bb15, ^bb16
+  pdl_interp.finalize
+^bb2:  // pred: ^bb0
+  %1 = pdl_interp.get_operand 0 of %arg5
+  %2 = pdl_interp.get_defining_op of %1 : !pdl.value
+  pdl_interp.is_not_null %2 : !pdl.operation -> ^bb3, ^bb1
+^bb3:  // pred: ^bb2
+  pdl_interp.check_operation_name of %arg5 is "arith.subi" -> ^bb4, ^bb1
+^bb4:  // pred: ^bb3
+  pdl_interp.check_operand_count of %arg5 is 2 -> ^bb5, ^bb1
+^bb5:  // pred: ^bb4
+  pdl_interp.check_result_count of %arg5 is 1 -> ^bb6, ^bb1
+^bb6:  // pred: ^bb5
+  pdl_interp.is_not_null %1 : !pdl.value -> ^bb7, ^bb1
+^bb7:  // pred: ^bb6
+  %3 = pdl_interp.get_operand 1 of %arg5
+  pdl_interp.is_not_null %3 : !pdl.value -> ^bb8, ^bb1
+^bb8:  // pred: ^bb7
+  pdl_interp.check_operation_name of %2 is "arith.addi" -> ^bb9, ^bb1
+^bb9:  // pred: ^bb8
+  pdl_interp.check_operand_count of %2 is 2 -> ^bb10, ^bb1
+^bb10:  // pred: ^bb9
+  pdl_interp.check_result_count of %2 is 1 -> ^bb11, ^bb1
+^bb11:  // pred: ^bb10
+  %4 = pdl_interp.get_operand 0 of %2
+  pdl_interp.is_not_null %4 : !pdl.value -> ^bb12, ^bb1
+^bb12:  // pred: ^bb11
+  %5 = pdl_interp.get_operand 1 of %2
+  pdl_interp.is_not_null %5 : !pdl.value -> ^bb13, ^bb1
+^bb13:  // pred: ^bb12
+  %6 = pdl_interp.get_result 0 of %2
+  pdl_interp.is_not_null %6 : !pdl.value -> ^bb14, ^bb1
+^bb14:  // pred: ^bb13
+  pdl_interp.are_equal %6, %1 : !pdl.value -> ^bb15, ^bb1
+^bb15:  // pred: ^bb14
+  %7 = pdl_interp.get_value_type of %6 : !pdl.type
+  %8 = pdl_interp.get_value_type of %0 : !pdl.type
+  pdl_interp.are_equal %7, %8 : !pdl.type -> ^bb16, ^bb1
+^bb16:  // pred: ^bb15
+  pdl_interp.record_match @rewriters::@pdl_generated_rewriter(%5, %3, %7, %4, %arg5 : !pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) : benefit(1), generatedOps(["arith.subi", "arith.addi"]), loc([%2, %arg5]), root("arith.subi") -> ^bb1
+}) : () -> ()
+module @rewriters {
+  "pdl_interp.func"() <{function_type = (!pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) -> (), sym_name = "pdl_generated_rewriter"}> ({
+  ^bb0(%arg0: !pdl.value, %arg1: !pdl.value, %arg2: !pdl.type, %arg3: !pdl.value, %arg4: !pdl.operation):
+    %0 = "pdl_interp.create_operation"(%arg0, %arg1, %arg2) <{inputAttributeNames = [], name = "arith.subi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+    %1 = pdl_interp.get_result 0 of %0
+    %2 = "pdl_interp.create_operation"(%arg3, %1, %arg2) <{inputAttributeNames = [], name = "arith.addi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+    %3 = pdl_interp.get_result 0 of %2
+    %4 = "pdl_interp.get_results"(%2) : (!pdl.operation) -> !pdl.range<value>
+    pdl_interp.replace %arg4 with (%4 : !pdl.range<value>)
+    pdl_interp.finalize
+  }) : () -> ()
+}
+
+//CHECK-NEXT:   builtin.module {
+//CHECK:     "pdl_interp.func"() <{function_type = (!pdl.operation) -> (), sym_name = "matcher"}> ({
+//CHECK:     ^0(%arg5 : !pdl.operation):
+//CHECK:       %0 = pdl_interp.get_result 0 of %arg5
+//CHECK:       pdl_interp.is_not_null %0 : !pdl.value -> ^1, ^2
+//CHECK:     ^2:
+//CHECK:       pdl_interp.finalize
+//CHECK:     ^1:
+//CHECK:       %1 = pdl_interp.get_operand 0 of %arg5
+//CHECK:       %2 = pdl_interp.get_defining_op of %1 : !pdl.value
+//CHECK:       pdl_interp.is_not_null %2 : !pdl.operation -> ^3, ^2
+//CHECK:     ^3:
+//CHECK:       pdl_interp.check_operation_name of %arg5 is "arith.subi" -> ^4, ^2
+//CHECK:     ^4:
+//CHECK:       pdl_interp.check_operand_count of %arg5 is 2 -> ^5, ^2
+//CHECK:     ^5:
+//CHECK:       pdl_interp.check_result_count of %arg5 is 1 -> ^6, ^2
+//CHECK:     ^6:
+//CHECK:       pdl_interp.is_not_null %1 : !pdl.value -> ^7, ^2
+//CHECK:     ^7:
+//CHECK:       %3 = pdl_interp.get_operand 1 of %arg5
+//CHECK:       pdl_interp.is_not_null %3 : !pdl.value -> ^8, ^2
+//CHECK:     ^8:
+//CHECK:       pdl_interp.check_operation_name of %2 is "arith.addi" -> ^9, ^2
+//CHECK:     ^9:
+//CHECK:       pdl_interp.check_operand_count of %2 is 2 -> ^10, ^2
+//CHECK:     ^10:
+//CHECK:       pdl_interp.check_result_count of %2 is 1 -> ^11, ^2
+//CHECK:     ^11:
+//CHECK:       %4 = pdl_interp.get_operand 0 of %2
+//CHECK:       pdl_interp.is_not_null %4 : !pdl.value -> ^12, ^2
+//CHECK:     ^12:
+//CHECK:       %5 = pdl_interp.get_operand 1 of %2
+//CHECK:       pdl_interp.is_not_null %5 : !pdl.value -> ^13, ^2
+//CHECK:     ^13:
+//CHECK:       %6 = pdl_interp.get_result 0 of %2
+//CHECK:       pdl_interp.is_not_null %6 : !pdl.value -> ^14, ^2
+//CHECK:     ^14:
+//CHECK:       pdl_interp.are_equal %6, %1 : !pdl.value -> ^15, ^2
+//CHECK:     ^15:
+//CHECK:       %7 = pdl_interp.get_value_type of %6 : !pdl.type
+//CHECK:       %8 = pdl_interp.get_value_type of %0 : !pdl.type
+//CHECK:       pdl_interp.are_equal %7, %8 : !pdl.type -> ^16, ^2
+//CHECK:     ^16:
+//CHECK:       pdl_interp.record_match @rewriters::@pdl_generated_rewriter(%5, %3, %7, %4, %arg5 : !pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) : benefit(1), generatedOps(["arith.subi", "arith.addi"]), loc([%2, %arg5]), root("arith.subi") -> ^2
+//CHECK:     }) : () -> ()
+//CHECK:     builtin.module @rewriters {
+//CHECK:       "pdl_interp.func"() <{function_type = (!pdl.value, !pdl.value, !pdl.type, !pdl.value, !pdl.operation) -> (), sym_name = "pdl_generated_rewriter"}> ({
+//CHECK:       ^0(%arg0 : !pdl.value, %arg1 : !pdl.value, %arg2 : !pdl.type, %arg3 : !pdl.value, %arg4 : !pdl.operation):
+//CHECK:         %0 = "pdl_interp.create_operation"(%arg0, %arg1, %arg2) <{inputAttributeNames = [], name = "arith.subi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+//CHECK:         %1 = pdl_interp.get_result 0 of %0
+//CHECK:         %2 = "pdl_interp.create_operation"(%arg3, %1, %arg2) <{inputAttributeNames = [], name = "arith.addi", operandSegmentSizes = array<i32: 2, 0, 1>}> : (!pdl.value, !pdl.value, !pdl.type) -> !pdl.operation
+//CHECK:         %3 = pdl_interp.get_result 0 of %2
+//CHECK:         %4 = "pdl_interp.get_results"(%2) : (!pdl.operation) -> !pdl.range<value>
+//CHECK:         pdl_interp.replace %arg4 with (%4 : !pdl.range<value>)
+//CHECK:         pdl_interp.finalize
+//CHECK:       }) : () -> ()
+//CHECK:     }
+//CHECK:   }
diff --git a/xdsl/dialects/__init__.py b/xdsl/dialects/__init__.py
index 0021b6e44b..09a9fd387c 100644
--- a/xdsl/dialects/__init__.py
+++ b/xdsl/dialects/__init__.py
@@ -193,6 +193,11 @@ def get_pdl():
 
         return PDL
 
+    def get_pdl_interp():
+        from xdsl.dialects.pdl_interp import PDLInterp
+
+        return PDLInterp
+
     def get_printf():
         from xdsl.dialects.printf import Printf
 
@@ -361,6 +366,7 @@ def get_transform():
         "mpi": get_mpi,
         "omp": get_omp,
         "pdl": get_pdl,
+        "pdl_interp": get_pdl_interp,
         "printf": get_printf,
         "ptr_xdsl": get_ptr_xdsl,
         "riscv": get_riscv,
diff --git a/xdsl/dialects/pdl_interp.py b/xdsl/dialects/pdl_interp.py
new file mode 100644
index 0000000000..1aaeecee61
--- /dev/null
+++ b/xdsl/dialects/pdl_interp.py
@@ -0,0 +1,664 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence, Set
+from dataclasses import dataclass
+from typing import ClassVar
+
+from xdsl.dialects.builtin import (
+    I16,
+    I32,
+    ArrayAttr,
+    ContainerOf,
+    DictionaryAttr,
+    Float16Type,
+    Float32Type,
+    Float64Type,
+    FunctionType,
+    IndexType,
+    IntegerAttr,
+    IntegerType,
+    StringAttr,
+    SymbolRefAttr,
+    UnitAttr,
+)
+from xdsl.dialects.pdl import (
+    AnyPDLTypeConstr,
+    AttributeType,
+    OperationType,
+    RangeType,
+    TypeType,
+    ValueType,
+)
+from xdsl.ir import Attribute, Block, Dialect, Operation, Region, SSAValue
+from xdsl.irdl import (
+    AnyAttr,
+    AnyOf,
+    AttrSizedOperandSegments,
+    ConstraintContext,
+    GenericAttrConstraint,
+    IRDLOperation,
+    VarConstraint,
+    base,
+    irdl_op_definition,
+    operand_def,
+    opt_prop_def,
+    prop_def,
+    region_def,
+    result_def,
+    successor_def,
+    traits_def,
+    var_operand_def,
+)
+from xdsl.traits import (
+    CallableOpInterface,
+    IsolatedFromAbove,
+    IsTerminator,
+    SymbolOpInterface,
+)
+from xdsl.utils.exceptions import VerifyException
+from xdsl.utils.hints import isa
+
+boolLike = ContainerOf(IntegerType(1))
+signlessIntegerLike = ContainerOf(AnyOf([IntegerType, IndexType]))
+floatingPointLike = ContainerOf(AnyOf([Float16Type, Float32Type, Float64Type]))
+
+
+@irdl_op_definition
+class GetOperandOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop
+    """
+
+    name = "pdl_interp.get_operand"
+    index = prop_def(IntegerAttr[I32])
+    input_op = operand_def(OperationType)
+    value = result_def(ValueType)
+
+    assembly_format = "$index `of` $input_op attr-dict"
+
+    def __init__(self, index: int | IntegerAttr[I32], input_op: SSAValue) -> None:
+        if isinstance(index, int):
+            index = IntegerAttr.from_int_and_width(index, 32)
+        super().__init__(
+            operands=[input_op], properties={"index": index}, result_types=[ValueType()]
+        )
+
+
+@irdl_op_definition
+class FinalizeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpfinalize-pdl_interpfinalizeop
+    """
+
+    name = "pdl_interp.finalize"
+    traits = traits_def(IsTerminator())
+
+    assembly_format = "attr-dict"
+
+    def __init__(self):
+        super().__init__()
+
+
+@irdl_op_definition
+class CheckOperationNameOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_operation_name-pdl_interpcheckoperationnameop
+    """
+
+    name = "pdl_interp.check_operation_name"
+    traits = traits_def(IsTerminator())
+    operation_name = prop_def(StringAttr, prop_name="name")
+    input_op = operand_def(OperationType)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "`of` $input_op `is` $name attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(
+        self,
+        operation_name: str | StringAttr,
+        input_op: SSAValue,
+        trueDest: Block,
+        falseDest: Block,
+    ) -> None:
+        if isinstance(operation_name, str):
+            operation_name = StringAttr(operation_name)
+        super().__init__(
+            operands=[input_op],
+            properties={"name": operation_name},
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class CheckOperandCountOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_operand_count-pdl_interpcheckoperandcountop
+    """
+
+    name = "pdl_interp.check_operand_count"
+    traits = traits_def(IsTerminator())
+    input_op = operand_def(OperationType)
+    count = prop_def(IntegerAttr[I32])
+    compareAtLeast = opt_prop_def(UnitAttr)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = "`of` $input_op `is` (`at_least` $compareAtLeast^)? $count attr-dict `->` $true_dest `, ` $false_dest"
+
+    def __init__(
+        self,
+        input_op: SSAValue,
+        count: int | IntegerAttr[I32],
+        trueDest: Block,
+        falseDest: Block,
+        compareAtLeast: bool = False,
+    ) -> None:
+        if isinstance(count, int):
+            count = IntegerAttr.from_int_and_width(count, 32)
+        properties = dict[str, Attribute](count=count)
+        if compareAtLeast:
+            properties["compareAtLeast"] = UnitAttr()
+        super().__init__(
+            operands=[input_op],
+            properties=properties,
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class CheckResultCountOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_result_count-pdl_interpcheckresultcountop
+    """
+
+    name = "pdl_interp.check_result_count"
+    traits = traits_def(IsTerminator())
+    input_op = operand_def(OperationType)
+    count = prop_def(IntegerAttr[I32])
+    compareAtLeast = opt_prop_def(UnitAttr)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = "`of` $input_op `is` (`at_least` $compareAtLeast^)? $count attr-dict `->` $true_dest `, ` $false_dest"
+
+    def __init__(
+        self,
+        input_op: SSAValue,
+        count: int | IntegerAttr[I32],
+        trueDest: Block,
+        falseDest: Block,
+        compareAtLeast: bool = False,
+    ) -> None:
+        if isinstance(count, int):
+            count = IntegerAttr.from_int_and_width(count, 32)
+        properties = dict[str, Attribute](count=count)
+        if compareAtLeast:
+            properties["compareAtLeast"] = UnitAttr()
+        super().__init__(
+            operands=[input_op],
+            properties=properties,
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class IsNotNullOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpis_not_null-pdl_interpisnotnullop
+    """
+
+    name = "pdl_interp.is_not_null"
+    traits = traits_def(IsTerminator())
+    value = operand_def(AnyPDLTypeConstr)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "$value `:` type($value) attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(self, value: SSAValue, trueDest: Block, falseDest: Block) -> None:
+        super().__init__(operands=[value], successors=[trueDest, falseDest])
+
+
+@irdl_op_definition
+class GetResultOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_result-pdl_interpgetresultop
+    """
+
+    name = "pdl_interp.get_result"
+    index = prop_def(IntegerAttr[I32])
+    input_op = operand_def(OperationType)
+    value = result_def(ValueType)
+
+    assembly_format = "$index `of` $input_op attr-dict"
+
+    def __init__(self, index: int | IntegerAttr[I32], input_op: SSAValue) -> None:
+        if isinstance(index, int):
+            index = IntegerAttr.from_int_and_width(index, 32)
+        super().__init__(
+            operands=[input_op], properties={"index": index}, result_types=[ValueType()]
+        )
+
+
+@irdl_op_definition
+class GetResultsOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_results-pdl_interpgetresultsop
+    """
+
+    name = "pdl_interp.get_results"
+    index = opt_prop_def(IntegerAttr[I32])
+    input_op = operand_def(OperationType)
+    value = result_def(ValueType | RangeType[ValueType])
+
+    # assembly_format = "($index^)? `of` $input_op `:` type($value) attr-dict"
+    # TODO: Fix bug preventing this assebmly format from working: https://github.com/xdslproject/xdsl/issues/4136
+
+    def __init__(
+        self,
+        index: int | IntegerAttr[I32] | None,
+        input_op: SSAValue,
+        result_type: ValueType | RangeType[ValueType],
+    ) -> None:
+        if isinstance(index, int):
+            index = IntegerAttr.from_int_and_width(index, 32)
+        super().__init__(
+            operands=[input_op],
+            properties={"index": index},
+            result_types=[result_type],
+        )
+
+
+@irdl_op_definition
+class GetAttributeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_attribute-pdl_interpgetattributeop
+    """
+
+    name = "pdl_interp.get_attribute"
+    constraint_name = prop_def(StringAttr, prop_name="name")
+    input_op = operand_def(OperationType)
+    value = result_def(AttributeType)
+
+    assembly_format = "$name `of` $input_op attr-dict"
+
+    def __init__(self, name: str | StringAttr, input_op: SSAValue) -> None:
+        if isinstance(name, str):
+            name = StringAttr(name)
+        super().__init__(
+            operands=[input_op],
+            properties={"name": name},
+            result_types=[AttributeType()],
+        )
+
+
+@irdl_op_definition
+class CheckAttributeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_attribute-pdl_interpcheckattributeop
+    """
+
+    name = "pdl_interp.check_attribute"
+    traits = traits_def(IsTerminator())
+    constantValue = prop_def(Attribute)
+    attribute = operand_def(AttributeType)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "$attribute `is` $constantValue attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(
+        self,
+        constantValue: Attribute,
+        attribute: SSAValue,
+        trueDest: Block,
+        falseDest: Block,
+    ) -> None:
+        super().__init__(
+            operands=[attribute],
+            properties={"constantValue": constantValue},
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class AreEqualOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpare_equal-pdl_interpareequalop
+    """
+
+    name = "pdl_interp.are_equal"
+    traits = traits_def(IsTerminator())
+    T: ClassVar = VarConstraint("T", AnyPDLTypeConstr)
+    lhs = operand_def(T)
+    rhs = operand_def(T)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "operands `:` type($lhs) attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(
+        self, lhs: SSAValue, rhs: SSAValue, trueDest: Block, falseDest: Block
+    ) -> None:
+        super().__init__(operands=[lhs, rhs], successors=[trueDest, falseDest])
+
+
+@irdl_op_definition
+class RecordMatchOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interprecord_match-pdl_interprecordmatchop
+    """
+
+    name = "pdl_interp.record_match"
+    traits = traits_def(IsTerminator())
+    rewriter = prop_def(SymbolRefAttr)
+    rootKind = opt_prop_def(StringAttr)
+    generatedOps = opt_prop_def(ArrayAttr)
+    benefit = prop_def(IntegerAttr[I16])
+
+    inputs = var_operand_def(AnyPDLTypeConstr)
+    matched_ops = var_operand_def(OperationType)
+
+    dest = successor_def()
+
+    irdl_options = [AttrSizedOperandSegments(as_property=True)]
+
+    assembly_format = (
+        "$rewriter (`(` $inputs^ `:` type($inputs) `)`)? `:` `benefit` `(` $benefit `)` `,`"
+        "(`generatedOps` `(` $generatedOps^ `)` `,`)? `loc` `(` `[` $matched_ops `]` `)`"
+        "(`,` `root` `(` $rootKind^ `)`)? attr-dict `->` $dest"
+    )
+
+    def __init__(
+        self,
+        rewriter: str | SymbolRefAttr,
+        root_kind: str | StringAttr,
+        generated_ops: list[OperationType] | None,
+        benefit: int | IntegerAttr[I16],
+        inputs: Sequence[SSAValue],
+        matched_ops: Sequence[SSAValue],
+        dest: Block,
+    ) -> None:
+        if isinstance(rewriter, str):
+            rewriter = SymbolRefAttr(rewriter)
+        if isinstance(root_kind, str):
+            root_kind = StringAttr(root_kind)
+        if (
+            generated_ops is None
+        ):  # TODO: if generatedOps is actually optional (check this), we shouldn't even pass an empty list
+            generated_ops = []
+        if isinstance(benefit, int):
+            benefit = IntegerAttr.from_int_and_width(benefit, 16)
+        super().__init__(
+            operands=[inputs, matched_ops],
+            properties={
+                "rewriter": rewriter,
+                "rootKind": root_kind,
+                "generatedOps": ArrayAttr(generated_ops),
+                "benefit": benefit,
+            },
+            successors=[dest],
+        )
+
+
+@dataclass(frozen=True)
+class ValueConstrFromResultConstr(
+    GenericAttrConstraint[ValueType | RangeType[ValueType]]
+):
+    result_constr: GenericAttrConstraint[TypeType | RangeType[TypeType]]
+
+    def can_infer(self, var_constraint_names: Set[str]) -> bool:
+        return self.result_constr.can_infer(var_constraint_names)
+
+    def infer(self, context: ConstraintContext) -> ValueType | RangeType[ValueType]:
+        result_type = self.result_constr.infer(context)
+        if isinstance(result_type, RangeType):
+            return RangeType(ValueType())
+        return ValueType()
+
+    def verify(self, attr: Attribute, constraint_context: ConstraintContext) -> None:
+        if isa(attr, RangeType[ValueType]):
+            result_type = RangeType(TypeType())
+        elif isa(attr, ValueType):
+            result_type = TypeType()
+        else:
+            raise VerifyException(
+                f"Expected an attribute of type ValueType or RangeType[ValueType], but got {attr}"
+            )
+        return self.result_constr.verify(result_type, constraint_context)
+
+
+@irdl_op_definition
+class GetValueTypeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_value_type-pdl_interpgetvaluetypeop
+    """
+
+    name = "pdl_interp.get_value_type"
+    T: ClassVar = VarConstraint("T", base(TypeType) | base(RangeType[TypeType]))
+    value = operand_def(ValueConstrFromResultConstr(T))
+    result = result_def(T)
+
+    assembly_format = "`of` $value `:` type($result) attr-dict"
+
+    def __init__(self, value: SSAValue) -> None:
+        super().__init__(
+            operands=[value],
+            result_types=[
+                RangeType(TypeType())
+                if isinstance(value.type, RangeType)
+                else TypeType()
+            ],
+        )
+
+
+@irdl_op_definition
+class ReplaceOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpreplace-pdl_interpreplaceop
+    """
+
+    name = "pdl_interp.replace"
+    input_op = operand_def(OperationType)
+    repl_values = var_operand_def(ValueType | RangeType[ValueType])
+
+    assembly_format = (
+        "$input_op `with` ` ` `(` ($repl_values^ `:` type($repl_values))? `)` attr-dict"
+    )
+
+    def __init__(self, input_op: SSAValue, repl_values: list[SSAValue]) -> None:
+        super().__init__(operands=[input_op, repl_values])
+
+
+@irdl_op_definition
+class CreateAttributeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcreate_attribute-pdl_interpcreateattributeop
+    """
+
+    name = "pdl_interp.create_attribute"
+    value = prop_def(AnyAttr())
+    attribute = result_def(AttributeType)
+
+    assembly_format = "$value attr-dict-with-keyword"
+
+    def __init__(self, value: Attribute) -> None:
+        super().__init__(properties={"value": value}, result_types=[AttributeType()])
+
+
+@irdl_op_definition
+class CreateOperationOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcreate_operation-pdl_interpcreateoperationop
+    """
+
+    name = "pdl_interp.create_operation"
+    constraint_name = prop_def(StringAttr, prop_name="name")
+    input_attribute_names = prop_def(ArrayAttr, prop_name="inputAttributeNames")
+    inferred_result_types = opt_prop_def(UnitAttr, prop_name="inferredResultTypes")
+
+    input_operands = var_operand_def(ValueType | RangeType[ValueType])
+    input_attributes = var_operand_def(AttributeType | RangeType[AttributeType])
+    input_result_types = var_operand_def(TypeType | RangeType[TypeType])
+
+    result_op = result_def(OperationType)
+
+    irdl_options = [AttrSizedOperandSegments(as_property=True)]
+
+    # assembly_format = (
+    #     "$name (`(` $input_operands^ `:` type($input_operands) `)`)?"
+    #     "` `custom<CreateOperationOpAttributes>($inputAttributes, $inputAttributeNames)"
+    #     "custom<CreateOperationOpResults>($inputResultTypes, type($inputResultTypes), $inferredResultTypes)"
+    #     "attr-dict"
+    # )
+    # TODO: this assebly format is unsupported in xDSL because of the `custom` directives.
+
+    def __init__(
+        self,
+        name: str | StringAttr,
+        inferred_result_types: UnitAttr | None = None,
+        input_attribute_names: Iterable[StringAttr] | None = None,
+        input_operands: Sequence[SSAValue] | None = None,
+        input_attributes: Sequence[SSAValue] | None = None,
+        input_result_types: Sequence[SSAValue] | None = None,
+    ) -> None:
+        if isinstance(name, str):
+            name = StringAttr(name)
+        if input_attribute_names is not None:
+            input_attribute_names = ArrayAttr(input_attribute_names)
+        if input_attribute_names is None:
+            input_attribute_names = ArrayAttr([])
+
+        if input_operands is None:
+            input_operands = []
+        if input_attributes is None:
+            input_attributes = []
+        if input_result_types is None:
+            input_result_types = []
+
+        super().__init__(
+            operands=[input_operands, input_attributes, input_result_types],
+            result_types=[OperationType()],
+            properties={
+                "name": name,
+                "inferredResultTypes": inferred_result_types,
+                "inputAttributeNames": input_attribute_names,
+            }
+            if inferred_result_types
+            else {
+                "name": name,
+                "inputAttributeNames": input_attribute_names,
+            },
+        )
+
+
+@irdl_op_definition
+class GetDefiningOpOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_defining_op-pdl_interpgetdefiningopop
+    """
+
+    name = "pdl_interp.get_defining_op"
+    value = operand_def(ValueType | ArrayAttr[ValueType])
+    input_op = result_def(OperationType)
+
+    assembly_format = "`of` $value `:` type($value) attr-dict"
+
+    def __init__(self, value: SSAValue) -> None:
+        super().__init__(operands=[value], result_types=[OperationType()])
+
+
+class FuncOpCallableInterface(CallableOpInterface):
+    @classmethod
+    def get_callable_region(cls, op: Operation) -> Region:
+        assert isinstance(op, FuncOp)
+        return op.body
+
+    @classmethod
+    def get_argument_types(cls, op: Operation) -> tuple[Attribute, ...]:
+        assert isinstance(op, FuncOp)
+        return op.function_type.inputs.data
+
+    @classmethod
+    def get_result_types(cls, op: Operation) -> tuple[Attribute, ...]:
+        assert isinstance(op, FuncOp)
+        return op.function_type.outputs.data
+
+
+@irdl_op_definition
+class FuncOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpfunc-pdl_interpfuncop
+    """
+
+    name = "pdl_interp.func"
+    sym_name = prop_def(StringAttr)
+    function_type = prop_def(FunctionType)
+    arg_attrs = opt_prop_def(ArrayAttr[DictionaryAttr])
+    res_attrs = opt_prop_def(ArrayAttr[DictionaryAttr])
+
+    body = region_def()
+
+    traits = traits_def(
+        IsolatedFromAbove(), SymbolOpInterface(), FuncOpCallableInterface()
+    )
+
+    def __init__(
+        self,
+        sym_name: str | StringAttr,
+        function_type: FunctionType | tuple[Sequence[Attribute], Sequence[Attribute]],
+        arg_attrs: ArrayAttr[DictionaryAttr] | None = None,
+        res_attrs: ArrayAttr[DictionaryAttr] | None = None,
+        region: Region | type[Region.DEFAULT] = Region.DEFAULT,
+    ) -> None:
+        if isinstance(sym_name, str):
+            sym_name = StringAttr(sym_name)
+        if isinstance(function_type, tuple):
+            inputs, outputs = function_type
+            function_type = FunctionType.from_lists(inputs, outputs)
+        if not isinstance(region, Region):
+            region = Region(Block(arg_types=function_type.inputs))
+
+        super().__init__(
+            properties={
+                "sym_name": sym_name,
+                "function_type": function_type,
+                "arg_attrs": arg_attrs,
+                "res_attrs": res_attrs,
+            },
+            regions=[region],
+        )
+
+
+PDLInterp = Dialect(
+    "pdl_interp",
+    [
+        GetOperandOp,
+        FinalizeOp,
+        CheckOperationNameOp,
+        CheckOperandCountOp,
+        CheckResultCountOp,
+        IsNotNullOp,
+        GetResultOp,
+        GetResultsOp,
+        GetAttributeOp,
+        CheckAttributeOp,
+        AreEqualOp,
+        RecordMatchOp,
+        GetValueTypeOp,
+        ReplaceOp,
+        CreateAttributeOp,
+        CreateOperationOp,
+        FuncOp,
+        GetDefiningOpOp,
+    ],
+)


Reviews:
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:33:31Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:37:00Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:37:09Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T08:40:00Z
user: login: jumerckx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T08:50:56Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T09:08:20Z
user: login: superlopuh
body: LGTM!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-04T09:16:03Z
user: login: compor
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T09:51:18Z
user: login: superlopuh
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-04T09:54:27Z
user: login: jumerckx
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T10:00:13Z
user: login: compor
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-04T10:02:57Z

Review comments:
diff_hunk: @@ -0,0 +1,122 @@
+// RUN: XDSL_ROUNDTRIP
+
+module {
path: tests/filecheck/dialects/pdl_interp/pdl_interp_ops.mlir
user: login: superlopuh
body: no need for module
created_at: 2025-04-04T08:33:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,119 @@
+// RUN: mlir-opt %s --mlir-print-op-generic | xdsl-opt --print-op-generic | filecheck %s
path: tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir
user: login: superlopuh
body: I would add a second test for custom syntax, and I would recommend testing parsing and printing both ways.
```suggestion
// RUN: xdsl-opt %s --mlir-print-op-generic | mlir-opt --mlir-print-op-generic | xdsl-opt | filecheck %s
// RUN: xdsl-opt %s | mlir-opt | xdsl-opt | filecheck %s
```
created_at: 2025-04-04T08:37:00Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,119 @@
+// RUN: mlir-opt %s --mlir-print-op-generic | xdsl-opt --print-op-generic | filecheck %s
+
+module {
path: tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir
user: login: superlopuh
body: No need for module 
```suggestion
```
created_at: 2025-04-04T08:37:09Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,664 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence, Set
+from dataclasses import dataclass
+from typing import ClassVar
+
+from xdsl.dialects.builtin import (
+    I16,
+    I32,
+    ArrayAttr,
+    ContainerOf,
+    DictionaryAttr,
+    Float16Type,
+    Float32Type,
+    Float64Type,
+    FunctionType,
+    IndexType,
+    IntegerAttr,
+    IntegerType,
+    StringAttr,
+    SymbolRefAttr,
+    UnitAttr,
+)
+from xdsl.dialects.pdl import (
+    AnyPDLTypeConstr,
+    AttributeType,
+    OperationType,
+    RangeType,
+    TypeType,
+    ValueType,
+)
+from xdsl.ir import Attribute, Block, Dialect, Operation, Region, SSAValue
+from xdsl.irdl import (
+    AnyAttr,
+    AnyOf,
+    AttrSizedOperandSegments,
+    ConstraintContext,
+    GenericAttrConstraint,
+    IRDLOperation,
+    VarConstraint,
+    base,
+    irdl_op_definition,
+    operand_def,
+    opt_prop_def,
+    prop_def,
+    region_def,
+    result_def,
+    successor_def,
+    traits_def,
+    var_operand_def,
+)
+from xdsl.traits import (
+    CallableOpInterface,
+    IsolatedFromAbove,
+    IsTerminator,
+    SymbolOpInterface,
+)
+from xdsl.utils.exceptions import VerifyException
+from xdsl.utils.hints import isa
+
+boolLike = ContainerOf(IntegerType(1))
+signlessIntegerLike = ContainerOf(AnyOf([IntegerType, IndexType]))
+floatingPointLike = ContainerOf(AnyOf([Float16Type, Float32Type, Float64Type]))
+
+
+@irdl_op_definition
+class GetOperandOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop
+    """
+
+    name = "pdl_interp.get_operand"
+    index = prop_def(IntegerAttr[I32])
+    input_op = operand_def(OperationType)
+    value = result_def(ValueType)
+
+    assembly_format = "$index `of` $input_op attr-dict"
+
+    def __init__(self, index: int | IntegerAttr[I32], input_op: SSAValue) -> None:
+        if isinstance(index, int):
+            index = IntegerAttr.from_int_and_width(index, 32)
+        super().__init__(
+            operands=[input_op], properties={"index": index}, result_types=[ValueType()]
+        )
+
+
+@irdl_op_definition
+class FinalizeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpfinalize-pdl_interpfinalizeop
+    """
+
+    name = "pdl_interp.finalize"
+    traits = traits_def(IsTerminator())
+
+    assembly_format = "attr-dict"
+
+    def __init__(self):
+        super().__init__()
+
+
+@irdl_op_definition
+class CheckOperationNameOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_operation_name-pdl_interpcheckoperationnameop
+    """
+
+    name = "pdl_interp.check_operation_name"
+    traits = traits_def(IsTerminator())
+    operation_name = prop_def(StringAttr, prop_name="name")
+    input_op = operand_def(OperationType)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "`of` $input_op `is` $name attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(
+        self,
+        operation_name: str | StringAttr,
+        input_op: SSAValue,
+        trueDest: Block,
+        falseDest: Block,
+    ) -> None:
+        if isinstance(operation_name, str):
+            operation_name = StringAttr(operation_name)
+        super().__init__(
+            operands=[input_op],
+            properties={"name": operation_name},
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class CheckOperandCountOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_operand_count-pdl_interpcheckoperandcountop
+    """
+
+    name = "pdl_interp.check_operand_count"
+    traits = traits_def(IsTerminator())
+    input_op = operand_def(OperationType)
+    count = prop_def(IntegerAttr[I32])
+    compareAtLeast = opt_prop_def(UnitAttr)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = "`of` $input_op `is` (`at_least` $compareAtLeast^)? $count attr-dict `->` $true_dest `, ` $false_dest"
+
+    def __init__(
+        self,
+        input_op: SSAValue,
+        count: int | IntegerAttr[I32],
+        trueDest: Block,
+        falseDest: Block,
+        compareAtLeast: bool = False,
+    ) -> None:
+        if isinstance(count, int):
+            count = IntegerAttr.from_int_and_width(count, 32)
+        properties = dict[str, Attribute](count=count)
+        if compareAtLeast:
+            properties["compareAtLeast"] = UnitAttr()
+        super().__init__(
+            operands=[input_op],
+            properties=properties,
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class CheckResultCountOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_result_count-pdl_interpcheckresultcountop
+    """
+
+    name = "pdl_interp.check_result_count"
+    traits = traits_def(IsTerminator())
+    input_op = operand_def(OperationType)
+    count = prop_def(IntegerAttr[I32])
+    compareAtLeast = opt_prop_def(UnitAttr)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = "`of` $input_op `is` (`at_least` $compareAtLeast^)? $count attr-dict `->` $true_dest `, ` $false_dest"
+
+    def __init__(
+        self,
+        input_op: SSAValue,
+        count: int | IntegerAttr[I32],
+        trueDest: Block,
+        falseDest: Block,
+        compareAtLeast: bool = False,
+    ) -> None:
+        if isinstance(count, int):
+            count = IntegerAttr.from_int_and_width(count, 32)
+        properties = dict[str, Attribute](count=count)
+        if compareAtLeast:
+            properties["compareAtLeast"] = UnitAttr()
+        super().__init__(
+            operands=[input_op],
+            properties=properties,
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class IsNotNullOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpis_not_null-pdl_interpisnotnullop
+    """
+
+    name = "pdl_interp.is_not_null"
+    traits = traits_def(IsTerminator())
+    value = operand_def(AnyPDLTypeConstr)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "$value `:` type($value) attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(self, value: SSAValue, trueDest: Block, falseDest: Block) -> None:
+        super().__init__(operands=[value], successors=[trueDest, falseDest])
+
+
+@irdl_op_definition
+class GetResultOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_result-pdl_interpgetresultop
+    """
+
+    name = "pdl_interp.get_result"
+    index = prop_def(IntegerAttr[I32])
+    input_op = operand_def(OperationType)
+    value = result_def(ValueType)
+
+    assembly_format = "$index `of` $input_op attr-dict"
+
+    def __init__(self, index: int | IntegerAttr[I32], input_op: SSAValue) -> None:
+        if isinstance(index, int):
+            index = IntegerAttr.from_int_and_width(index, 32)
+        super().__init__(
+            operands=[input_op], properties={"index": index}, result_types=[ValueType()]
+        )
+
+
+@irdl_op_definition
+class GetResultsOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_results-pdl_interpgetresultsop
+    """
+
+    name = "pdl_interp.get_results"
+    index = opt_prop_def(IntegerAttr[I32])
+    input_op = operand_def(OperationType)
+    value = result_def(ValueType | RangeType[ValueType])
+
+    # assembly_format = "($index^)? `of` $input_op `:` type($value) attr-dict"
+    # TODO: if parse_optional_integer ...
+
+    def __init__(
+        self,
+        index: int | IntegerAttr[I32] | None,
+        input_op: SSAValue,
+        result_type: ValueType | RangeType[ValueType],
+    ) -> None:
+        if isinstance(index, int):
+            index = IntegerAttr.from_int_and_width(index, 32)
+        super().__init__(
+            operands=[input_op],
+            properties={"index": index},
+            result_types=[result_type],
+        )
+
+
+@irdl_op_definition
+class GetAttributeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_attribute-pdl_interpgetattributeop
+    """
+
+    name = "pdl_interp.get_attribute"
+    constraint_name = prop_def(StringAttr, prop_name="name")
+    input_op = operand_def(OperationType)
+    value = result_def(AttributeType)
+
+    assembly_format = "$name `of` $input_op attr-dict"
+
+    def __init__(self, name: str | StringAttr, input_op: SSAValue) -> None:
+        if isinstance(name, str):
+            name = StringAttr(name)
+        super().__init__(
+            operands=[input_op],
+            properties={"name": name},
+            result_types=[AttributeType()],
+        )
+
+
+@irdl_op_definition
+class CheckAttributeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcheck_attribute-pdl_interpcheckattributeop
+    """
+
+    name = "pdl_interp.check_attribute"
+    traits = traits_def(IsTerminator())
+    constantValue = prop_def(Attribute)
+    attribute = operand_def(AttributeType)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "$attribute `is` $constantValue attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(
+        self,
+        constantValue: Attribute,
+        attribute: SSAValue,
+        trueDest: Block,
+        falseDest: Block,
+    ) -> None:
+        super().__init__(
+            operands=[attribute],
+            properties={"constantValue": constantValue},
+            successors=[trueDest, falseDest],
+        )
+
+
+@irdl_op_definition
+class AreEqualOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpare_equal-pdl_interpareequalop
+    """
+
+    name = "pdl_interp.are_equal"
+    traits = traits_def(IsTerminator())
+    T: ClassVar = VarConstraint("T", AnyPDLTypeConstr)
+    lhs = operand_def(T)
+    rhs = operand_def(T)
+    true_dest = successor_def()
+    false_dest = successor_def()
+
+    assembly_format = (
+        "operands `:` type($lhs) attr-dict `->` $true_dest `, ` $false_dest"
+    )
+
+    def __init__(
+        self, lhs: SSAValue, rhs: SSAValue, trueDest: Block, falseDest: Block
+    ) -> None:
+        super().__init__(operands=[lhs, rhs], successors=[trueDest, falseDest])
+
+
+@irdl_op_definition
+class RecordMatchOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interprecord_match-pdl_interprecordmatchop
+    """
+
+    name = "pdl_interp.record_match"
+    traits = traits_def(IsTerminator())
+    rewriter = prop_def(SymbolRefAttr)
+    rootKind = opt_prop_def(StringAttr)
+    generatedOps = opt_prop_def(ArrayAttr)
+    benefit = prop_def(IntegerAttr[I16])
+
+    inputs = var_operand_def(AnyPDLTypeConstr)
+    matched_ops = var_operand_def(OperationType)
+
+    dest = successor_def()
+
+    irdl_options = [AttrSizedOperandSegments(as_property=True)]
+
+    assembly_format = (
+        "$rewriter (`(` $inputs^ `:` type($inputs) `)`)? `:` `benefit` `(` $benefit `)` `,`"
+        "(`generatedOps` `(` $generatedOps^ `)` `,`)? `loc` `(` `[` $matched_ops `]` `)`"
+        "(`,` `root` `(` $rootKind^ `)`)? attr-dict `->` $dest"
+    )
+
+    def __init__(
+        self,
+        rewriter: str | SymbolRefAttr,
+        root_kind: str | StringAttr,
+        generated_ops: list[OperationType] | None,
+        benefit: int | IntegerAttr[I16],
+        inputs: Sequence[SSAValue],
+        matched_ops: Sequence[SSAValue],
+        dest: Block,
+    ) -> None:
+        if isinstance(rewriter, str):
+            rewriter = SymbolRefAttr(rewriter)
+        if isinstance(root_kind, str):
+            root_kind = StringAttr(root_kind)
+        if (
+            generated_ops is None
+        ):  # TODO: if generatedOps is actually optional (check this), we shouldn't even pass an empty list
+            generated_ops = []
+        if isinstance(benefit, int):
+            benefit = IntegerAttr.from_int_and_width(benefit, 16)
+        super().__init__(
+            operands=[inputs, matched_ops],
+            properties={
+                "rewriter": rewriter,
+                "rootKind": root_kind,
+                "generatedOps": ArrayAttr(generated_ops),
+                "benefit": benefit,
+            },
+            successors=[dest],
+        )
+
+
+@dataclass(frozen=True)
+class ValueConstrFromResultConstr(
+    GenericAttrConstraint[ValueType | RangeType[ValueType]]
+):
+    result_constr: GenericAttrConstraint[TypeType | RangeType[TypeType]]
+
+    def can_infer(self, var_constraint_names: Set[str]) -> bool:
+        return self.result_constr.can_infer(var_constraint_names)
+
+    def infer(self, context: ConstraintContext) -> ValueType | RangeType[ValueType]:
+        result_type = self.result_constr.infer(context)
+        if isinstance(result_type, RangeType):
+            return RangeType(ValueType())
+        return ValueType()
+
+    def verify(self, attr: Attribute, constraint_context: ConstraintContext) -> None:
+        if isa(attr, RangeType[ValueType]):
+            result_type = RangeType(TypeType())
+        elif isa(attr, ValueType):
+            result_type = TypeType()
+        else:
+            raise VerifyException(
+                f"Expected an attribute of type ValueType or RangeType[ValueType], but got {attr}"
+            )
+        return self.result_constr.verify(result_type, constraint_context)
+
+
+@irdl_op_definition
+class GetValueTypeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_value_type-pdl_interpgetvaluetypeop
+    """
+
+    name = "pdl_interp.get_value_type"
+    T: ClassVar = VarConstraint("T", base(TypeType) | base(RangeType[TypeType]))
+    value = operand_def(ValueConstrFromResultConstr(T))
+    result = result_def(T)
+
+    assembly_format = "`of` $value `:` type($result) attr-dict"
+
+    def __init__(self, value: SSAValue) -> None:
+        super().__init__(
+            operands=[value],
+            result_types=[
+                RangeType(TypeType())
+                if isinstance(value.type, RangeType)
+                else TypeType()
+            ],
+        )
+
+
+@irdl_op_definition
+class ReplaceOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpreplace-pdl_interpreplaceop
+    """
+
+    name = "pdl_interp.replace"
+    input_op = operand_def(OperationType)
+    repl_values = var_operand_def(ValueType | RangeType[ValueType])
+
+    assembly_format = (
+        "$input_op `with` ` ` `(` ($repl_values^ `:` type($repl_values))? `)` attr-dict"
+    )
+
+    def __init__(self, input_op: SSAValue, repl_values: list[SSAValue]) -> None:
+        super().__init__(operands=[input_op, repl_values])
+
+
+@irdl_op_definition
+class CreateAttributeOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcreate_attribute-pdl_interpcreateattributeop
+    """
+
+    name = "pdl_interp.create_attribute"
+    value = prop_def(AnyAttr())
+    attribute = result_def(AttributeType)
+
+    assembly_format = "$value attr-dict-with-keyword"
+
+    def __init__(self, value: Attribute) -> None:
+        super().__init__(properties={"value": value}, result_types=[AttributeType()])
+
+
+@irdl_op_definition
+class CreateOperationOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpcreate_operation-pdl_interpcreateoperationop
+    """
+
+    name = "pdl_interp.create_operation"
+    constraint_name = prop_def(StringAttr, prop_name="name")
+    input_attribute_names = prop_def(ArrayAttr, prop_name="inputAttributeNames")
+    inferred_result_types = opt_prop_def(UnitAttr, prop_name="inferredResultTypes")
+
+    input_operands = var_operand_def(ValueType | RangeType[ValueType])
+    input_attributes = var_operand_def(AttributeType | RangeType[AttributeType])
+    input_result_types = var_operand_def(TypeType | RangeType[TypeType])
+
+    result_op = result_def(OperationType)
+
+    irdl_options = [AttrSizedOperandSegments(as_property=True)]
+
+    # assembly_format = (
+    #     "$name (`(` $input_operands^ `:` type($input_operands) `)`)?"
+    #     "` `custom<CreateOperationOpAttributes>($inputAttributes, $inputAttributeNames)"
+    #     "custom<CreateOperationOpResults>($inputResultTypes, type($inputResultTypes), $inferredResultTypes)"
+    #     "attr-dict"
+    # )
+    # # unexpected token 'custom'
path: xdsl/dialects/pdl_interp.py
user: login: superlopuh
body: Can you please add a bit more of a comment here? In general we don't want commented code, but if the broken assembly format is a useful thing to track that's OK, it would be good to add this in text here.
created_at: 2025-04-04T08:39:59Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,119 @@
+// RUN: mlir-opt %s --mlir-print-op-generic | xdsl-opt --print-op-generic | filecheck %s
path: tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir
user: login: jumerckx
body: That custom syntax test won't work on the same input as `pdl_interp.func` and others don't have the custom syntax yet so the output from mlir-opt can't be parsed.
Is it ok to add this in the follow up pr where custom parsing is added for those? (custom syntax printing I can still test here) 
created_at: 2025-04-04T08:50:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,119 @@
+// RUN: mlir-opt %s --mlir-print-op-generic | xdsl-opt --print-op-generic | filecheck %s
path: tests/filecheck/mlir-conversion/with-mlir/dialects/pdl_interp/pdl_interp_ops.mlir
user: login: superlopuh
body: Ah yes of course, maybe add it as a comment in this PR, and add it in the next one?
created_at: 2025-04-04T09:08:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,119 @@
+// RUN: XDSL_ROUNDTRIP
path: tests/filecheck/dialects/pdl_interp/pdl_interp_ops.mlir
user: login: superlopuh
body: Sorry missed this earlier
```suggestion
// RUN: XDSL_ROUNDTRIP
// RUN: XDSL_GENERIC_ROUNDTRIP
```
created_at: 2025-04-04T09:15:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,664 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence, Set
+from dataclasses import dataclass
+from typing import ClassVar
+
+from xdsl.dialects.builtin import (
+    I16,
+    I32,
+    ArrayAttr,
+    ContainerOf,
+    DictionaryAttr,
+    Float16Type,
+    Float32Type,
+    Float64Type,
+    FunctionType,
+    IndexType,
+    IntegerAttr,
+    IntegerType,
+    StringAttr,
+    SymbolRefAttr,
+    UnitAttr,
+)
+from xdsl.dialects.pdl import (
+    AnyPDLTypeConstr,
+    AttributeType,
+    OperationType,
+    RangeType,
+    TypeType,
+    ValueType,
+)
+from xdsl.ir import Attribute, Block, Dialect, Operation, Region, SSAValue
+from xdsl.irdl import (
+    AnyAttr,
+    AnyOf,
+    AttrSizedOperandSegments,
+    ConstraintContext,
+    GenericAttrConstraint,
+    IRDLOperation,
+    VarConstraint,
+    base,
+    irdl_op_definition,
+    operand_def,
+    opt_prop_def,
+    prop_def,
+    region_def,
+    result_def,
+    successor_def,
+    traits_def,
+    var_operand_def,
+)
+from xdsl.traits import (
+    CallableOpInterface,
+    IsolatedFromAbove,
+    IsTerminator,
+    SymbolOpInterface,
+)
+from xdsl.utils.exceptions import VerifyException
+from xdsl.utils.hints import isa
+
+boolLike = ContainerOf(IntegerType(1))
+signlessIntegerLike = ContainerOf(AnyOf([IntegerType, IndexType]))
+floatingPointLike = ContainerOf(AnyOf([Float16Type, Float32Type, Float64Type]))
+
+
+@irdl_op_definition
+class GetOperandOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop
path: xdsl/dialects/pdl_interp.py
user: login: compor
body: ```py
"""
See external [documentation](https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop).
"""
```
Similarly, below. A micro-PR fixing the documentation would be good.
created_at: 2025-04-04T09:51:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,664 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence, Set
+from dataclasses import dataclass
+from typing import ClassVar
+
+from xdsl.dialects.builtin import (
+    I16,
+    I32,
+    ArrayAttr,
+    ContainerOf,
+    DictionaryAttr,
+    Float16Type,
+    Float32Type,
+    Float64Type,
+    FunctionType,
+    IndexType,
+    IntegerAttr,
+    IntegerType,
+    StringAttr,
+    SymbolRefAttr,
+    UnitAttr,
+)
+from xdsl.dialects.pdl import (
+    AnyPDLTypeConstr,
+    AttributeType,
+    OperationType,
+    RangeType,
+    TypeType,
+    ValueType,
+)
+from xdsl.ir import Attribute, Block, Dialect, Operation, Region, SSAValue
+from xdsl.irdl import (
+    AnyAttr,
+    AnyOf,
+    AttrSizedOperandSegments,
+    ConstraintContext,
+    GenericAttrConstraint,
+    IRDLOperation,
+    VarConstraint,
+    base,
+    irdl_op_definition,
+    operand_def,
+    opt_prop_def,
+    prop_def,
+    region_def,
+    result_def,
+    successor_def,
+    traits_def,
+    var_operand_def,
+)
+from xdsl.traits import (
+    CallableOpInterface,
+    IsolatedFromAbove,
+    IsTerminator,
+    SymbolOpInterface,
+)
+from xdsl.utils.exceptions import VerifyException
+from xdsl.utils.hints import isa
+
+boolLike = ContainerOf(IntegerType(1))
+signlessIntegerLike = ContainerOf(AnyOf([IntegerType, IndexType]))
+floatingPointLike = ContainerOf(AnyOf([Float16Type, Float32Type, Float64Type]))
+
+
+@irdl_op_definition
+class GetOperandOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop
path: xdsl/dialects/pdl_interp.py
user: login: superlopuh
body: Yeah I missed this, hopefully easy enough to do with a find/replace script.
created_at: 2025-04-04T09:54:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,664 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence, Set
+from dataclasses import dataclass
+from typing import ClassVar
+
+from xdsl.dialects.builtin import (
+    I16,
+    I32,
+    ArrayAttr,
+    ContainerOf,
+    DictionaryAttr,
+    Float16Type,
+    Float32Type,
+    Float64Type,
+    FunctionType,
+    IndexType,
+    IntegerAttr,
+    IntegerType,
+    StringAttr,
+    SymbolRefAttr,
+    UnitAttr,
+)
+from xdsl.dialects.pdl import (
+    AnyPDLTypeConstr,
+    AttributeType,
+    OperationType,
+    RangeType,
+    TypeType,
+    ValueType,
+)
+from xdsl.ir import Attribute, Block, Dialect, Operation, Region, SSAValue
+from xdsl.irdl import (
+    AnyAttr,
+    AnyOf,
+    AttrSizedOperandSegments,
+    ConstraintContext,
+    GenericAttrConstraint,
+    IRDLOperation,
+    VarConstraint,
+    base,
+    irdl_op_definition,
+    operand_def,
+    opt_prop_def,
+    prop_def,
+    region_def,
+    result_def,
+    successor_def,
+    traits_def,
+    var_operand_def,
+)
+from xdsl.traits import (
+    CallableOpInterface,
+    IsolatedFromAbove,
+    IsTerminator,
+    SymbolOpInterface,
+)
+from xdsl.utils.exceptions import VerifyException
+from xdsl.utils.hints import isa
+
+boolLike = ContainerOf(IntegerType(1))
+signlessIntegerLike = ContainerOf(AnyOf([IntegerType, IndexType]))
+floatingPointLike = ContainerOf(AnyOf([Float16Type, Float32Type, Float64Type]))
+
+
+@irdl_op_definition
+class GetOperandOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop
path: xdsl/dialects/pdl_interp.py
user: login: jumerckx
body: You mean to rephrase this as "See external [documentation](...)", right? Will do.
created_at: 2025-04-04T10:00:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,664 @@
+from __future__ import annotations
+
+from collections.abc import Iterable, Sequence, Set
+from dataclasses import dataclass
+from typing import ClassVar
+
+from xdsl.dialects.builtin import (
+    I16,
+    I32,
+    ArrayAttr,
+    ContainerOf,
+    DictionaryAttr,
+    Float16Type,
+    Float32Type,
+    Float64Type,
+    FunctionType,
+    IndexType,
+    IntegerAttr,
+    IntegerType,
+    StringAttr,
+    SymbolRefAttr,
+    UnitAttr,
+)
+from xdsl.dialects.pdl import (
+    AnyPDLTypeConstr,
+    AttributeType,
+    OperationType,
+    RangeType,
+    TypeType,
+    ValueType,
+)
+from xdsl.ir import Attribute, Block, Dialect, Operation, Region, SSAValue
+from xdsl.irdl import (
+    AnyAttr,
+    AnyOf,
+    AttrSizedOperandSegments,
+    ConstraintContext,
+    GenericAttrConstraint,
+    IRDLOperation,
+    VarConstraint,
+    base,
+    irdl_op_definition,
+    operand_def,
+    opt_prop_def,
+    prop_def,
+    region_def,
+    result_def,
+    successor_def,
+    traits_def,
+    var_operand_def,
+)
+from xdsl.traits import (
+    CallableOpInterface,
+    IsolatedFromAbove,
+    IsTerminator,
+    SymbolOpInterface,
+)
+from xdsl.utils.exceptions import VerifyException
+from xdsl.utils.hints import isa
+
+boolLike = ContainerOf(IntegerType(1))
+signlessIntegerLike = ContainerOf(AnyOf([IntegerType, IndexType]))
+floatingPointLike = ContainerOf(AnyOf([Float16Type, Float32Type, Float64Type]))
+
+
+@irdl_op_definition
+class GetOperandOp(IRDLOperation):
+    """
+    https://mlir.llvm.org/docs/Dialects/PDLInterpOps/#pdl_interpget_operand-pdl_interpgetoperandop
path: xdsl/dialects/pdl_interp.py
user: login: compor
body: Yeah, this is in order to have this as a "live" URL in our readthedocs documentation :)

no sweat
created_at: 2025-04-04T10:02:57Z
author_association: COLLABORATOR

PR comments:
user: login: codecov[bot]
created_at: 2025-04-04T08:24:23Z
body: ## [Codecov](https://app.codecov.io/gh/xdslproject/xdsl/pull/4176?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 88.90%. Comparing base [(`b8b367b`)](https://app.codecov.io/gh/xdslproject/xdsl/commit/b8b367b082777c03ab2bb2c64b84491adacbb96a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject) to head [(`290d826`)](https://app.codecov.io/gh/xdslproject/xdsl/commit/290d8267dd59b37494d7ad38556137dcf981da9b?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject).
> Report is 463 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #4176      +/-   ##
==========================================
- Coverage   89.00%   88.90%   -0.11%     
==========================================
  Files         323      324       +1     
  Lines       43970    44247     +277     
  Branches     5456     5481      +25     
==========================================
+ Hits        39137    39337     +200     
- Misses       3495     3569      +74     
- Partials     1338     1341       +3     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/xdslproject/xdsl/pull/4176?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=xdslproject).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: jumerckx
created_at: 2025-04-04T09:13:53Z
body: Thanks Sasha! Your comments have been addressed.
author_association: COLLABORATOR
user: login: superlopuh
created_at: 2025-04-04T09:42:49Z
body: LGTM, feel free to hit the merge button :)
author_association: MEMBER
user: login: compor
created_at: 2025-04-04T09:53:02Z
body: LGTM; I was a wee late, but please address the doc fix in another PR.
author_association: COLLABORATOR