[Mlir-commits] [mlir] [mlir][emitc]: support method calls in CallOpaqueOp (PR #200057)

Jeremy Kun llvmlistbot at llvm.org
Thu May 28 13:48:52 PDT 2026


https://github.com/j2kun updated https://github.com/llvm/llvm-project/pull/200057

>From 54c78bae731103d2719514ae893ab5e72596ec04 Mon Sep 17 00:00:00 2001
From: Jeremy Kun <jkun at google.com>
Date: Wed, 27 May 2026 14:10:04 -0700
Subject: [PATCH 1/4] [mlir][emitc]: support method calls in CallOpaqueOp

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 22 +++++++++++++-
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp         |  3 ++
 mlir/lib/Target/Cpp/TranslateToCpp.cpp      | 33 ++++++++++++++++++---
 mlir/test/Dialect/EmitC/invalid_ops.mlir    |  8 +++++
 mlir/test/Target/Cpp/call.mlir              | 24 +++++++++++++++
 5 files changed, 85 insertions(+), 5 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index deb138225c643..49b1076e0b470 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -324,6 +324,19 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
     - integer value of index type refers to an operand;
     - attribute which will get lowered to constant value in call;
 
+    If `is_member_call` is true, the operation represents a member call. The
+    first operand is treated as the receiver and emitted before the member
+    access operator (`.` or `->`).
+
+    The `args` attribute can be used to override the default operand order and
+    to mix operands with attributes. If `args` is present, it specifies the
+    arguments passed to the call in parentheses.
+
+    If `args` is not present:
+    - If `is_member_call` is true, the first operand is the receiver, and the
+      remaining operands are passed as arguments.
+    - If `is_member_call` is false, all operands are passed as arguments.
+
     Example:
 
     ```mlir
@@ -332,12 +345,19 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
 
     // Generic form of the same operation.
     %0 = "emitc.call_opaque"() {callee = "foo"} : () -> i32
+
+    // Member call
+    %1 = emitc.call_opaque "method" (%arg0, %arg1) {is_member_call = true} : (!emitc.opaque<"MyClass">, i32) -> i32
+
+    // Member call with args attribute
+    %2 = emitc.call_opaque "method" (%arg0, %arg1) {is_member_call = true, args = [1 : index]} : (!emitc.opaque<"MyClass">, i32) -> i32
     ```
   }];
   let arguments = (ins
     Arg<StrAttr, "the C++ function to call">:$callee,
     Arg<OptionalAttr<ArrayAttr>, "the order of operands and further attributes">:$args,
     Arg<OptionalAttr<ArrayAttr>, "template arguments">:$template_args,
+    DefaultValuedAttr<BoolAttr, "false">:$is_member_call,
     Variadic<EmitCType>:$operands
   );
   let results = (outs Variadic<EmitCType>);
@@ -349,7 +369,7 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
       CArg<"::mlir::ArrayAttr", "{}">:$args,
       CArg<"::mlir::ArrayAttr", "{}">:$template_args), [{
         build($_builder, $_state, resultTypes, callee, args, template_args,
-            operands);
+            false, operands);
       }]
     >
   ];
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 36394e67008da..cd27a97941e10 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -345,6 +345,9 @@ LogicalResult emitc::CallOpaqueOp::verify() {
   if (getCallee().empty())
     return emitOpError("callee must not be empty");
 
+  if (getIsMemberCall() && getNumOperands() == 0)
+    return emitOpError("member call requires at least one operand (receiver)");
+
   if (std::optional<ArrayAttr> argsAttr = getArgs()) {
     for (Attribute arg : *argsAttr) {
       auto intAttr = llvm::dyn_cast<IntegerAttr>(arg);
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index dd61a81ce1cdc..b05e049050021 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -916,6 +916,23 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
   if (failed(emitter.emitAssignPrefix(op)))
     return failure();
+
+  bool isMemberCall = callOpaqueOp.getIsMemberCall();
+
+  if (isMemberCall) {
+    Value receiver = op.getOperand(0);
+    bool isPtr = llvm::isa<emitc::PointerType>(receiver.getType());
+
+    if (failed(emitter.emitOperand(receiver)))
+      return failure();
+
+    if (isPtr) {
+      os << "->";
+    } else {
+      os << ".";
+    }
+  }
+
   os << callOpaqueOp.getCallee();
 
   // Template arguments can't refer to SSA values and as such the template
@@ -951,10 +968,18 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
   os << "(";
 
-  LogicalResult emittedArgs =
-      callOpaqueOp.getArgs()
-          ? interleaveCommaWithError(*callOpaqueOp.getArgs(), os, emitArgs)
-          : emitter.emitOperands(op);
+  LogicalResult emittedArgs = success();
+  if (callOpaqueOp.getArgs()) {
+    emittedArgs =
+        interleaveCommaWithError(*callOpaqueOp.getArgs(), os, emitArgs);
+  } else if (isMemberCall) {
+    emittedArgs = interleaveCommaWithError(
+        llvm::seq<size_t>(1, op.getNumOperands()), os,
+        [&](size_t i) { return emitter.emitOperand(op.getOperand(i)); });
+  } else {
+    emittedArgs = emitter.emitOperands(op);
+  }
+
   if (failed(emittedArgs))
     return failure();
   os << ")";
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 0d878e90cdf0c..79f2c0b210be9 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -48,6 +48,14 @@ func.func @index_args_out_of_range_2(%arg : i32) {
 
 // -----
 
+func.func @member_call_no_operands() {
+    // expected-error @+1 {{'emitc.call_opaque' op member call requires at least one operand (receiver)}}
+    emitc.call_opaque "method" () {is_member_call = true} : () -> ()
+    return
+}
+
+// -----
+
 func.func @empty_callee() {
     // expected-error @+1 {{'emitc.call_opaque' op callee must not be empty}}
     emitc.call_opaque "" () : () -> ()
diff --git a/mlir/test/Target/Cpp/call.mlir b/mlir/test/Target/Cpp/call.mlir
index e3ac392f30b62..c447b149f84c3 100644
--- a/mlir/test/Target/Cpp/call.mlir
+++ b/mlir/test/Target/Cpp/call.mlir
@@ -34,3 +34,27 @@ func.func @emitc_call_opaque_two_results() {
 // CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
 // CPP-DECLTOP-NEXT: [[V1]] = 0;
 // CPP-DECLTOP-NEXT: std::tie([[V2]], [[V3]]) = two_results();
+
+func.func @emitc_call_opaque_member(%arg0 : !emitc.opaque<"MyClass">, %arg1 : !emitc.ptr<!emitc.opaque<"MyClass">>) {
+  %0 = emitc.call_opaque "method" (%arg0) {is_member_call = true} : (!emitc.opaque<"MyClass">) -> i32
+  %1 = emitc.call_opaque "ptr_method" (%arg1) {is_member_call = true} : (!emitc.ptr<!emitc.opaque<"MyClass">>) -> i32
+  return
+}
+// CPP-DEFAULT: void emitc_call_opaque_member(MyClass [[V0:[^ ]*]], MyClass* [[V1:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = [[V0]].method();
+// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V1]]->ptr_method();
+
+func.func @emitc_call_opaque_member_args(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32, %arg2 : i32) {
+  %0 = emitc.call_opaque "method" (%arg0, %arg1, %arg2) {is_member_call = true} : (!emitc.opaque<"MyClass">, i32, i32) -> i32
+  %1 = emitc.call_opaque "method_with_args" (%arg0, %arg1, %arg2) {is_member_call = true, args = [1 : index, 2 : index]} : (!emitc.opaque<"MyClass">, i32, i32) -> i32
+  return
+}
+// CPP-DEFAULT: void emitc_call_opaque_member_args(MyClass [[V0:[^ ]*]], int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V0]].method([[V1]], [[V2]]);
+// CPP-DEFAULT-NEXT: int32_t [[V4:[^ ]*]] = [[V0]].method_with_args([[V1]], [[V2]]);
+
+// CPP-DECLTOP: void emitc_call_opaque_member_args(MyClass [[V0:[^ ]*]], int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
+// CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
+// CPP-DECLTOP-NEXT: int32_t [[V4:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[V3]] = [[V0]].method([[V1]], [[V2]]);
+// CPP-DECLTOP-NEXT: [[V4]] = [[V0]].method_with_args([[V1]], [[V2]]);

>From 5eaee6402c1625a121ab59897e535764e17eb8d3 Mon Sep 17 00:00:00 2001
From: Jeremy Kun <jkun at google.com>
Date: Thu, 28 May 2026 10:50:52 -0700
Subject: [PATCH 2/4] Revert "[mlir][emitc]: support method calls in
 CallOpaqueOp"

This reverts commit 54c78bae731103d2719514ae893ab5e72596ec04.
---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td | 22 +-------------
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp         |  3 --
 mlir/lib/Target/Cpp/TranslateToCpp.cpp      | 33 +++------------------
 mlir/test/Dialect/EmitC/invalid_ops.mlir    |  8 -----
 mlir/test/Target/Cpp/call.mlir              | 24 ---------------
 5 files changed, 5 insertions(+), 85 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 49b1076e0b470..deb138225c643 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -324,19 +324,6 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
     - integer value of index type refers to an operand;
     - attribute which will get lowered to constant value in call;
 
-    If `is_member_call` is true, the operation represents a member call. The
-    first operand is treated as the receiver and emitted before the member
-    access operator (`.` or `->`).
-
-    The `args` attribute can be used to override the default operand order and
-    to mix operands with attributes. If `args` is present, it specifies the
-    arguments passed to the call in parentheses.
-
-    If `args` is not present:
-    - If `is_member_call` is true, the first operand is the receiver, and the
-      remaining operands are passed as arguments.
-    - If `is_member_call` is false, all operands are passed as arguments.
-
     Example:
 
     ```mlir
@@ -345,19 +332,12 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
 
     // Generic form of the same operation.
     %0 = "emitc.call_opaque"() {callee = "foo"} : () -> i32
-
-    // Member call
-    %1 = emitc.call_opaque "method" (%arg0, %arg1) {is_member_call = true} : (!emitc.opaque<"MyClass">, i32) -> i32
-
-    // Member call with args attribute
-    %2 = emitc.call_opaque "method" (%arg0, %arg1) {is_member_call = true, args = [1 : index]} : (!emitc.opaque<"MyClass">, i32) -> i32
     ```
   }];
   let arguments = (ins
     Arg<StrAttr, "the C++ function to call">:$callee,
     Arg<OptionalAttr<ArrayAttr>, "the order of operands and further attributes">:$args,
     Arg<OptionalAttr<ArrayAttr>, "template arguments">:$template_args,
-    DefaultValuedAttr<BoolAttr, "false">:$is_member_call,
     Variadic<EmitCType>:$operands
   );
   let results = (outs Variadic<EmitCType>);
@@ -369,7 +349,7 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
       CArg<"::mlir::ArrayAttr", "{}">:$args,
       CArg<"::mlir::ArrayAttr", "{}">:$template_args), [{
         build($_builder, $_state, resultTypes, callee, args, template_args,
-            false, operands);
+            operands);
       }]
     >
   ];
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index cd27a97941e10..36394e67008da 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -345,9 +345,6 @@ LogicalResult emitc::CallOpaqueOp::verify() {
   if (getCallee().empty())
     return emitOpError("callee must not be empty");
 
-  if (getIsMemberCall() && getNumOperands() == 0)
-    return emitOpError("member call requires at least one operand (receiver)");
-
   if (std::optional<ArrayAttr> argsAttr = getArgs()) {
     for (Attribute arg : *argsAttr) {
       auto intAttr = llvm::dyn_cast<IntegerAttr>(arg);
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index b05e049050021..dd61a81ce1cdc 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -916,23 +916,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
   if (failed(emitter.emitAssignPrefix(op)))
     return failure();
-
-  bool isMemberCall = callOpaqueOp.getIsMemberCall();
-
-  if (isMemberCall) {
-    Value receiver = op.getOperand(0);
-    bool isPtr = llvm::isa<emitc::PointerType>(receiver.getType());
-
-    if (failed(emitter.emitOperand(receiver)))
-      return failure();
-
-    if (isPtr) {
-      os << "->";
-    } else {
-      os << ".";
-    }
-  }
-
   os << callOpaqueOp.getCallee();
 
   // Template arguments can't refer to SSA values and as such the template
@@ -968,18 +951,10 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
   os << "(";
 
-  LogicalResult emittedArgs = success();
-  if (callOpaqueOp.getArgs()) {
-    emittedArgs =
-        interleaveCommaWithError(*callOpaqueOp.getArgs(), os, emitArgs);
-  } else if (isMemberCall) {
-    emittedArgs = interleaveCommaWithError(
-        llvm::seq<size_t>(1, op.getNumOperands()), os,
-        [&](size_t i) { return emitter.emitOperand(op.getOperand(i)); });
-  } else {
-    emittedArgs = emitter.emitOperands(op);
-  }
-
+  LogicalResult emittedArgs =
+      callOpaqueOp.getArgs()
+          ? interleaveCommaWithError(*callOpaqueOp.getArgs(), os, emitArgs)
+          : emitter.emitOperands(op);
   if (failed(emittedArgs))
     return failure();
   os << ")";
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 79f2c0b210be9..0d878e90cdf0c 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -48,14 +48,6 @@ func.func @index_args_out_of_range_2(%arg : i32) {
 
 // -----
 
-func.func @member_call_no_operands() {
-    // expected-error @+1 {{'emitc.call_opaque' op member call requires at least one operand (receiver)}}
-    emitc.call_opaque "method" () {is_member_call = true} : () -> ()
-    return
-}
-
-// -----
-
 func.func @empty_callee() {
     // expected-error @+1 {{'emitc.call_opaque' op callee must not be empty}}
     emitc.call_opaque "" () : () -> ()
diff --git a/mlir/test/Target/Cpp/call.mlir b/mlir/test/Target/Cpp/call.mlir
index c447b149f84c3..e3ac392f30b62 100644
--- a/mlir/test/Target/Cpp/call.mlir
+++ b/mlir/test/Target/Cpp/call.mlir
@@ -34,27 +34,3 @@ func.func @emitc_call_opaque_two_results() {
 // CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
 // CPP-DECLTOP-NEXT: [[V1]] = 0;
 // CPP-DECLTOP-NEXT: std::tie([[V2]], [[V3]]) = two_results();
-
-func.func @emitc_call_opaque_member(%arg0 : !emitc.opaque<"MyClass">, %arg1 : !emitc.ptr<!emitc.opaque<"MyClass">>) {
-  %0 = emitc.call_opaque "method" (%arg0) {is_member_call = true} : (!emitc.opaque<"MyClass">) -> i32
-  %1 = emitc.call_opaque "ptr_method" (%arg1) {is_member_call = true} : (!emitc.ptr<!emitc.opaque<"MyClass">>) -> i32
-  return
-}
-// CPP-DEFAULT: void emitc_call_opaque_member(MyClass [[V0:[^ ]*]], MyClass* [[V1:[^ ]*]]) {
-// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = [[V0]].method();
-// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V1]]->ptr_method();
-
-func.func @emitc_call_opaque_member_args(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32, %arg2 : i32) {
-  %0 = emitc.call_opaque "method" (%arg0, %arg1, %arg2) {is_member_call = true} : (!emitc.opaque<"MyClass">, i32, i32) -> i32
-  %1 = emitc.call_opaque "method_with_args" (%arg0, %arg1, %arg2) {is_member_call = true, args = [1 : index, 2 : index]} : (!emitc.opaque<"MyClass">, i32, i32) -> i32
-  return
-}
-// CPP-DEFAULT: void emitc_call_opaque_member_args(MyClass [[V0:[^ ]*]], int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
-// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V0]].method([[V1]], [[V2]]);
-// CPP-DEFAULT-NEXT: int32_t [[V4:[^ ]*]] = [[V0]].method_with_args([[V1]], [[V2]]);
-
-// CPP-DECLTOP: void emitc_call_opaque_member_args(MyClass [[V0:[^ ]*]], int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
-// CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
-// CPP-DECLTOP-NEXT: int32_t [[V4:[^ ]*]];
-// CPP-DECLTOP-NEXT: [[V3]] = [[V0]].method([[V1]], [[V2]]);
-// CPP-DECLTOP-NEXT: [[V4]] = [[V0]].method_with_args([[V1]], [[V2]]);

>From 93305fa5cdfb5152083f69777d52f98594de3b5c Mon Sep 17 00:00:00 2001
From: Jeremy Kun <jkun at google.com>
Date: Thu, 28 May 2026 12:26:50 -0700
Subject: [PATCH 3/4] [mlir][emitc]: add MemberCallOpaque op

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   | 33 +++++++++
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           | 36 +++++++++
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        | 74 ++++++++++++++++++-
 mlir/test/Dialect/EmitC/invalid_ops.mlir      | 40 ++++++++++
 .../Dialect/EmitC/member_call_opaque.mlir     | 29 ++++++++
 mlir/test/Target/Cpp/call.mlir                | 30 ++++++++
 6 files changed, 238 insertions(+), 4 deletions(-)
 create mode 100644 mlir/test/Dialect/EmitC/member_call_opaque.mlir

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index deb138225c643..a724e69b5a552 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -360,6 +360,39 @@ def EmitC_CallOpaqueOp : EmitC_Op<"call_opaque", [CExpressionInterface]> {
   let hasVerifier = 1;
 }
 
+def EmitC_MemberCallOpaqueOp : EmitC_Op<"member_call_opaque", [CExpressionInterface]> {
+  let summary = "Opaque member call operation";
+  let description = [{
+    The `emitc.member_call_opaque` operation represents a C++ member function
+    call. It takes a receiver operand, a callee string attribute (the method
+    name), and variadic operands for arguments.
+
+    The call allows specifying order of operands and attributes in the call as
+    follows:
+    - integer value of index type refers to an argument operand;
+    - attribute which will get lowered to constant value in call;
+
+    Example:
+
+    ```mlir
+    %0 = emitc.member_call_opaque %receiver "method" (%arg0, %arg1) : !emitc.opaque<"MyClass">, (i32, i32) -> i32
+    ```
+  }];
+  let arguments = (ins
+    EmitCType:$receiver,
+    StrAttr:$callee,
+    OptionalAttr<ArrayAttr>:$args,
+    OptionalAttr<ArrayAttr>:$template_args,
+    Variadic<EmitCType>:$args_operands
+  );
+  let results = (outs Variadic<EmitCType>);
+
+  let assemblyFormat = [{
+    $receiver $callee `(` $args_operands `)` attr-dict `:` type($receiver) `,` functional-type($args_operands, results)
+  }];
+  let hasVerifier = 1;
+}
+
 def EmitC_CastOp : EmitC_Op<"cast",
     [CExpressionInterface,
      DeclareOpInterfaceMethods<CastOpInterface>]> {
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 36394e67008da..80f856f5a1234 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -378,6 +378,42 @@ LogicalResult emitc::CallOpaqueOp::verify() {
   return success();
 }
 
+LogicalResult emitc::MemberCallOpaqueOp::verify() {
+  // Callee must not be empty.
+  if (getCallee().empty())
+    return emitOpError("callee must not be empty");
+
+  if (std::optional<ArrayAttr> argsAttr = getArgs()) {
+    for (Attribute arg : *argsAttr) {
+      auto intAttr = llvm::dyn_cast<IntegerAttr>(arg);
+      if (intAttr && llvm::isa<IndexType>(intAttr.getType())) {
+        int64_t index = intAttr.getInt();
+        // Args with elements of type index must be in range
+        // [0..args_operands.size).
+        if ((index < 0) ||
+            (index >= static_cast<int64_t>(getArgsOperands().size())))
+          return emitOpError("index argument is out of range");
+
+      } else if (llvm::isa<ArrayAttr>(arg)) {
+        return emitOpError("array argument has no type");
+      }
+    }
+  }
+
+  if (std::optional<ArrayAttr> templateArgsAttr = getTemplateArgs()) {
+    for (Attribute tArg : *templateArgsAttr) {
+      if (!llvm::isa<TypeAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(tArg))
+        return emitOpError("template argument has invalid type");
+    }
+  }
+
+  if (llvm::any_of(getResultTypes(), llvm::IsaPred<ArrayType>)) {
+    return emitOpError() << "cannot return array type";
+  }
+
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // ConstantOp
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index dd61a81ce1cdc..b1710f3d1dc36 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -961,6 +961,71 @@ static LogicalResult printOperation(CppEmitter &emitter,
   return success();
 }
 
+static LogicalResult
+printOperation(CppEmitter &emitter,
+               emitc::MemberCallOpaqueOp memberCallOpaqueOp) {
+  raw_ostream &os = emitter.ostream();
+  Operation &op = *memberCallOpaqueOp.getOperation();
+
+  if (failed(emitter.emitAssignPrefix(op)))
+    return failure();
+
+  Value receiver = memberCallOpaqueOp.getReceiver();
+  if (failed(emitter.emitOperand(receiver)))
+    return failure();
+
+  if (llvm::isa<emitc::PointerType>(receiver.getType()))
+    os << "->";
+  else
+    os << ".";
+
+  os << memberCallOpaqueOp.getCallee();
+
+  auto emitTemplateArgs = [&](Attribute attr) -> LogicalResult {
+    return emitter.emitAttribute(op.getLoc(), attr);
+  };
+
+  if (memberCallOpaqueOp.getTemplateArgs()) {
+    os << "<";
+    if (failed(interleaveCommaWithError(*memberCallOpaqueOp.getTemplateArgs(),
+                                        os, emitTemplateArgs)))
+      return failure();
+    os << ">";
+  }
+
+  auto emitArgs = [&](Attribute attr) -> LogicalResult {
+    if (auto t = dyn_cast<IntegerAttr>(attr)) {
+      if (t.getType().isIndex()) {
+        int64_t idx = t.getInt();
+        Value operand = op.getOperand(idx + 1);
+        return emitter.emitOperand(operand, /*isInBrackets=*/true);
+      }
+    }
+    if (failed(emitter.emitAttribute(op.getLoc(), attr)))
+      return failure();
+
+    return success();
+  };
+
+  os << "(";
+
+  LogicalResult emittedArgs = success();
+  if (memberCallOpaqueOp.getArgs()) {
+    emittedArgs =
+        interleaveCommaWithError(*memberCallOpaqueOp.getArgs(), os, emitArgs);
+  } else {
+    auto operands = op.getOperands().drop_front(1);
+    emittedArgs = interleaveCommaWithError(operands, os, [&](Value operand) {
+      return emitter.emitOperand(operand, /*isInBrackets=*/true);
+    });
+  }
+
+  if (failed(emittedArgs))
+    return failure();
+  os << ")";
+  return success();
+}
+
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::ApplyOp applyOp) {
   raw_ostream &os = emitter.ostream();
@@ -1870,10 +1935,11 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::GetGlobalOp, emitc::GlobalOp, emitc::IfOp,
                 emitc::IncludeOp, emitc::LiteralOp, emitc::LoadOp,
                 emitc::LogicalAndOp, emitc::LogicalNotOp, emitc::LogicalOrOp,
-                emitc::MemberOfPtrOp, emitc::MemberOp, emitc::MulOp,
-                emitc::RemOp, emitc::ReturnOp, emitc::SubscriptOp, emitc::SubOp,
-                emitc::SwitchOp, emitc::UnaryMinusOp, emitc::UnaryPlusOp,
-                emitc::VariableOp, emitc::VerbatimOp>(
+                emitc::MemberCallOpaqueOp, emitc::MemberOfPtrOp,
+                emitc::MemberOp, emitc::MulOp, emitc::RemOp, emitc::ReturnOp,
+                emitc::SubscriptOp, emitc::SubOp, emitc::SwitchOp,
+                emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp,
+                emitc::VerbatimOp>(
 
               [&](auto op) { return printOperation(*this, op); })
           // Func ops.
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 0d878e90cdf0c..1c15af89bdbcd 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -88,6 +88,46 @@ func.func @array_result() {
 
 // -----
 
+func.func @member_call_empty_callee(%arg0 : !emitc.opaque<"MyClass">) {
+    // expected-error @+1 {{'emitc.member_call_opaque' op callee must not be empty}}
+    emitc.member_call_opaque %arg0 "" () : !emitc.opaque<"MyClass">, () -> ()
+    return
+}
+
+// -----
+
+func.func @member_call_index_out_of_range(%arg0 : !emitc.opaque<"MyClass">) {
+    // expected-error @+1 {{'emitc.member_call_opaque' op index argument is out of range}}
+    emitc.member_call_opaque %arg0 "test" () {args = [1 : index]} : !emitc.opaque<"MyClass">, () -> ()
+    return
+}
+
+// -----
+
+func.func @member_call_array_result(%arg0 : !emitc.opaque<"MyClass">) {
+    // expected-error @+1 {{'emitc.member_call_opaque' op cannot return array type}}
+    emitc.member_call_opaque %arg0 "array_result"() : !emitc.opaque<"MyClass">, () -> !emitc.array<4xi32>
+    return
+}
+
+// -----
+
+func.func @member_call_nonetype_template_arg(%arg0 : !emitc.opaque<"MyClass">) {
+    // expected-error @+1 {{'emitc.member_call_opaque' op template argument has invalid type}}
+    emitc.member_call_opaque %arg0 "nonetype_template_arg"() {template_args = [[0, 1, 2]]} : !emitc.opaque<"MyClass">, () -> ()
+    return
+}
+
+// -----
+
+func.func @member_call_dense_template_argument(%arg0 : !emitc.opaque<"MyClass">) {
+    // expected-error @+1 {{'emitc.member_call_opaque' op template argument has invalid type}}
+    emitc.member_call_opaque %arg0 "dense_template_argument"() {template_args = [dense<[1.0, 1.0]> : tensor<2xf32>]} : !emitc.opaque<"MyClass">, () -> ()
+    return
+}
+
+// -----
+
 func.func @empty_operator() {
     %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
     // expected-error @+1 {{'emitc.apply' op applicable operator must not be empty}}
diff --git a/mlir/test/Dialect/EmitC/member_call_opaque.mlir b/mlir/test/Dialect/EmitC/member_call_opaque.mlir
new file mode 100644
index 0000000000000..e7a0a0be5eef6
--- /dev/null
+++ b/mlir/test/Dialect/EmitC/member_call_opaque.mlir
@@ -0,0 +1,29 @@
+// RUN: mlir-opt %s | mlir-opt | FileCheck %s
+
+func.func @member_call(%arg0 : !emitc.opaque<"MyClass">) {
+  %0 = emitc.member_call_opaque %arg0 "method" () : !emitc.opaque<"MyClass">, () -> i32
+  return
+}
+// CHECK-LABEL: func @member_call
+// CHECK: emitc.member_call_opaque %arg0 "method"() : !emitc.opaque<"MyClass">, () -> i32
+
+func.func @member_call_args(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32) {
+  %0 = emitc.member_call_opaque %arg0 "method" (%arg1) : !emitc.opaque<"MyClass">, (i32) -> i32
+  return
+}
+// CHECK-LABEL: func @member_call_args
+// CHECK: emitc.member_call_opaque %arg0 "method"(%arg1) : !emitc.opaque<"MyClass">, (i32) -> i32
+
+func.func @member_call_template_args(%arg0 : !emitc.opaque<"MyClass">) {
+  %0 = emitc.member_call_opaque %arg0 "method" () {template_args = [i32]} : !emitc.opaque<"MyClass">, () -> i32
+  return
+}
+// CHECK-LABEL: func @member_call_template_args
+// CHECK: emitc.member_call_opaque %arg0 "method"() {template_args = [i32]} : !emitc.opaque<"MyClass">, () -> i32
+
+func.func @member_call_reorder(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32, %arg2 : i32) {
+  %0 = emitc.member_call_opaque %arg0 "method" (%arg1, %arg2) {args = [0 : index, 2 : index, 1 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32
+  return
+}
+// CHECK-LABEL: func @member_call_reorder
+// CHECK: emitc.member_call_opaque %arg0 "method"(%arg1, %arg2) {args = [0 : index, 2 : index, 1 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32
diff --git a/mlir/test/Target/Cpp/call.mlir b/mlir/test/Target/Cpp/call.mlir
index e3ac392f30b62..de279e667cac0 100644
--- a/mlir/test/Target/Cpp/call.mlir
+++ b/mlir/test/Target/Cpp/call.mlir
@@ -34,3 +34,33 @@ func.func @emitc_call_opaque_two_results() {
 // CPP-DECLTOP-NEXT: int32_t [[V3:[^ ]*]];
 // CPP-DECLTOP-NEXT: [[V1]] = 0;
 // CPP-DECLTOP-NEXT: std::tie([[V2]], [[V3]]) = two_results();
+
+func.func @emitc_member_call(%arg0 : !emitc.opaque<"MyClass">, %arg1 : !emitc.ptr<!emitc.opaque<"MyClass">> ) {
+  %0 = emitc.member_call_opaque %arg0 "method" () : !emitc.opaque<"MyClass">, () -> i32
+  %1 = emitc.member_call_opaque %arg1 "ptr_method" () : !emitc.ptr<!emitc.opaque<"MyClass">>, () -> i32
+  return
+}
+// CPP-DEFAULT: void emitc_member_call(MyClass [[V0:[^ ]*]], MyClass* [[V1:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = [[V0]].method();
+// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V1]]->ptr_method();
+
+func.func @emitc_member_call_args(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32) {
+  %0 = emitc.member_call_opaque %arg0 "method" (%arg1) : !emitc.opaque<"MyClass">, (i32) -> i32
+  return
+}
+// CPP-DEFAULT: void emitc_member_call_args(MyClass [[V0:[^ ]*]], int32_t [[V1:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: int32_t [[V2:[^ ]*]] = [[V0]].method([[V1]]);
+
+func.func @emitc_member_call_args_reorder(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32, %arg2 : i32) {
+  %0 = emitc.member_call_opaque %arg0 "method" (%arg1, %arg2) {args = [1 : index, 0 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32
+  return
+}
+// CPP-DEFAULT: void emitc_member_call_args_reorder(MyClass [[V0:[^ ]*]], int32_t [[V1:[^ ]*]], int32_t [[V2:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V0]].method([[V2]], [[V1]]);
+
+func.func @emitc_member_call_template_args(%arg0 : !emitc.opaque<"MyClass">) {
+  %0 = emitc.member_call_opaque %arg0 "method" () {template_args = [i32]} : !emitc.opaque<"MyClass">, () -> i32
+  return
+}
+// CPP-DEFAULT: void emitc_member_call_template_args(MyClass [[V0:[^ ]*]]) {
+// CPP-DEFAULT-NEXT: int32_t [[V1:[^ ]*]] = [[V0]].method<int32_t>();

>From 517493ecf07b5087ffbc5857984a7fc9a0f975f8 Mon Sep 17 00:00:00 2001
From: Jeremy Kun <jkun at google.com>
Date: Thu, 28 May 2026 13:45:00 -0700
Subject: [PATCH 4/4] refactor common logic among verifier and emitter

---
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |  81 +++++-------
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        | 120 +++++++-----------
 .../Dialect/EmitC/member_call_opaque.mlir     |   4 +-
 3 files changed, 78 insertions(+), 127 deletions(-)

diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 80f856f5a1234..d57bf5e2faf55 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -340,78 +340,55 @@ bool CastOp::areCastCompatible(TypeRange inputs, TypeRange outputs) {
 // CallOpaqueOp
 //===----------------------------------------------------------------------===//
 
-LogicalResult emitc::CallOpaqueOp::verify() {
+static LogicalResult
+verifyOpaqueCallCommon(Operation *op, StringRef callee,
+                       std::optional<ArrayAttr> args,
+                       std::optional<ArrayAttr> templateArgs,
+                       TypeRange resultTypes, size_t numArgsOperands) {
   // Callee must not be empty.
-  if (getCallee().empty())
-    return emitOpError("callee must not be empty");
+  if (callee.empty())
+    return op->emitOpError("callee must not be empty");
 
-  if (std::optional<ArrayAttr> argsAttr = getArgs()) {
-    for (Attribute arg : *argsAttr) {
+  if (args) {
+    for (Attribute arg : *args) {
       auto intAttr = llvm::dyn_cast<IntegerAttr>(arg);
       if (intAttr && llvm::isa<IndexType>(intAttr.getType())) {
         int64_t index = intAttr.getInt();
         // Args with elements of type index must be in range
-        // [0..operands.size).
-        if ((index < 0) || (index >= static_cast<int64_t>(getNumOperands())))
-          return emitOpError("index argument is out of range");
-
-        // Args with elements of type ArrayAttr must have a type.
-      } else if (llvm::isa<ArrayAttr>(
-                     arg) /*&& llvm::isa<NoneType>(arg.getType())*/) {
-        // FIXME: Array attributes never have types
-        return emitOpError("array argument has no type");
+        // [0..numArgsOperands).
+        if ((index < 0) || (index >= static_cast<int64_t>(numArgsOperands)))
+          return op->emitOpError("index argument is out of range");
+
+      } else if (llvm::isa<ArrayAttr>(arg)) {
+        return op->emitOpError("array argument has no type");
       }
     }
   }
 
-  if (std::optional<ArrayAttr> templateArgsAttr = getTemplateArgs()) {
-    for (Attribute tArg : *templateArgsAttr) {
+  if (templateArgs) {
+    for (Attribute tArg : *templateArgs) {
       if (!llvm::isa<TypeAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(tArg))
-        return emitOpError("template argument has invalid type");
+        return op->emitOpError("template argument has invalid type");
     }
   }
 
-  if (llvm::any_of(getResultTypes(), llvm::IsaPred<ArrayType>)) {
-    return emitOpError() << "cannot return array type";
+  if (llvm::any_of(resultTypes, llvm::IsaPred<ArrayType>)) {
+    return op->emitOpError() << "cannot return array type";
   }
 
   return success();
 }
 
-LogicalResult emitc::MemberCallOpaqueOp::verify() {
-  // Callee must not be empty.
-  if (getCallee().empty())
-    return emitOpError("callee must not be empty");
-
-  if (std::optional<ArrayAttr> argsAttr = getArgs()) {
-    for (Attribute arg : *argsAttr) {
-      auto intAttr = llvm::dyn_cast<IntegerAttr>(arg);
-      if (intAttr && llvm::isa<IndexType>(intAttr.getType())) {
-        int64_t index = intAttr.getInt();
-        // Args with elements of type index must be in range
-        // [0..args_operands.size).
-        if ((index < 0) ||
-            (index >= static_cast<int64_t>(getArgsOperands().size())))
-          return emitOpError("index argument is out of range");
-
-      } else if (llvm::isa<ArrayAttr>(arg)) {
-        return emitOpError("array argument has no type");
-      }
-    }
-  }
-
-  if (std::optional<ArrayAttr> templateArgsAttr = getTemplateArgs()) {
-    for (Attribute tArg : *templateArgsAttr) {
-      if (!llvm::isa<TypeAttr, IntegerAttr, FloatAttr, emitc::OpaqueAttr>(tArg))
-        return emitOpError("template argument has invalid type");
-    }
-  }
-
-  if (llvm::any_of(getResultTypes(), llvm::IsaPred<ArrayType>)) {
-    return emitOpError() << "cannot return array type";
-  }
+LogicalResult emitc::CallOpaqueOp::verify() {
+  return verifyOpaqueCallCommon(getOperation(), getCallee(), getArgs(),
+                                getTemplateArgs(), getResultTypes(),
+                                getNumOperands());
+}
 
-  return success();
+LogicalResult emitc::MemberCallOpaqueOp::verify() {
+  return verifyOpaqueCallCommon(getOperation(), getCallee(), getArgs(),
+                                getTemplateArgs(), getResultTypes(),
+                                getArgsOperands().size());
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index b1710f3d1dc36..764781d75545e 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -909,86 +909,40 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::CallOp callOp) {
   return printCallOperation(emitter, operation, callee);
 }
 
-static LogicalResult printOperation(CppEmitter &emitter,
-                                    emitc::CallOpaqueOp callOpaqueOp) {
-  raw_ostream &os = emitter.ostream();
-  Operation &op = *callOpaqueOp.getOperation();
-
-  if (failed(emitter.emitAssignPrefix(op)))
-    return failure();
-  os << callOpaqueOp.getCallee();
-
-  // Template arguments can't refer to SSA values and as such the template
-  // arguments which are supplied in form of attributes can be emitted as is. We
-  // don't need to handle integer attributes specially like we do for arguments
-  // - see below.
-  auto emitTemplateArgs = [&](Attribute attr) -> LogicalResult {
-    return emitter.emitAttribute(op.getLoc(), attr);
-  };
-
-  if (callOpaqueOp.getTemplateArgs()) {
-    os << "<";
-    if (failed(interleaveCommaWithError(*callOpaqueOp.getTemplateArgs(), os,
-                                        emitTemplateArgs)))
-      return failure();
-    os << ">";
-  }
-
-  auto emitArgs = [&](Attribute attr) -> LogicalResult {
-    if (auto t = dyn_cast<IntegerAttr>(attr)) {
-      // Index attributes are treated specially as operand index.
-      if (t.getType().isIndex()) {
-        int64_t idx = t.getInt();
-        Value operand = op.getOperand(idx);
-        return emitter.emitOperand(operand);
-      }
-    }
-    if (failed(emitter.emitAttribute(op.getLoc(), attr)))
-      return failure();
-
-    return success();
-  };
-
-  os << "(";
-
-  LogicalResult emittedArgs =
-      callOpaqueOp.getArgs()
-          ? interleaveCommaWithError(*callOpaqueOp.getArgs(), os, emitArgs)
-          : emitter.emitOperands(op);
-  if (failed(emittedArgs))
-    return failure();
-  os << ")";
-  return success();
-}
-
 static LogicalResult
-printOperation(CppEmitter &emitter,
-               emitc::MemberCallOpaqueOp memberCallOpaqueOp) {
+printOpaqueCallCommon(CppEmitter &emitter, Operation &op, StringRef callee,
+                      std::optional<ArrayAttr> templateArgs,
+                      std::optional<ArrayAttr> args, bool isMemberCall,
+                      Value receiver = nullptr) {
   raw_ostream &os = emitter.ostream();
-  Operation &op = *memberCallOpaqueOp.getOperation();
 
   if (failed(emitter.emitAssignPrefix(op)))
     return failure();
 
-  Value receiver = memberCallOpaqueOp.getReceiver();
-  if (failed(emitter.emitOperand(receiver)))
-    return failure();
+  if (isMemberCall) {
+    assert(receiver && "Expected receiver for member call");
+    if (failed(emitter.emitOperand(receiver)))
+      return failure();
 
-  if (llvm::isa<emitc::PointerType>(receiver.getType()))
-    os << "->";
-  else
-    os << ".";
+    if (llvm::isa<emitc::PointerType>(receiver.getType()))
+      os << "->";
+    else
+      os << ".";
+  }
 
-  os << memberCallOpaqueOp.getCallee();
+  os << callee;
 
+  // Template arguments can't refer to SSA values and as such the template
+  // arguments which are supplied in form of attributes can be emitted as is. We
+  // don't need to handle integer attributes specially like we do for arguments
+  // - see below.
   auto emitTemplateArgs = [&](Attribute attr) -> LogicalResult {
     return emitter.emitAttribute(op.getLoc(), attr);
   };
 
-  if (memberCallOpaqueOp.getTemplateArgs()) {
+  if (templateArgs) {
     os << "<";
-    if (failed(interleaveCommaWithError(*memberCallOpaqueOp.getTemplateArgs(),
-                                        os, emitTemplateArgs)))
+    if (failed(interleaveCommaWithError(*templateArgs, os, emitTemplateArgs)))
       return failure();
     os << ">";
   }
@@ -997,8 +951,9 @@ printOperation(CppEmitter &emitter,
     if (auto t = dyn_cast<IntegerAttr>(attr)) {
       if (t.getType().isIndex()) {
         int64_t idx = t.getInt();
-        Value operand = op.getOperand(idx + 1);
-        return emitter.emitOperand(operand, /*isInBrackets=*/true);
+        // Shift index by 1 for member calls to skip the receiver operand.
+        Value operand = op.getOperand(isMemberCall ? idx + 1 : idx);
+        return emitter.emitOperand(operand, /*isInBrackets=*/false);
       }
     }
     if (failed(emitter.emitAttribute(op.getLoc(), attr)))
@@ -1010,22 +965,41 @@ printOperation(CppEmitter &emitter,
   os << "(";
 
   LogicalResult emittedArgs = success();
-  if (memberCallOpaqueOp.getArgs()) {
-    emittedArgs =
-        interleaveCommaWithError(*memberCallOpaqueOp.getArgs(), os, emitArgs);
+  if (args) {
+    emittedArgs = interleaveCommaWithError(*args, os, emitArgs);
   } else {
-    auto operands = op.getOperands().drop_front(1);
+    auto operands = op.getOperands();
+    if (isMemberCall)
+      operands = operands.drop_front(1);
+
     emittedArgs = interleaveCommaWithError(operands, os, [&](Value operand) {
       return emitter.emitOperand(operand, /*isInBrackets=*/true);
     });
   }
-
   if (failed(emittedArgs))
     return failure();
   os << ")";
   return success();
 }
 
+static LogicalResult printOperation(CppEmitter &emitter,
+                                    emitc::CallOpaqueOp callOpaqueOp) {
+  return printOpaqueCallCommon(
+      emitter, *callOpaqueOp.getOperation(), callOpaqueOp.getCallee(),
+      callOpaqueOp.getTemplateArgs(), callOpaqueOp.getArgs(),
+      /*isMemberCall=*/false);
+}
+
+static LogicalResult
+printOperation(CppEmitter &emitter,
+               emitc::MemberCallOpaqueOp memberCallOpaqueOp) {
+  return printOpaqueCallCommon(
+      emitter, *memberCallOpaqueOp.getOperation(),
+      memberCallOpaqueOp.getCallee(), memberCallOpaqueOp.getTemplateArgs(),
+      memberCallOpaqueOp.getArgs(),
+      /*isMemberCall=*/true, memberCallOpaqueOp.getReceiver());
+}
+
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::ApplyOp applyOp) {
   raw_ostream &os = emitter.ostream();
diff --git a/mlir/test/Dialect/EmitC/member_call_opaque.mlir b/mlir/test/Dialect/EmitC/member_call_opaque.mlir
index e7a0a0be5eef6..2413da6f740d2 100644
--- a/mlir/test/Dialect/EmitC/member_call_opaque.mlir
+++ b/mlir/test/Dialect/EmitC/member_call_opaque.mlir
@@ -22,8 +22,8 @@ func.func @member_call_template_args(%arg0 : !emitc.opaque<"MyClass">) {
 // CHECK: emitc.member_call_opaque %arg0 "method"() {template_args = [i32]} : !emitc.opaque<"MyClass">, () -> i32
 
 func.func @member_call_reorder(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32, %arg2 : i32) {
-  %0 = emitc.member_call_opaque %arg0 "method" (%arg1, %arg2) {args = [0 : index, 2 : index, 1 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32
+  %0 = emitc.member_call_opaque %arg0 "method" (%arg1, %arg2) {args = [1 : index, 0 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32
   return
 }
 // CHECK-LABEL: func @member_call_reorder
-// CHECK: emitc.member_call_opaque %arg0 "method"(%arg1, %arg2) {args = [0 : index, 2 : index, 1 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32
+// CHECK: emitc.member_call_opaque %arg0 "method"(%arg1, %arg2) {args = [1 : index, 0 : index]} : !emitc.opaque<"MyClass">, (i32, i32) -> i32



More information about the Mlir-commits mailing list