[Mlir-commits] [mlir] [mlir][emitc] Deferred emission as expressions (PR #159975)

Gil Rapaport llvmlistbot at llvm.org
Sun Apr 26 03:22:34 PDT 2026


https://github.com/aniragil updated https://github.com/llvm/llvm-project/pull/159975

>From 2ae00bec76794f4afaa1728ff8fce38795df6ef2 Mon Sep 17 00:00:00 2001
From: Gil Rapaport <gil.rapaport at mobileye.com>
Date: Tue, 22 Jul 2025 10:52:37 +0300
Subject: [PATCH 1/2] [mlir][emitc] Deferred emission as expressions

The translator currently implements deferred emission for certain ops.
Like expressions, these ops are emitted as part of their users but
unlike expressions, this is mandatory. Besides complicating the code
with a second inlining mechanism, deferred emission's inlining is
limited as it's not recursive.

This patch extends EmitC's expressions to support the always-inline
semantics required by the deferred emission ops by adding an
`always-inline` property to the CExpression interface, which forces
unconditional inlining whether such ops appear within an
`emitc.expression` op or not. With deferred emission ops converted
to always-inline CExpressions, current behavior is retained.
---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   |  88 ++++++++-
 .../mlir/Dialect/EmitC/IR/EmitCInterfaces.td  |  19 ++
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |  15 +-
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        | 153 +++++++++------
 mlir/test/Dialect/EmitC/form-expressions.mlir | 163 +++++++++++++++-
 mlir/test/Target/Cpp/expressions.mlir         | 180 ++++++++++++++++++
 mlir/test/Target/Cpp/member.mlir              |   8 +-
 7 files changed, 542 insertions(+), 84 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 7d9bb8907eb8b..deb138225c643 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -471,6 +471,7 @@ def EmitC_ConstantOp
 }
 
 def EmitC_DereferenceOp : EmitC_Op<"dereference", [
+    CExpressionInterface,
     TypesMatchWith<"input and result reference the same type", "pointer", "result",
                    "emitc::LValueType::get(::llvm::cast<emitc::PointerType>($_self).getPointee())">
 ]> {
@@ -493,6 +494,15 @@ def EmitC_DereferenceOp : EmitC_Op<"dereference", [
     $pointer `:` qualified(type($pointer)) attr-dict
   }];
   let hasVerifier = 1;
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // C doesn't support references.
+    }
+  }];
 }
 
 def EmitC_DivOp : EmitC_BinaryOp<"div", []> {
@@ -561,7 +571,7 @@ def EmitC_ExpressionOp
 
   let arguments = (ins Variadic<AnyTypeOf<[EmitCType, EmitC_LValueType]>>:$defs,
       UnitAttr:$do_not_inline);
-  let results = (outs EmitCType:$result);
+  let results = (outs AnyTypeOf<[EmitCType, EmitC_LValueType]>:$result);
   let regions = (region SizedRegion<1>:$region);
 
   let hasVerifier = 1;
@@ -928,7 +938,7 @@ def EmitC_IncludeOp
   let hasCustomAssemblyFormat = 1;
 }
 
-def EmitC_LiteralOp : EmitC_Op<"literal", [Pure]> {
+def EmitC_LiteralOp : EmitC_Op<"literal", [Pure, CExpressionInterface]> {
   let summary = "Literal operation";
   let description = [{
     The `emitc.literal` operation produces an SSA value equal to some constant
@@ -951,6 +961,15 @@ def EmitC_LiteralOp : EmitC_Op<"literal", [Pure]> {
 
   let hasVerifier = 1;
   let assemblyFormat = "$value attr-dict `:` type($result)";
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // Always inlined by design.
+    }
+  }];
 }
 
 def EmitC_LogicalAndOp : EmitC_BinaryOp<"logical_and", []> {
@@ -1117,7 +1136,7 @@ def EmitC_SubOp : EmitC_BinaryOp<"sub", []> {
   let hasVerifier = 1;
 }
 
-def EmitC_MemberOp : EmitC_Op<"member"> {
+def EmitC_MemberOp : EmitC_Op<"member", [CExpressionInterface]> {
   let summary = "Member operation";
   let description = [{
     With the `emitc.member` operation the member access operator `.` can be
@@ -1138,9 +1157,18 @@ def EmitC_MemberOp : EmitC_Op<"member"> {
     EmitC_LValueOf<[EmitC_OpaqueType]>:$operand
   );
   let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>);
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // C doesn't support references.
+    }
+  }];
 }
 
-def EmitC_MemberOfPtrOp : EmitC_Op<"member_of_ptr"> {
+def EmitC_MemberOfPtrOp : EmitC_Op<"member_of_ptr", [CExpressionInterface]> {
   let summary = "Member of pointer operation";
   let description = [{
     With the `emitc.member_of_ptr` operation the member access operator `->`
@@ -1163,6 +1191,15 @@ def EmitC_MemberOfPtrOp : EmitC_Op<"member_of_ptr"> {
     EmitC_LValueOf<[EmitC_OpaqueType,EmitC_PointerType]>:$operand
   );
   let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>);
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // C doesn't support references.
+    }
+  }];
 }
 
 def EmitC_ConditionalOp : EmitC_Op<"conditional",
@@ -1332,8 +1369,10 @@ def EmitC_GlobalOp : EmitC_Op<"global", [Symbol]> {
   let hasVerifier = 1;
 }
 
-def EmitC_GetGlobalOp : EmitC_Op<"get_global",
-    [Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
+def EmitC_GetGlobalOp
+    : EmitC_Op<"get_global", [Pure,
+                              DeclareOpInterfaceMethods<SymbolUserOpInterface>,
+                              CExpressionInterface]> {
   let summary = "Obtain access to a global variable";
   let description = [{
      The `emitc.get_global` operation retrieves the lvalue of a
@@ -1351,6 +1390,15 @@ def EmitC_GetGlobalOp : EmitC_Op<"get_global",
   let arguments = (ins FlatSymbolRefAttr:$name);
   let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>:$result);
   let assemblyFormat = "$name `:` type($result) attr-dict";
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // C doesn't support references.
+    }
+  }];
 }
 
 def EmitC_VerbatimOp : EmitC_Op<"verbatim"> {
@@ -1461,7 +1509,8 @@ def EmitC_YieldOp : EmitC_Op<"yield",
     value is yielded.
   }];
 
-  let arguments = (ins Optional<EmitCType>:$result);
+  let arguments =
+      (ins Optional<AnyTypeOf<[EmitCType, EmitC_LValueType]>>:$result);
   let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];
 
   let hasVerifier = 1;
@@ -1533,7 +1582,7 @@ def EmitC_IfOp : EmitC_Op<"if",
   let hasCustomAssemblyFormat = 1;
 }
 
-def EmitC_SubscriptOp : EmitC_Op<"subscript", []> {
+def EmitC_SubscriptOp : EmitC_Op<"subscript", [CExpressionInterface]> {
   let summary = "Subscript operation";
   let description = [{
     With the `emitc.subscript` operation the subscript operator `[]` can be applied
@@ -1581,6 +1630,15 @@ def EmitC_SubscriptOp : EmitC_Op<"subscript", []> {
 
   let hasVerifier = 1;
   let assemblyFormat = "$value `[` $indices `]` attr-dict `:` functional-type(operands, results)";
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // C doesn't support references.
+    }
+  }];
 }
 
 def EmitC_SwitchOp : EmitC_Op<"switch", [RecursiveMemoryEffects,
@@ -1761,8 +1819,9 @@ def EmitC_FieldOp : EmitC_Op<"field", [Symbol]> {
 }
 
 def EmitC_GetFieldOp
-    : EmitC_Op<"get_field", [Pure, DeclareOpInterfaceMethods<
-                                       SymbolUserOpInterface>]> {
+    : EmitC_Op<"get_field", [Pure,
+                             DeclareOpInterfaceMethods<SymbolUserOpInterface>,
+                             CExpressionInterface]> {
   let summary = "Obtain access to a field within a class instance";
   let description = [{
      The `emitc.get_field` operation retrieves the lvalue of a
@@ -1779,6 +1838,15 @@ def EmitC_GetFieldOp
   let results = (outs EmitCType:$result);
   let assemblyFormat = "$field_name `:` type($result) attr-dict";
   let hasVerifier = 1;
+
+  let extraClassDeclaration = [{
+    bool hasSideEffects() {
+      return false;
+    }
+    bool alwaysInline() {
+      return true; // C doesn't support references.
+    }
+  }];
 }
 
 def EmitC_DoOp : EmitC_Op<"do",
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCInterfaces.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCInterfaces.td
index 777784e56202a..5e02d72252a23 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCInterfaces.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCInterfaces.td
@@ -42,6 +42,25 @@ def CExpressionInterface : OpInterface<"CExpressionInterface"> {
        /*defaultImplementation=*/[{
         return true;
     }]>,
+    InterfaceMethod<[{
+      Check whether operation must be inlined into all its users.
+
+      By default operation is not marked as always inlined.
+
+      ```c++
+      class ConcreteOp ... {
+      public:
+        bool alwaysInline() {
+          // That way we can override the default implementation.
+          return true;
+        }
+      };
+      ```
+    }],
+    "bool", "alwaysInline", (ins), /*methodBody=*/[{}],
+    /*defaultImplementation=*/[{
+        return false;
+    }]>,
   ];
 }
 
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 8def84fc49378..36394e67008da 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -606,7 +606,8 @@ LogicalResult ExpressionOp::verify() {
     Operation *op = worklist.back();
     worklist.pop_back();
     if (visited.contains(op)) {
-      if (cast<CExpressionInterface>(op).hasSideEffects())
+      auto cExpr = cast<CExpressionInterface>(op);
+      if (!cExpr.alwaysInline() && cExpr.hasSideEffects())
         return emitOpError(
             "requires exactly one use for operations with side effects");
     }
@@ -617,6 +618,14 @@ LogicalResult ExpressionOp::verify() {
       }
   }
 
+  // It is illegal to forbid inlining of expressions whose root operation must
+  // be inlined.
+  if (getDoNotInline() &&
+      cast<emitc::CExpressionInterface>(rootOp).alwaysInline()) {
+    return emitOpError("root operation must be inlined but expression is marked"
+                       " do-not-inline");
+  }
+
   return success();
 }
 
@@ -1112,6 +1121,10 @@ LogicalResult emitc::YieldOp::verify() {
   if (!isa<DoOp>(containingOp) && !result && containingOp->getNumResults() != 0)
     return emitOpError() << "does not yield a value to be returned by parent";
 
+  if (result && isa<emitc::LValueType>(result.getType()) &&
+      !isa<ExpressionOp>(containingOp))
+    return emitOpError() << "yielding lvalues is not supported for this op";
+
   return success();
 }
 
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index fd96395f77235..f4dbb2afcaf65 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -8,6 +8,7 @@
 
 #include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
 #include "mlir/Dialect/EmitC/IR/EmitC.h"
+#include "mlir/Dialect/EmitC/IR/EmitCInterfaces.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/IR/BuiltinOps.h"
 #include "mlir/IR/BuiltinTypes.h"
@@ -21,6 +22,7 @@
 #include "llvm/ADT/ScopedHashTable.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/TypeSwitch.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/FormatVariadic.h"
 #include <stack>
@@ -99,14 +101,20 @@ static FailureOr<int> getOperatorPrecedence(Operation *operation) {
       })
       .Case([&](emitc::ConditionalOp op) { return 2; })
       .Case([&](emitc::ConstantOp op) { return 17; })
+      .Case([&](emitc::DereferenceOp op) { return 15; })
       .Case([&](emitc::DivOp op) { return 13; })
+      .Case([&](emitc::GetGlobalOp op) { return 18; })
+      .Case([&](emitc::LiteralOp op) { return 18; })
       .Case([&](emitc::LoadOp op) { return 16; })
       .Case([&](emitc::LogicalAndOp op) { return 4; })
       .Case([&](emitc::LogicalNotOp op) { return 15; })
       .Case([&](emitc::LogicalOrOp op) { return 3; })
+      .Case([&](emitc::MemberOfPtrOp op) { return 17; })
+      .Case([&](emitc::MemberOp op) { return 17; })
       .Case([&](emitc::MulOp op) { return 13; })
       .Case([&](emitc::RemOp op) { return 13; })
       .Case([&](emitc::SubOp op) { return 12; })
+      .Case([&](emitc::SubscriptOp op) { return 17; })
       .Case([&](emitc::UnaryMinusOp op) { return 15; })
       .Case([&](emitc::UnaryPlusOp op) { return 15; })
       .Default([](auto op) { return op->emitError("unsupported operation"); });
@@ -183,10 +191,7 @@ struct CppEmitter {
   LogicalResult emitOperand(Value value, bool isInBrackets = false);
 
   /// Emit an expression as a C expression.
-  LogicalResult emitExpression(ExpressionOp expressionOp);
-
-  /// Insert the expression representing the operation into the value cache.
-  void cacheDeferredOpResult(Value value, StringRef str);
+  LogicalResult emitExpression(Operation *op);
 
   /// Return the existing or a new name for a Value.
   StringRef getOrCreateName(Value val);
@@ -331,30 +336,26 @@ struct CppEmitter {
 };
 } // namespace
 
-/// Determine whether expression \p op should be emitted in a deferred way.
-static bool hasDeferredEmission(Operation *op) {
-  return isa_and_nonnull<emitc::DereferenceOp, emitc::GetGlobalOp,
-                         emitc::LiteralOp, emitc::MemberOp,
-                         emitc::MemberOfPtrOp, emitc::SubscriptOp,
-                         emitc::GetFieldOp>(op);
-}
-
 /// Determine whether operation \p op should be emitted inline, i.e.
 /// as part of its user. This function recommends inlining of any expressions
 /// that can be inlined unless it is used by another expression, under the
 /// assumption that  any expression fusion/re-materialization was taken care of
 /// by transformations run by the backend.
 static bool shouldBeInlined(Operation *op) {
-  // CExpression operations are inlined if and only if they reside within an
-  // ExpressionOp.
-  if (isa<CExpressionInterface>(op))
-    return isa<ExpressionOp>(op->getParentOp());
+  // CExpression operations are inlined if and only if they are marked as
+  // always-inline or reside in an ExpressionOp.
+  if (auto cExpression = dyn_cast<CExpressionInterface>(op))
+    return cExpression.alwaysInline() || isa<ExpressionOp>(op->getParentOp());
 
   // Only other inlinable operation is ExpressionOp itself.
   ExpressionOp expressionOp = dyn_cast<ExpressionOp>(op);
   if (!expressionOp)
     return false;
 
+  // Inline if the root operation is an always-inline CExpression.
+  if (cast<CExpressionInterface>(expressionOp.getRootOp()).alwaysInline())
+    return true;
+
   // Do not inline if expression is marked as such.
   if (expressionOp.getDoNotInline())
     return false;
@@ -366,11 +367,6 @@ static bool shouldBeInlined(Operation *op) {
 
   Operation *user = *result.getUsers().begin();
 
-  // Do not inline expressions used by operations with deferred emission, since
-  // their translation requires the materialization of variables.
-  if (hasDeferredEmission(user))
-    return false;
-
   // Do not inline expressions used by other expressions or by ops with the
   // CExpressionInterface. If this was intended, the user could have been merged
   // into the expression op.
@@ -443,61 +439,79 @@ static LogicalResult emitAddressOfWithConstCast(CppEmitter &emitter,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::DereferenceOp dereferenceOp) {
-  std::string out;
-  llvm::raw_string_ostream ss(out);
-  ss << "*" << emitter.getOrCreateName(dereferenceOp.getPointer());
-  emitter.cacheDeferredOpResult(dereferenceOp.getResult(), out);
-  return success();
+  raw_ostream &os = emitter.ostream();
+  Operation &op = *dereferenceOp.getOperation();
+
+  if (failed(emitter.emitAssignPrefix(op)))
+    return failure();
+  os << "*";
+  return emitter.emitOperand(dereferenceOp.getPointer());
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::GetFieldOp getFieldOp) {
-  emitter.cacheDeferredOpResult(getFieldOp.getResult(),
-                                getFieldOp.getFieldName());
+  if (!emitter.isPartOfCurrentExpression(getFieldOp.getOperation()))
+    return success();
+
+  emitter.ostream() << getFieldOp.getFieldName();
   return success();
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::GetGlobalOp getGlobalOp) {
-  emitter.cacheDeferredOpResult(getGlobalOp.getResult(), getGlobalOp.getName());
+  if (!emitter.isPartOfCurrentExpression(getGlobalOp.getOperation()))
+    return success();
+
+  emitter.ostream() << getGlobalOp.getName();
   return success();
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::LiteralOp literalOp) {
-  emitter.cacheDeferredOpResult(literalOp.getResult(), literalOp.getValue());
+  if (!emitter.isPartOfCurrentExpression(literalOp.getOperation()))
+    return success();
+
+  emitter.ostream() << literalOp.getValue();
   return success();
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::MemberOp memberOp) {
-  std::string out;
-  llvm::raw_string_ostream ss(out);
-  ss << emitter.getOrCreateName(memberOp.getOperand());
-  ss << "." << memberOp.getMember();
-  emitter.cacheDeferredOpResult(memberOp.getResult(), out);
+  if (!emitter.isPartOfCurrentExpression(memberOp.getOperation()))
+    return success();
+
+  if (failed(emitter.emitOperand(memberOp.getOperand())))
+    return failure();
+  emitter.ostream() << "." << memberOp.getMember();
   return success();
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::MemberOfPtrOp memberOfPtrOp) {
-  std::string out;
-  llvm::raw_string_ostream ss(out);
-  ss << emitter.getOrCreateName(memberOfPtrOp.getOperand());
-  ss << "->" << memberOfPtrOp.getMember();
-  emitter.cacheDeferredOpResult(memberOfPtrOp.getResult(), out);
+  if (!emitter.isPartOfCurrentExpression(memberOfPtrOp.getOperation()))
+    return success();
+
+  if (failed(emitter.emitOperand(memberOfPtrOp.getOperand())))
+    return failure();
+  emitter.ostream() << "->" << memberOfPtrOp.getMember();
   return success();
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::SubscriptOp subscriptOp) {
-  std::string out;
-  llvm::raw_string_ostream ss(out);
-  ss << emitter.getOrCreateName(subscriptOp.getValue());
+  if (!emitter.isPartOfCurrentExpression(subscriptOp.getOperation())) {
+    return success();
+  }
+
+  raw_ostream &os = emitter.ostream();
+  if (failed(emitter.emitOperand(subscriptOp.getValue())))
+    return failure();
   for (auto index : subscriptOp.getIndices()) {
-    ss << "[" << emitter.getOrCreateName(index) << "]";
+    os << "[";
+    if (failed(emitter.emitOperand(index, /*isInBrackets=*/true)))
+      return failure();
+    os << "]";
   }
-  emitter.cacheDeferredOpResult(subscriptOp.getResult(), out);
   return success();
 }
 
@@ -578,11 +592,11 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::AssignOp assignOp) {
-  OpResult result = assignOp.getVar().getDefiningOp()->getResult(0);
-
-  if (failed(emitter.emitVariableAssignment(result)))
+  if (failed(emitter.emitOperand(assignOp.getVar())))
     return failure();
 
+  emitter.ostream() << " = ";
+
   return emitter.emitOperand(assignOp.getValue());
 }
 
@@ -1461,18 +1475,9 @@ CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop,
   labelInScopeCount.push(0);
 }
 
-void CppEmitter::cacheDeferredOpResult(Value value, StringRef str) {
-  if (!valueMapper.count(value))
-    valueMapper.insert(value, str.str());
-}
-
 /// Return the existing or a new name for a Value.
 StringRef CppEmitter::getOrCreateName(Value val) {
   if (!valueMapper.count(val)) {
-    assert(!hasDeferredEmission(val.getDefiningOp()) &&
-           "cacheDeferredOpResult should have been called on this value, "
-           "update the emitOperation function.");
-
     valueMapper.insert(val, formatv("v{0}", ++valueCount));
   }
   return *valueMapper.begin(val);
@@ -1639,11 +1644,20 @@ LogicalResult CppEmitter::emitAttribute(Location loc, Attribute attr) {
   return emitError(loc, "cannot emit attribute: ") << attr;
 }
 
-LogicalResult CppEmitter::emitExpression(ExpressionOp expressionOp) {
+LogicalResult CppEmitter::emitExpression(Operation *op) {
   assert(emittedExpressionPrecedence.empty() &&
          "Expected precedence stack to be empty");
-  Operation *rootOp = expressionOp.getRootOp();
+  Operation *rootOp = nullptr;
 
+  if (auto expressionOp = dyn_cast<ExpressionOp>(op)) {
+    rootOp = expressionOp.getRootOp();
+  } else {
+    assert(cast<CExpressionInterface>(op).alwaysInline() &&
+           "Expected an always-inline operation");
+    assert(!isa<ExpressionOp>(op->getParentOp()) &&
+           "Expected operation to have no containing expression");
+    rootOp = op;
+  }
   FailureOr<int> precedence = getOperatorPrecedence(rootOp);
   if (failed(precedence))
     return failure();
@@ -1663,6 +1677,8 @@ LogicalResult CppEmitter::emitOperand(Value value, bool isInBrackets) {
   if (isPartOfCurrentExpression(value)) {
     Operation *def = value.getDefiningOp();
     assert(def && "Expected operand to be defined by an operation");
+    if (auto expressionOp = dyn_cast<ExpressionOp>(def))
+      def = expressionOp.getRootOp();
     FailureOr<int> precedence = getOperatorPrecedence(def);
     if (failed(precedence))
       return failure();
@@ -1691,6 +1707,12 @@ LogicalResult CppEmitter::emitOperand(Value value, bool isInBrackets) {
   if (expressionOp && shouldBeInlined(expressionOp))
     return emitExpression(expressionOp);
 
+  if (auto cExpression =
+          dyn_cast_if_present<CExpressionInterface>(value.getDefiningOp())) {
+    if (cExpression.alwaysInline())
+      return emitExpression(value.getDefiningOp());
+  }
+
   if (BlockArgument arg = dyn_cast<BlockArgument>(value)) {
     // If this operand is a block argument of an expression, emit instead the
     // matching expression parameter.
@@ -1748,8 +1770,11 @@ LogicalResult CppEmitter::emitVariableAssignment(OpResult result) {
 
 LogicalResult CppEmitter::emitVariableDeclaration(OpResult result,
                                                   bool trailingSemicolon) {
-  if (hasDeferredEmission(result.getDefiningOp()))
-    return success();
+  if (auto cExpression =
+          dyn_cast<CExpressionInterface>(result.getDefiningOp())) {
+    if (cExpression.alwaysInline())
+      return success();
+  }
   if (hasValueInScope(result)) {
     return result.getDefiningOp()->emitError(
         "result variable for the operation already declared");
@@ -1868,8 +1893,10 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
   if (failed(status))
     return failure();
 
-  if (hasDeferredEmission(&op))
-    return success();
+  if (auto cExpression = dyn_cast<CExpressionInterface>(op)) {
+    if (cExpression.alwaysInline())
+      return success();
+  }
 
   if (isEmittingExpression() ||
       (isa<emitc::ExpressionOp>(op) &&
diff --git a/mlir/test/Dialect/EmitC/form-expressions.mlir b/mlir/test/Dialect/EmitC/form-expressions.mlir
index f5c002bba84a1..1f44d7e870915 100644
--- a/mlir/test/Dialect/EmitC/form-expressions.mlir
+++ b/mlir/test/Dialect/EmitC/form-expressions.mlir
@@ -150,17 +150,14 @@ func.func @single_result_requirement() -> (i32, i32) {
 // CHECK-LABEL:   func.func @expression_with_load(
 // CHECK-SAME:                                    %[[VAL_0:.*]]: i32,
 // CHECK-SAME:                                    %[[VAL_1:.*]]: !emitc.ptr<i32>) -> i1 {
-// CHECK:           %[[VAL_2:.*]] = emitc.expression : () -> i64 {
-// CHECK:             %[[VAL_C:.*]] = "emitc.constant"() <{value = 0 : i64}> : () -> i64
-// CHECK:             yield %[[VAL_C]] : i64
-// CHECK:           }
 // CHECK:           %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"42">}> : () -> !emitc.lvalue<i32>
 // CHECK:           %[[VAL_4:.*]] = emitc.expression %[[VAL_3]] : (!emitc.lvalue<i32>) -> i32 {
 // CHECK:             %[[VAL_5:.*]] = load %[[VAL_3]] : <i32>
 // CHECK:             yield %[[VAL_5]] : i32
 // CHECK:           }
-// CHECK:           %[[VAL_6:.*]] = emitc.subscript %[[VAL_1]]{{\[}}%[[VAL_2]]] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
-// CHECK:           %[[VAL_7:.*]] = emitc.expression %[[VAL_6]] : (!emitc.lvalue<i32>) -> i32 {
+// CHECK:           %[[VAL_7:.*]] = emitc.expression %[[VAL_1]] : (!emitc.ptr<i32>) -> i32 {
+// CHECK:             %[[VAL_C:.*]] = "emitc.constant"() <{value = 0 : i64}> : () -> i64
+// CHECK:             %[[VAL_6:.*]] = subscript %[[VAL_1]]{{\[}}%[[VAL_C]]] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
 // CHECK:             %[[VAL_8:.*]] = load %[[VAL_6]] : <i32>
 // CHECK:             yield %[[VAL_8]] : i32
 // CHECK:           }
@@ -255,3 +252,157 @@ func.func @identity_cast_folded(%arg0: i32) -> i32 {
   %1 = emitc.bitwise_and %0, %arg0 : (i32, i32) -> i32
   return %1 : i32
 }
+
+// CHECK-LABEL:   func.func @expression_with_subscript(
+// CHECK-SAME:      %[[ARG0:.*]]: !emitc.array<4x8xi32>,
+// CHECK-SAME:      %[[ARG1:.*]]: i32,
+// CHECK-SAME:      %[[ARG2:.*]]: i32) -> i32 {
+// CHECK:           %[[VAL_0:.*]] = emitc.expression %[[ARG0]], %[[ARG2]], %[[ARG1]] : (!emitc.array<4x8xi32>, i32, i32) -> i32 {
+// CHECK:             %[[VAL_1:.*]] = add %[[ARG1]], %[[ARG2]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_2:.*]] = mul %[[VAL_1]], %[[ARG2]] : (i32, i32) -> i32
+// CHECK:             %[[VAL_3:.*]] = subscript %[[ARG0]]{{\[}}%[[VAL_1]], %[[VAL_2]]] : (!emitc.array<4x8xi32>, i32, i32) -> !emitc.lvalue<i32>
+// CHECK:             %[[VAL_4:.*]] = load %[[VAL_3]] : <i32>
+// CHECK:             yield %[[VAL_4]] : i32
+// CHECK:           }
+// CHECK:           return %[[VAL_0]] : i32
+// CHECK:         }
+
+func.func @expression_with_subscript(%arg0: !emitc.array<4x8xi32>, %arg1: i32, %arg2: i32) -> i32 {
+  %0 = emitc.add %arg1, %arg2 : (i32, i32) -> i32
+  %1 = emitc.mul %0, %arg2 : (i32, i32) -> i32
+  %2 = emitc.subscript %arg0[%0, %1] : (!emitc.array<4x8xi32>, i32, i32) -> !emitc.lvalue<i32>
+  %3 = emitc.load %2 : !emitc.lvalue<i32>
+  return %3 : i32
+}
+
+// CHECK-LABEL:   func.func @member(
+// CHECK-SAME:                      %[[ARG0:.*]]: !emitc.opaque<"mystruct">,
+// CHECK-SAME:                      %[[ARG1:.*]]: i32,
+// CHECK-SAME:                      %[[ARG2:.*]]: index) {
+// CHECK:           %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"mystruct">>
+// CHECK:           emitc.assign %[[ARG0]] : !emitc.opaque<"mystruct"> to %[[VAL_0]] : <!emitc.opaque<"mystruct">>
+// CHECK:           %[[VAL_1:.*]] = emitc.expression %[[VAL_0]] : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32> {
+// CHECK:             %[[VAL_2:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "a"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
+// CHECK:             yield %[[VAL_2]] : !emitc.lvalue<i32>
+// CHECK:           }
+// CHECK:           emitc.assign %[[ARG1]] : i32 to %[[VAL_1]] : <i32>
+// CHECK:           %[[VAL_3:.*]] = emitc.expression %[[VAL_0]] : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> i32 {
+// CHECK:             %[[VAL_4:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "b"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_4]] : <i32>
+// CHECK:             yield %[[VAL_5]] : i32
+// CHECK:           }
+// CHECK:           %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_3]] : i32 to %[[VAL_6]] : <i32>
+// CHECK:           %[[VAL_7:.*]] = emitc.expression %[[ARG2]], %[[VAL_0]] : (index, !emitc.lvalue<!emitc.opaque<"mystruct">>) -> i32 {
+// CHECK:             %[[VAL_8:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "c"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.array<2xi32>
+// CHECK:             %[[VAL_9:.*]] = subscript %[[VAL_8]]{{\[}}%[[ARG2]]] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+// CHECK:             %[[VAL_10:.*]] = load %[[VAL_9]] : <i32>
+// CHECK:             yield %[[VAL_10]] : i32
+// CHECK:           }
+// CHECK:           emitc.assign %[[VAL_7]] : i32 to %[[VAL_6]] : <i32>
+// CHECK:           %[[VAL_11:.*]] = emitc.expression %[[ARG2]], %[[VAL_0]] : (index, !emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32> {
+// CHECK:             %[[VAL_12:.*]] = "emitc.member"(%[[VAL_0]]) <{member = "d"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.array<2xi32>
+// CHECK:             %[[VAL_13:.*]] = subscript %[[VAL_12]]{{\[}}%[[ARG2]]] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+// CHECK:             yield %[[VAL_13]] : !emitc.lvalue<i32>
+// CHECK:           }
+// CHECK:           emitc.assign %[[ARG1]] : i32 to %[[VAL_11]] : <i32>
+// CHECK:           return
+// CHECK:         }
+
+func.func @member(%arg0: !emitc.opaque<"mystruct">, %arg1: i32, %arg2: index) {
+  %var0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"mystruct">>
+  emitc.assign %arg0 : !emitc.opaque<"mystruct"> to %var0 : !emitc.lvalue<!emitc.opaque<"mystruct">>
+
+  %0 = "emitc.member" (%var0) {member = "a"} : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
+  emitc.assign %arg1 : i32 to %0 : !emitc.lvalue<i32>
+
+  %1 = "emitc.member" (%var0) {member = "b"} : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
+  %2 = emitc.load %1 : !emitc.lvalue<i32>
+  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  emitc.assign %2 : i32 to %3 : !emitc.lvalue<i32>
+
+  %4 = "emitc.member" (%var0) {member = "c"} : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.array<2xi32>
+  %5 = emitc.subscript %4[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+  %6 = emitc.load %5 : <i32>
+  emitc.assign %6 : i32 to %3 : !emitc.lvalue<i32>
+
+  %7 = "emitc.member" (%var0) {member = "d"} : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.array<2xi32>
+  %8 = emitc.subscript %7[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+  emitc.assign %arg1 : i32 to %8 : !emitc.lvalue<i32>
+
+  return
+}
+
+// CHECK-LABEL:   func.func @member_of_pointer(
+// CHECK-SAME:      %[[ARG0:.*]]: !emitc.ptr<!emitc.opaque<"mystruct">>,
+// CHECK-SAME:      %[[ARG1:.*]]: i32,
+// CHECK-SAME:      %[[ARG2:.*]]: index) {
+// CHECK:           %[[VAL_0:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>
+// CHECK:           emitc.assign %[[ARG0]] : !emitc.ptr<!emitc.opaque<"mystruct">> to %[[VAL_0]] : <!emitc.ptr<!emitc.opaque<"mystruct">>>
+// CHECK:           %[[VAL_1:.*]] = emitc.expression %[[VAL_0]] : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32> {
+// CHECK:             %[[VAL_2:.*]] = "emitc.member_of_ptr"(%[[VAL_0]]) <{member = "a"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
+// CHECK:             yield %[[VAL_2]] : !emitc.lvalue<i32>
+// CHECK:           }
+// CHECK:           emitc.assign %[[ARG1]] : i32 to %[[VAL_1]] : <i32>
+// CHECK:           %[[VAL_3:.*]] = emitc.expression %[[VAL_0]] : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> i32 {
+// CHECK:             %[[VAL_4:.*]] = "emitc.member_of_ptr"(%[[VAL_0]]) <{member = "b"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
+// CHECK:             %[[VAL_5:.*]] = load %[[VAL_4]] : <i32>
+// CHECK:             yield %[[VAL_5]] : i32
+// CHECK:           }
+// CHECK:           %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK:           emitc.assign %[[VAL_3]] : i32 to %[[VAL_6]] : <i32>
+// CHECK:           %[[VAL_7:.*]] = emitc.expression %[[ARG2]], %[[VAL_0]] : (index, !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> i32 {
+// CHECK:             %[[VAL_8:.*]] = "emitc.member_of_ptr"(%[[VAL_0]]) <{member = "c"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+// CHECK:             %[[VAL_9:.*]] = subscript %[[VAL_8]]{{\[}}%[[ARG2]]] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+// CHECK:             %[[VAL_10:.*]] = load %[[VAL_9]] : <i32>
+// CHECK:             yield %[[VAL_10]] : i32
+// CHECK:           }
+// CHECK:           emitc.assign %[[VAL_7]] : i32 to %[[VAL_6]] : <i32>
+// CHECK:           %[[VAL_11:.*]] = emitc.expression %[[ARG2]], %[[VAL_0]] : (index, !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32> {
+// CHECK:             %[[VAL_12:.*]] = "emitc.member_of_ptr"(%[[VAL_0]]) <{member = "d"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+// CHECK:             %[[VAL_13:.*]] = subscript %[[VAL_12]]{{\[}}%[[ARG2]]] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+// CHECK:             yield %[[VAL_13]] : !emitc.lvalue<i32>
+// CHECK:           }
+// CHECK:           emitc.assign %[[ARG1]] : i32 to %[[VAL_11]] : <i32>
+// CHECK:           return
+// CHECK:         }
+
+func.func @member_of_pointer(%arg0: !emitc.ptr<!emitc.opaque<"mystruct">>, %arg1: i32, %arg2: index) {
+  %var0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>
+  emitc.assign %arg0 : !emitc.ptr<!emitc.opaque<"mystruct">> to %var0 : !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>
+
+  %0 = "emitc.member_of_ptr" (%var0) {member = "a"} : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
+  emitc.assign %arg1 : i32 to %0 : !emitc.lvalue<i32>
+
+  %1 = "emitc.member_of_ptr" (%var0) {member = "b"} : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
+  %2 = emitc.load %1 : !emitc.lvalue<i32>
+  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  emitc.assign %2 : i32 to %3 : !emitc.lvalue<i32>
+
+  %4 = "emitc.member_of_ptr" (%var0) {member = "c"} : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+  %5 = emitc.subscript %4[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+  %6 = emitc.load %5 : <i32>
+  emitc.assign %6 : i32 to %3 : !emitc.lvalue<i32>
+
+  %7 = "emitc.member_of_ptr" (%var0) {member = "d"} : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+  %8 = emitc.subscript %7[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+  emitc.assign %arg1 : i32 to %8 : !emitc.lvalue<i32>
+
+  return
+}
+
+// CHECK-LABEL:   func.func @expression_with_literal(
+// CHECK-SAME:      %[[ARG0:.*]]: f32) -> f32 {
+// CHECK:           %[[VAL_0:.*]] = emitc.expression %[[ARG0]] : (f32) -> f32 {
+// CHECK:             %[[VAL_1:.*]] = literal "M_PI" : f32
+// CHECK:             %[[VAL_2:.*]] = add %[[ARG0]], %[[VAL_1]] : (f32, f32) -> f32
+// CHECK:             yield %[[VAL_2]] : f32
+// CHECK:           }
+// CHECK:           return %[[VAL_0]] : f32
+// CHECK:         }
+
+func.func @expression_with_literal(%arg0: f32) -> f32 {
+  %p0 = emitc.literal "M_PI" : f32
+  %1 = "emitc.add" (%arg0, %p0) : (f32, f32) -> f32
+  return %1 : f32
+}
diff --git a/mlir/test/Target/Cpp/expressions.mlir b/mlir/test/Target/Cpp/expressions.mlir
index 2de94d0b11fc8..7280377990cfc 100644
--- a/mlir/test/Target/Cpp/expressions.mlir
+++ b/mlir/test/Target/Cpp/expressions.mlir
@@ -374,6 +374,26 @@ func.func @expression_with_address_taken(%arg0: i32, %arg1: i32, %arg2: !emitc.p
   return %c : i1
 }
 
+// CPP-DEFAULT: int32_t expression_with_subscript(int32_t [[VAL_1:v.+]][4][8], int32_t [[VAL_2:v.+]], int32_t [[VAL_3:v.+]])
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_4:v.+]] = [[VAL_1]][[[VAL_2]] + [[VAL_3]]][([[VAL_2]] + [[VAL_3]]) * [[VAL_3]]];
+// CPP-DEFAULT-NEXT:   return [[VAL_4]];
+
+// CPP-DECLTOP: int32_t expression_with_subscript(int32_t [[VAL_1:v.+]][4][8], int32_t [[VAL_2:v.+]], int32_t [[VAL_3:v.+]])
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_4:v.+]];
+// CPP-DECLTOP-NEXT:   [[VAL_4]] = [[VAL_1]][[[VAL_2]] + [[VAL_3]]][([[VAL_2]] + [[VAL_3]]) * [[VAL_3]]];
+// CPP-DECLTOP-NEXT:   return [[VAL_4]];
+
+func.func @expression_with_subscript(%arg0: !emitc.array<4x8xi32>, %arg1: i32, %arg2: i32) -> i32 {
+  %res = emitc.expression %arg0, %arg1, %arg2 : (!emitc.array<4x8xi32>, i32, i32) -> i32 {
+    %0 = add %arg1, %arg2 : (i32, i32) -> i32
+    %1 = mul %0, %arg2 : (i32, i32) -> i32
+    %2 = subscript %arg0[%0, %1] : (!emitc.array<4x8xi32>, i32, i32) -> !emitc.lvalue<i32>
+    %3 = emitc.load %2 : !emitc.lvalue<i32>
+    yield %3 : i32
+  }
+  return %res : i32
+}
+
 // CPP-DEFAULT: int32_t expression_with_subscript_user(void* [[VAL_1:v.+]])
 // CPP-DEFAULT-NEXT:   int64_t [[VAL_2:v.+]] = 0;
 // CPP-DEFAULT-NEXT:   int32_t* [[VAL_3:v.+]] = (int32_t*) [[VAL_1]];
@@ -672,3 +692,163 @@ func.func @inline_side_effects_into_switch(%arg0: i32, %arg1: i32, %arg2: i32) {
   }
   return
 }
+
+// CPP-DEFAULT: void member(mystruct [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], size_t [[VAL_3:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT:   mystruct [[VAL_4:v[0-9]+]];
+// CPP-DEFAULT-NEXT:   [[VAL_4]] = [[VAL_1]];
+// CPP-DEFAULT-NEXT:   [[VAL_4]].a = [[VAL_2]];
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]].b;
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_6:v[0-9]+]];
+// CPP-DEFAULT-NEXT:   [[VAL_6]] = [[VAL_5]];
+// CPP-DEFAULT-NEXT:   [[VAL_6]] = ([[VAL_4]].c)[[[VAL_3]]];
+// CPP-DEFAULT-NEXT:   ([[VAL_4]].d)[[[VAL_3]]] = [[VAL_2]];
+// CPP-DEFAULT-NEXT:   return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void member(mystruct [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], size_t [[VAL_3:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT:   mystruct [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_6:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   ;
+// CPP-DECLTOP-NEXT:   [[VAL_4]] = [[VAL_1]];
+// CPP-DECLTOP-NEXT:   [[VAL_4]].a = [[VAL_2]];
+// CPP-DECLTOP-NEXT:   [[VAL_5]] = [[VAL_4]].b;
+// CPP-DECLTOP-NEXT:   ;
+// CPP-DECLTOP-NEXT:   [[VAL_6]] = [[VAL_5]];
+// CPP-DECLTOP-NEXT:   [[VAL_6]] = ([[VAL_4]].c)[[[VAL_3]]];
+// CPP-DECLTOP-NEXT:   ([[VAL_4]].d)[[[VAL_3]]] = [[VAL_2]];
+// CPP-DECLTOP-NEXT:   return;
+// CPP-DECLTOP-NEXT: }
+
+func.func @member(%arg0: !emitc.opaque<"mystruct">, %arg1: i32, %arg2: index) {
+  %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.opaque<"mystruct">>
+  emitc.assign %arg0 : !emitc.opaque<"mystruct"> to %0 : <!emitc.opaque<"mystruct">>
+  %1 = emitc.expression %0 : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32> {
+    %6 = "emitc.member"(%0) <{member = "a"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
+    yield %6 : !emitc.lvalue<i32>
+  }
+  emitc.assign %arg1 : i32 to %1 : <i32>
+  %2 = emitc.expression %0 : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> i32 {
+    %6 = "emitc.member"(%0) <{member = "b"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32>
+    %7 = load %6 : <i32>
+    yield %7 : i32
+  }
+  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  emitc.assign %2 : i32 to %3 : <i32>
+  %4 = emitc.expression %arg2, %0 : (index, !emitc.lvalue<!emitc.opaque<"mystruct">>) -> i32 {
+    %6 = "emitc.member"(%0) <{member = "c"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.array<2xi32>
+    %7 = subscript %6[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+    %8 = load %7 : <i32>
+    yield %8 : i32
+  }
+  emitc.assign %4 : i32 to %3 : <i32>
+  %5 = emitc.expression %arg2, %0 : (index, !emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.lvalue<i32> {
+    %6 = "emitc.member"(%0) <{member = "d"}> : (!emitc.lvalue<!emitc.opaque<"mystruct">>) -> !emitc.array<2xi32>
+    %7 = subscript %6[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+    yield %7 : !emitc.lvalue<i32>
+  }
+  emitc.assign %arg1 : i32 to %5 : <i32>
+  return
+}
+
+// CPP-DEFAULT: void member_of_pointer(mystruct* [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], size_t [[VAL_3:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT:   mystruct* [[VAL_4:v[0-9]+]];
+// CPP-DEFAULT-NEXT:   [[VAL_4]] = [[VAL_1]];
+// CPP-DEFAULT-NEXT:   [[VAL_4]]->a = [[VAL_2]];
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_5:v[0-9]+]] = [[VAL_4]]->b;
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_6:v[0-9]+]];
+// CPP-DEFAULT-NEXT:   [[VAL_6]] = [[VAL_5]];
+// CPP-DEFAULT-NEXT:   [[VAL_6]] = ([[VAL_4]]->c)[[[VAL_3]]];
+// CPP-DEFAULT-NEXT:   ([[VAL_4]]->d)[[[VAL_3]]] = [[VAL_2]];
+// CPP-DEFAULT-NEXT:   return;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: void member_of_pointer(mystruct* [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], size_t [[VAL_3:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT:   mystruct* [[VAL_4:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_6:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   ;
+// CPP-DECLTOP-NEXT:   [[VAL_4]] = [[VAL_1]];
+// CPP-DECLTOP-NEXT:   [[VAL_4]]->a = [[VAL_2]];
+// CPP-DECLTOP-NEXT:   [[VAL_5]] = [[VAL_4]]->b;
+// CPP-DECLTOP-NEXT:   ;
+// CPP-DECLTOP-NEXT:   [[VAL_6]] = [[VAL_5]];
+// CPP-DECLTOP-NEXT:   [[VAL_6]] = ([[VAL_4]]->c)[[[VAL_3]]];
+// CPP-DECLTOP-NEXT:   ([[VAL_4]]->d)[[[VAL_3]]] = [[VAL_2]];
+// CPP-DECLTOP-NEXT:   return;
+// CPP-DECLTOP-NEXT: }
+
+func.func @member_of_pointer(%arg0: !emitc.ptr<!emitc.opaque<"mystruct">>, %arg1: i32, %arg2: index) {
+  %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>
+  emitc.assign %arg0 : !emitc.ptr<!emitc.opaque<"mystruct">> to %0 : <!emitc.ptr<!emitc.opaque<"mystruct">>>
+  %1 = emitc.expression %0 : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32> {
+    %6 = "emitc.member_of_ptr"(%0) <{member = "a"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
+    yield %6 : !emitc.lvalue<i32>
+  }
+  emitc.assign %arg1 : i32 to %1 : <i32>
+  %2 = emitc.expression %0 : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> i32 {
+    %6 = "emitc.member_of_ptr"(%0) <{member = "b"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32>
+    %7 = load %6 : <i32>
+    yield %7 : i32
+  }
+  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  emitc.assign %2 : i32 to %3 : <i32>
+  %4 = emitc.expression %arg2, %0 : (index, !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> i32 {
+    %6 = "emitc.member_of_ptr"(%0) <{member = "c"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+    %7 = subscript %6[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+    %8 = load %7 : <i32>
+    yield %8 : i32
+  }
+  emitc.assign %4 : i32 to %3 : <i32>
+  %5 = emitc.expression %arg2, %0 : (index, !emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.lvalue<i32> {
+    %6 = "emitc.member_of_ptr"(%0) <{member = "d"}> : (!emitc.lvalue<!emitc.ptr<!emitc.opaque<"mystruct">>>) -> !emitc.array<2xi32>
+    %7 = subscript %6[%arg2] : (!emitc.array<2xi32>, index) -> !emitc.lvalue<i32>
+    yield %7 : !emitc.lvalue<i32>
+  }
+  emitc.assign %arg1 : i32 to %5 : <i32>
+  return
+}
+
+// CPP-DEFAULT: float expression_with_literal(float [[VAL_1:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT:   return [[VAL_1]] + M_PI;
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: float expression_with_literal(float [[VAL_1:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT:   return [[VAL_1]] + M_PI;
+// CPP-DECLTOP-NEXT: }
+
+func.func @expression_with_literal(%arg0: f32) -> f32 {
+  %0 = emitc.expression %arg0 : (f32) -> f32 {
+    %1 = literal "M_PI" : f32
+    %2 = add %arg0, %1 : (f32, f32) -> f32
+    yield %2 : f32
+  }
+  return %0 : f32
+}
+
+// CPP-DEFAULT: bool expression_tree(int32_t [[VAL_1:v[0-9]+]][2000], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]], size_t [[VAL_4:v[0-9]+]]) {
+// CPP-DEFAULT-NEXT: bool [[VAL_5:v[0-9]+]] = [[VAL_1]][[[VAL_4]] * 42] - [[VAL_2]] < [[VAL_3]];
+// CPP-DEFAULT-NEXT: return [[VAL_5]];
+// CPP-DEFAULT-NEXT: }
+
+// CPP-DECLTOP: bool expression_tree(int32_t [[VAL_1:v[0-9]+]][2000], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]], size_t [[VAL_4:v[0-9]+]]) {
+// CPP-DECLTOP-NEXT: bool [[VAL_5:v[0-9]+]];
+// CPP-DECLTOP-NEXT: [[VAL_5]] = [[VAL_1]][[[VAL_4]] * 42] - [[VAL_2]] < [[VAL_3]];
+// CPP-DECLTOP-NEXT: return [[VAL_5]];
+// CPP-DECLTOP-NEXT: }
+
+func.func @expression_tree(%arg0: !emitc.array<2000xi32>, %arg1: i32, %arg2: i32, %arg4: index) -> i1 {
+  %e1 = emitc.expression %arg0, %arg4 : (!emitc.array<2000xi32>, index) -> !emitc.lvalue<i32> {
+    %c42 = "emitc.constant"(){value = 42 : index} : () -> index
+    %i = mul %arg4, %c42 : (index, index) -> index
+    %k = subscript %arg0[%i] : (!emitc.array<2000xi32>, index) -> !emitc.lvalue<i32>
+    yield %k : !emitc.lvalue<i32>
+  }
+  %e2 = emitc.expression %e1, %arg1, %arg2 : (!emitc.lvalue<i32>, i32, i32) -> i1 {
+    %a = load %e1: !emitc.lvalue<i32>
+    %b = sub %a, %arg1 : (i32, i32) -> i32
+    %c = cmp lt, %b, %arg2 :(i32, i32) -> i1
+    yield %c : i1
+  }
+  return %e2 : i1
+}
diff --git a/mlir/test/Target/Cpp/member.mlir b/mlir/test/Target/Cpp/member.mlir
index 6e0395250afbd..45b6336f63ce0 100644
--- a/mlir/test/Target/Cpp/member.mlir
+++ b/mlir/test/Target/Cpp/member.mlir
@@ -31,9 +31,9 @@ func.func @member(%arg0: !emitc.opaque<"mystruct">, %arg1: i32, %arg2: index) {
 // CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V2]].b;
 // CPP-DEFAULT-NEXT: int32_t [[V4:[^ ]*]];
 // CPP-DEFAULT-NEXT: [[V4]] = [[V3]];
-// CPP-DEFAULT-NEXT: int32_t [[V5:[^ ]*]] = [[V2]].c[[[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: ([[V2]].d)[[[Index]]] = [[V1]];
 
 
 func.func @member_of_pointer(%arg0: !emitc.ptr<!emitc.opaque<"mystruct">>, %arg1: i32, %arg2: index) {
@@ -67,6 +67,6 @@ func.func @member_of_pointer(%arg0: !emitc.ptr<!emitc.opaque<"mystruct">>, %arg1
 // CPP-DEFAULT-NEXT: int32_t [[V3:[^ ]*]] = [[V2]]->b;
 // CPP-DEFAULT-NEXT: int32_t [[V4:[^ ]*]];
 // CPP-DEFAULT-NEXT: [[V4]] = [[V3]];
-// CPP-DEFAULT-NEXT: int32_t [[V5:[^ ]*]] = [[V2]]->c[[[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: ([[V2]]->d)[[[Index]]] = [[V1]];

>From c11b8e729b3b7b4ff0cc0b1ea559a0f6bde1dff6 Mon Sep 17 00:00:00 2001
From: Gil Rapaport <gil.rapaport at mobileye.com>
Date: Sat, 25 Apr 2026 19:21:10 +0300
Subject: [PATCH 2/2] Implement review comment

In emitOperand it's enough to call shouldBeInlined() on the defining op
of the operand value (if there is one) as it already checks for these
two cases for calling emitExpression.
---
 mlir/lib/Target/Cpp/TranslateToCpp.cpp | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index f4dbb2afcaf65..dd61a81ce1cdc 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -1703,15 +1703,8 @@ LogicalResult CppEmitter::emitOperand(Value value, bool isInBrackets) {
     return success();
   }
 
-  auto expressionOp = value.getDefiningOp<ExpressionOp>();
-  if (expressionOp && shouldBeInlined(expressionOp))
-    return emitExpression(expressionOp);
-
-  if (auto cExpression =
-          dyn_cast_if_present<CExpressionInterface>(value.getDefiningOp())) {
-    if (cExpression.alwaysInline())
-      return emitExpression(value.getDefiningOp());
-  }
+  if (Operation *def = value.getDefiningOp(); def && shouldBeInlined(def))
+    return emitExpression(def);
 
   if (BlockArgument arg = dyn_cast<BlockArgument>(value)) {
     // If this operand is a block argument of an expression, emit instead the



More information about the Mlir-commits mailing list