[Mlir-commits] [mlir] 5388fea - [mlir][emitc] Support member access for values (#203308)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Mon Jun 15 06:20:18 PDT 2026


Author: Gil Rapaport
Date: 2026-06-15T16:20:12+03:00
New Revision: 5388feaf865f2d48592588417bdf9d7f93de0755

URL: https://github.com/llvm/llvm-project/commit/5388feaf865f2d48592588417bdf9d7f93de0755
DIFF: https://github.com/llvm/llvm-project/commit/5388feaf865f2d48592588417bdf9d7f93de0755.diff

LOG: [mlir][emitc] Support member access for values (#203308)

The `emitc.member` op is currently limited to taking lvalues of opaque
types representing structs and returning either lvalues or arrays of its
fields. Accessing members of SSA values of opaque types, therefore,
requires assigning them to an `emitc.variable`, applying `emitc.member`
to it and `emitc.load` to the member's lvalue. For users only wishing to
read members of a struct value, this should be redundant.

This PR extends `emitc.member` to handle struct values directly by
accepting opaque types as argument, as long as the result type is
neither an lvalue nor an array, which imply memory location. This
provides similar semantics to extracting elements out of SSA
tensors/vectors.

Added: 
    

Modified: 
    mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
    mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
    mlir/lib/Dialect/EmitC/IR/EmitC.cpp
    mlir/lib/Target/Cpp/TranslateToCpp.cpp
    mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
    mlir/test/Dialect/EmitC/invalid_ops.mlir
    mlir/test/Dialect/EmitC/ops.mlir
    mlir/test/Target/Cpp/func.mlir
    mlir/test/Target/Cpp/member.mlir

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index c3e8221f7e574..4a9609aac0cfb 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1142,6 +1142,8 @@ def EmitC_MemberOp : EmitC_Op<"member", [CExpressionInterface]> {
     Example:
 
     ```mlir
+    %0 = "emitc.member" (%arg0) {member = "a"}
+        : (!emitc.opaque<"mystruct">) -> i32
     %0 = "emitc.member" (%arg0) {member = "a"}
         : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
     %1 = "emitc.member" (%arg0) {member = "b"}
@@ -1151,16 +1153,18 @@ def EmitC_MemberOp : EmitC_Op<"member", [CExpressionInterface]> {
 
   let arguments = (ins
     Arg<StrAttr, "the member to access">:$member,
-    EmitC_LValueOf<[EmitC_OpaqueType]>:$operand
+    AnyTypeOf<[EmitC_OpaqueType, EmitC_LValueOf<[EmitC_OpaqueType]>]>:$operand
   );
-  let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>);
+  let results = (outs AnyTypeOf<[EmitCType, EmitC_LValueType]>);
+  let hasVerifier = 1;
 
   let extraClassDeclaration = [{
     bool hasSideEffects() {
       return false;
     }
     bool alwaysInline() {
-      return true; // C doesn't support references.
+      // C doesn't support references.
+      return isa<emitc::LValueType, emitc::ArrayType>(getResult().getType());
     }
   }];
 }

diff  --git a/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp b/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
index 14352ea3595e3..a2279242fb86b 100644
--- a/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
+++ b/mlir/lib/Conversion/FuncToEmitC/FuncToEmitC.cpp
@@ -210,13 +210,6 @@ class CallOpConversion final : public OpConversionPattern<func::CallOp> {
             .getResult(0);
 
     // Unpack struct fields to replace the original multiple results.
-    MLIRContext *ctx = rewriter.getContext();
-    auto noInit = emitc::OpaqueAttr::get(ctx, "");
-    Value structLv =
-        emitc::VariableOp::create(rewriter, loc,
-                                  emitc::LValueType::get(*structType), noInit)
-            .getResult();
-    emitc::AssignOp::create(rewriter, loc, structLv, structVal);
     SmallVector<Value> results;
     for (auto [i, result] : llvm::enumerate(callOp.getResults())) {
       if (result.use_empty()) {
@@ -226,12 +219,10 @@ class CallOpConversion final : public OpConversionPattern<func::CallOp> {
       Type fieldType = convertedResultTypes[i];
       StringAttr fieldName =
           rewriter.getStringAttr("field" + std::to_string(i));
-      Value fieldLv = emitc::MemberOp::create(rewriter, loc,
-                                              emitc::LValueType::get(fieldType),
-                                              fieldName, structLv)
-                          .getResult();
-      results.push_back(
-          emitc::LoadOp::create(rewriter, loc, fieldType, fieldLv).getResult());
+      Value fieldValue = emitc::MemberOp::create(rewriter, loc, fieldType,
+                                                 fieldName, structVal)
+                             .getResult();
+      results.push_back(fieldValue);
     }
 
     rewriter.replaceOp(callOp, results);

diff  --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 6cd012ce99539..08878647c7591 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -1058,6 +1058,33 @@ LogicalResult emitc::LiteralOp::verify() {
     return emitOpError() << "value must not be empty";
   return success();
 }
+
+//===----------------------------------------------------------------------===//
+// MemberOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult MemberOp::verify() {
+  Type operandType = getOperand().getType();
+  Type resultType = getResult().getType();
+  bool resultIsWritable = isa<emitc::LValueType, emitc::ArrayType>(resultType);
+
+  // Make sure the operand and return type agree on value/memory semantics:
+  // If the operand is an lvalue it models a memory location and as such its
+  // elements are also memory locations: They require a load operation to use
+  // their value and they can be assigned new values.
+  // If the operand isn't an lvalue it models an aggregate SSA value and as
+  // such its elements are also SSA values: Their value can be used directly
+  // but they cannot be assigned to.
+
+  if (isa<emitc::LValueType>(operandType) && !resultIsWritable)
+    return emitOpError("lvalues must return lvalues or arrays");
+
+  if (!isa<emitc::LValueType>(operandType) && resultIsWritable)
+    return emitOpError("non-lvalues cannot return lvalues or arrays");
+
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // SubOp
 //===----------------------------------------------------------------------===//

diff  --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index c686a5654c3f2..7b2ad67df11fe 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -476,9 +476,13 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::MemberOp memberOp) {
-  if (!emitter.isPartOfCurrentExpression(memberOp.getOperation()))
-    return success();
-
+  if (memberOp.alwaysInline()) {
+    if (!emitter.isPartOfCurrentExpression(memberOp.getOperation()))
+      return success();
+  } else {
+    if (failed(emitter.emitAssignPrefix(*memberOp.getOperation())))
+      return failure();
+  }
   if (failed(emitter.emitOperand(memberOp.getOperand())))
     return failure();
   emitter.ostream() << "." << memberOp.getMember();

diff  --git a/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir b/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
index 1a2a8e764e22d..9ac863c4a8e9f 100644
--- a/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
+++ b/mlir/test/Conversion/FuncToEmitC/func-to-emitc.mlir
@@ -121,11 +121,8 @@ func.func @return_two(%arg0: i32, %arg1: i32) -> (i32, i32) {
 // CHECK-LABEL:   emitc.func @caller(
 // CHECK-SAME:      %[[ARG0:.*]]: i32) -> i32 {
 // CHECK-NEXT:      %[[VAL_0:.*]] = call @return_two(%[[ARG0]], %[[ARG0]]) : (i32, i32) -> !emitc.opaque<"struct return_i32_i32">
-// CHECK-NEXT:      %[[VAL_1:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
-// CHECK-NEXT:      assign %[[VAL_0]] : !emitc.opaque<"struct return_i32_i32"> to %[[VAL_1]] : <!emitc.opaque<"struct return_i32_i32">>
-// CHECK-NEXT:      %[[VAL_2:.*]] = "emitc.member"(%[[VAL_1]]) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
-// CHECK-NEXT:      %[[VAL_3:.*]] = load %[[VAL_2]] : <i32>
-// CHECK-NEXT:      return %[[VAL_3]] : i32
+// CHECK-NEXT:      %[[VAL_1:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "field1"}> : (!emitc.opaque<"struct return_i32_i32">) -> i32
+// CHECK-NEXT:      return %[[VAL_1]] : i32
 // CHECK-NEXT:    }
 func.func @return_two(%arg0: i32, %arg1: i32) -> (i32, i32) {
   return %arg0, %arg1 : i32, i32

diff  --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index df17f077513de..f4af20587a56a 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -578,13 +578,29 @@ func.func @use_global() {
 // -----
 
 func.func @member(%arg0: !emitc.lvalue<i32>) {
-  // expected-error @+1 {{'emitc.member' op operand #0 must be emitc.lvalue of EmitC opaque type values, but got '!emitc.lvalue<i32>'}}
+  // expected-error @+1 {{'emitc.member' op operand #0 must be EmitC opaque type or emitc.lvalue of EmitC opaque type values, but got '!emitc.lvalue<i32>'}}
   %0 = "emitc.member" (%arg0) {member = "a"} : (!emitc.lvalue<i32>) -> !emitc.lvalue<i32>
   return
 }
 
 // -----
 
+func.func @member_of_value_as_lvalue(%arg0: !emitc.opaque<"mystruct">) {
+  // expected-error @+1 {{'emitc.member' op non-lvalues cannot return lvalues or arrays}}
+  %1 = "emitc.member" (%arg0) {member = "a"} : (!emitc.opaque<"mystruct">) -> !emitc.lvalue<i32>
+  return
+}
+
+// -----
+
+func.func @member_of_value_array(%arg0: !emitc.opaque<"mystruct">) {
+  // expected-error @+1 {{'emitc.member' op non-lvalues cannot return lvalues or arrays}}
+  %1 = "emitc.member" (%arg0) {member = "a"} : (!emitc.opaque<"mystruct">) -> !emitc.array<2xi32>
+  return
+}
+
+// -----
+
 func.func @member_of_ptr(%arg0: !emitc.lvalue<i32>) {
   // expected-error @+1 {{'emitc.member_of_ptr' op operand #0 must be emitc.lvalue of EmitC opaque type or EmitC pointer type values, but got '!emitc.lvalue<i32>'}}
   %0 = "emitc.member_of_ptr" (%arg0) {member = "a"} : (!emitc.lvalue<i32>) -> !emitc.lvalue<i32>

diff  --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index 4e558020c01f9..e456db4634892 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -315,6 +315,8 @@ func.func @member_access(%arg0: !emitc.lvalue<!emitc.opaque<"mystruct">>, %arg1:
   %3 = "emitc.member_of_ptr" (%arg1) {member = "b"} : (!emitc.lvalue<!emitc.opaque<"mystruct_ptr">>) -> !emitc.array<2xi32>
   %4 = "emitc.member_of_ptr" (%arg2) {member = "a"} : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
   %5 = "emitc.member_of_ptr" (%arg2) {member = "b"} : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+  %6 = emitc.load %arg0 : !emitc.lvalue<!emitc.opaque<"mystruct">>
+  %7 = "emitc.member" (%6) {member = "a"} : (!emitc.opaque<"mystruct">) -> i32
   return
 }
 

diff  --git a/mlir/test/Target/Cpp/func.mlir b/mlir/test/Target/Cpp/func.mlir
index 82f1ee9f6ec2b..e7aa2f5cbbdb0 100644
--- a/mlir/test/Target/Cpp/func.mlir
+++ b/mlir/test/Target/Cpp/func.mlir
@@ -61,11 +61,8 @@ emitc.func @return_two(%arg0: i32, %arg1: i32) -> !emitc.opaque<"struct return_i
 
 emitc.func @call_two(%arg0: i32) -> i32 {
   %0 = call @return_two(%arg0, %arg0) : (i32, i32) -> !emitc.opaque<"struct return_i32_i32">
-  %1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>
-  assign %0 : !emitc.opaque<"struct return_i32_i32"> to %1 : <!emitc.opaque<"struct return_i32_i32">>
-  %2 = "emitc.member"(%1) <{member = "field1"}> : (!emitc.lvalue<!emitc.opaque<"struct return_i32_i32">>) -> !emitc.lvalue<i32>
-  %3 = load %2 : <i32>
-  return %3 : i32
+  %1 = "emitc.member"(%0) <{member = "field1"}> : (!emitc.opaque<"struct return_i32_i32">) -> i32
+  return %1 : i32
 }
 
 // CPP-DEFAULT: struct return_i32_i32 {
@@ -81,10 +78,8 @@ emitc.func @call_two(%arg0: i32) -> i32 {
 // CPP-DEFAULT-NEXT: }
 // CPP-DEFAULT-NEXT: int32_t call_two(int32_t [[V1:[^ ]*]]) {
 // CPP-DEFAULT-NEXT:   struct return_i32_i32 [[V2:[^ ]*]] = return_two([[V1]], [[V1]]);
-// CPP-DEFAULT-NEXT:   struct return_i32_i32 [[V3:[^ ]*]];
-// CPP-DEFAULT-NEXT:   [[V3]] = [[V2]];
-// CPP-DEFAULT-NEXT:   int32_t [[V4:[^ ]*]] = [[V3]].field1;
-// CPP-DEFAULT-NEXT:   return [[V4]];
+// CPP-DEFAULT-NEXT:   int32_t [[V3:[^ ]*]] = [[V2]].field1;
+// CPP-DEFAULT-NEXT:   return [[V3]];
 
 // CPP-DECLTOP: struct return_i32_i32 {
 // CPP-DECLTOP-NEXT:   int32_t field0;
@@ -100,9 +95,7 @@ emitc.func @call_two(%arg0: i32) -> i32 {
 // CPP-DECLTOP-NEXT: }
 // CPP-DECLTOP-NEXT: int32_t call_two(int32_t [[V1:[^ ]*]]) {
 // CPP-DECLTOP-NEXT:   struct return_i32_i32 [[V2:[^ ]*]];
-// CPP-DECLTOP-NEXT:   struct return_i32_i32 [[V3:[^ ]*]];
-// CPP-DECLTOP-NEXT:   int32_t [[V4:[^ ]*]];
+// CPP-DECLTOP-NEXT:   int32_t [[V3:[^ ]*]];
 // CPP-DECLTOP-NEXT:   [[V2]] = return_two([[V1]], [[V1]]);
-// CPP-DECLTOP:        [[V3]] = [[V2]];
-// CPP-DECLTOP-NEXT:   [[V4]] = [[V3]].field1;
-// CPP-DECLTOP-NEXT:   return [[V4]];
+// CPP-DECLTOP-NEXT:   [[V3]] = [[V2]].field1;
+// CPP-DECLTOP-NEXT:   return [[V3]];

diff  --git a/mlir/test/Target/Cpp/member.mlir b/mlir/test/Target/Cpp/member.mlir
index 45b6336f63ce0..0fd25e440f752 100644
--- a/mlir/test/Target/Cpp/member.mlir
+++ b/mlir/test/Target/Cpp/member.mlir
@@ -21,6 +21,8 @@ func.func @member(%arg0: !emitc.opaque<"mystruct">, %arg1: i32, %arg2: index) {
   %8 = emitc.subscript %7[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
   emitc.assign %arg1 : i32 to %8 : !emitc.lvalue<i32>
 
+  %9 = "emitc.member" (%arg0) {member = "a"} : (!emitc.opaque<"mystruct">) -> i32
+
   return
 }
 
@@ -34,6 +36,7 @@ func.func @member(%arg0: !emitc.opaque<"mystruct">, %arg1: i32, %arg2: index) {
 // CPP-DEFAULT-NEXT: int32_t [[V5:[^ ]*]] = ([[V2]].c)[[[Index]]];
 // CPP-DEFAULT-NEXT: [[V4]] = [[V5]];
 // CPP-DEFAULT-NEXT: ([[V2]].d)[[[Index]]] = [[V1]];
+// CPP-DEFAULT-NEXT: int32_t [[V6:[^ ]*]] = [[V0]].a;
 
 
 func.func @member_of_pointer(%arg0: !emitc.ptr<!emitc.opaque<"mystruct">>, %arg1: i32, %arg2: index) {


        


More information about the Mlir-commits mailing list