[Mlir-commits] [mlir] [WIP][mlir][EmitC] Model lvalues as a type in EmitC (PR #91475)

Simon Camphausen llvmlistbot at llvm.org
Mon Jun 3 07:43:54 PDT 2024


https://github.com/simon-camp updated https://github.com/llvm/llvm-project/pull/91475

>From 1aadeb3df9b05b74a73428163b22e38eacdfde77 Mon Sep 17 00:00:00 2001
From: Simon Camphausen <simon.camphausen at iml.fraunhofer.de>
Date: Mon, 6 May 2024 12:30:56 +0000
Subject: [PATCH 1/3] [WIP][mlir][EmitC] Model lvalues as a type in EmitC

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   |  22 +++-
 .../mlir/Dialect/EmitC/IR/EmitCTypes.td       |  17 +++
 mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp | 104 +++++++++++++-----
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |  82 ++++++++++++--
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        |  61 ++++++++--
 mlir/test/Conversion/SCFToEmitC/for.mlir      |  55 ++++-----
 mlir/test/Conversion/SCFToEmitC/if.mlir       |  22 ++--
 mlir/test/Dialect/EmitC/invalid_ops.mlir      |  18 +--
 mlir/test/Dialect/EmitC/ops.mlir              |   4 +-
 mlir/test/Target/Cpp/common-cpp.mlir          |   8 +-
 mlir/test/Target/Cpp/expressions.mlir         |  33 +++---
 mlir/test/Target/Cpp/for.mlir                 |  56 ++++++----
 mlir/test/Target/Cpp/if.mlir                  |  12 +-
 mlir/test/Target/Cpp/invalid.mlir             |   2 +-
 mlir/test/Target/Cpp/lvalue.mlir              |  22 ++++
 mlir/test/Target/Cpp/subscript.mlir           |  13 ++-
 mlir/test/Target/Cpp/variable.mlir            |  20 ++--
 17 files changed, 390 insertions(+), 161 deletions(-)
 create mode 100644 mlir/test/Target/Cpp/lvalue.mlir

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 5da8593f59563..396bf37abe979 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -835,6 +835,22 @@ def EmitC_LogicalOrOp : EmitC_BinaryOp<"logical_or", [CExpression]> {
   let assemblyFormat = "operands attr-dict `:` type(operands)";
 }
 
+def EmitC_LValueToRValueOp : EmitC_Op<"lvalue_to_rvalue", [
+  TypesMatchWith<"result type matches value type of 'operand'",
+                  "operand", "result",
+                  "::llvm::cast<LValueType>($_self).getValue()">
+]> {
+  let summary = "lvalue to rvalue conversion operation";
+  let description = [{}];
+
+  let arguments = (ins EmitC_LValueType:$operand);
+  let results = (outs AnyType:$result);
+
+  let assemblyFormat = "$operand attr-dict `:` type($operand)"; 
+
+  let hasVerifier = 1;
+}
+
 def EmitC_MulOp : EmitC_BinaryOp<"mul", [CExpression]> {
   let summary = "Multiplication operation";
   let description = [{
@@ -1009,7 +1025,7 @@ def EmitC_VariableOp : EmitC_Op<"variable", []> {
   }];
 
   let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
-  let results = (outs EmitCType);
+  let results = (outs EmitC_LValueType);
 
   let hasVerifier = 1;
 }
@@ -1137,7 +1153,7 @@ def EmitC_AssignOp : EmitC_Op<"assign", []> {
     ```
   }];
 
-  let arguments = (ins EmitCType:$var, EmitCType:$value);
+  let arguments = (ins EmitC_LValueType:$var, EmitCType:$value);
   let results = (outs);
 
   let hasVerifier = 1;
@@ -1243,7 +1259,7 @@ def EmitC_SubscriptOp : EmitC_Op<"subscript", []> {
       EmitC_PointerType]>,
     "the value to subscript">:$value,
     Variadic<EmitCType>:$indices);
-  let results = (outs EmitCType:$result);
+  let results = (outs EmitC_LValueType:$result);
 
   let builders = [
     OpBuilder<(ins "TypedValue<ArrayType>":$array, "ValueRange":$indices), [{
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
index 444395b915e25..941af42dba3b5 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
@@ -83,6 +83,23 @@ def EmitC_ArrayType : EmitC_Type<"Array", "array", [ShapedTypeInterface]> {
   let hasCustomAssemblyFormat = 1;
 }
 
+def EmitC_LValueType : EmitC_Type<"LValue", "lvalue"> {
+  let summary = "EmitC lvalue type";
+
+  let description = [{
+    Values of this type can be assigned to and their address can be taken.
+  }];
+
+  let parameters = (ins "Type":$value);
+  let builders = [
+    TypeBuilderWithInferredContext<(ins "Type":$value), [{
+      return $_get(value.getContext(), value);
+    }]>
+  ];
+  let assemblyFormat = "`<` qualified($value) `>`";
+  let genVerifyDecl = 1;
+}
+
 def EmitC_OpaqueType : EmitC_Type<"Opaque", "opaque"> {
   let summary = "EmitC opaque type";
 
diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index 367142a520742..7ed84a96057c0 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -63,9 +63,10 @@ static SmallVector<Value> createVariablesForResults(T op,
 
   for (OpResult result : op.getResults()) {
     Type resultType = result.getType();
+    Type varType = emitc::LValueType::get(resultType);
     emitc::OpaqueAttr noInit = emitc::OpaqueAttr::get(context, "");
     emitc::VariableOp var =
-        rewriter.create<emitc::VariableOp>(loc, resultType, noInit);
+        rewriter.create<emitc::VariableOp>(loc, varType, noInit);
     resultVariables.push_back(var);
   }
 
@@ -76,57 +77,98 @@ static SmallVector<Value> createVariablesForResults(T op,
 // the current insertion point of given rewriter.
 static void assignValues(ValueRange values, SmallVector<Value> &variables,
                          PatternRewriter &rewriter, Location loc) {
-  for (auto [value, var] : llvm::zip(values, variables))
-    rewriter.create<emitc::AssignOp>(loc, var, value);
+  for (auto [value, var] : llvm::zip(values, variables)) {
+    assert(isa<emitc::LValueType>(var.getType()) &&
+           "expected var to be an lvalue type");
+    assert(!isa<emitc::LValueType>(value.getType()) &&
+           "expected value to not be an lvalue type");
+    auto assign = rewriter.create<emitc::AssignOp>(loc, var, value);
+
+    // TODO: Make sure this is safe, as this moves operations with memory
+    // effects.
+    if (auto op = dyn_cast_if_present<emitc::LValueToRValueOp>(
+            value.getDefiningOp())) {
+      rewriter.moveOpBefore(op, assign);
+    }
+  }
 }
 
-static void lowerYield(SmallVector<Value> &resultVariables,
-                       PatternRewriter &rewriter, scf::YieldOp yield) {
+static void lowerYield(SmallVector<Value> &variables, PatternRewriter &rewriter,
+                       scf::YieldOp yield) {
   Location loc = yield.getLoc();
   ValueRange operands = yield.getOperands();
 
   OpBuilder::InsertionGuard guard(rewriter);
   rewriter.setInsertionPoint(yield);
 
-  assignValues(operands, resultVariables, rewriter, loc);
+  assignValues(operands, variables, rewriter, loc);
 
   rewriter.create<emitc::YieldOp>(loc);
   rewriter.eraseOp(yield);
 }
 
+static void replaceUsers(PatternRewriter &rewriter,
+                         SmallVector<Value> fromValues,
+                         SmallVector<Value> toValues) {
+  OpBuilder::InsertionGuard guard(rewriter);
+  for (auto [from, to] : llvm::zip(fromValues, toValues)) {
+    assert(from.getType() == cast<emitc::LValueType>(to.getType()).getValue() &&
+           "expected types to match");
+
+    for (OpOperand &operand : llvm::make_early_inc_range(from.getUses())) {
+      Operation *op = operand.getOwner();
+      // Skip yield ops, as these get rewritten anyways.
+      if (isa<scf::YieldOp>(op)) {
+        continue;
+      }
+      Location loc = op->getLoc();
+
+      rewriter.setInsertionPoint(op);
+      Value rValue =
+          rewriter.create<emitc::LValueToRValueOp>(loc, from.getType(), to);
+      operand.set(rValue);
+    }
+  }
+}
+
 LogicalResult ForLowering::matchAndRewrite(ForOp forOp,
                                            PatternRewriter &rewriter) const {
   Location loc = forOp.getLoc();
 
-  // Create an emitc::variable op for each result. These variables will be
-  // assigned to by emitc::assign ops within the loop body.
-  SmallVector<Value> resultVariables =
-      createVariablesForResults(forOp, rewriter);
-  SmallVector<Value> iterArgsVariables =
-      createVariablesForResults(forOp, rewriter);
+  // Create an emitc::variable op for each result. These variables will be used
+  // for the results of the operations as well as the iter_args. They are
+  // assigned to by emitc::assign ops before the loop and at the end of the loop
+  // body.
+  SmallVector<Value> variables = createVariablesForResults(forOp, rewriter);
 
-  assignValues(forOp.getInits(), iterArgsVariables, rewriter, loc);
+  // Assign initial values to the iter arg variables.
+  assignValues(forOp.getInits(), variables, rewriter, loc);
 
-  emitc::ForOp loweredFor = rewriter.create<emitc::ForOp>(
-      loc, forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep());
+  // Replace users of the iter args with variables.
+  SmallVector<Value> iterArgs;
+  for (BlockArgument arg : forOp.getRegionIterArgs()) {
+    iterArgs.push_back(arg);
+  }
 
-  Block *loweredBody = loweredFor.getBody();
+  replaceUsers(rewriter, iterArgs, variables);
 
-  // Erase the auto-generated terminator for the lowered for op.
-  rewriter.eraseOp(loweredBody->getTerminator());
+  emitc::ForOp loweredFor = rewriter.create<emitc::ForOp>(
+      loc, forOp.getLowerBound(), forOp.getUpperBound(), forOp.getStep());
+  rewriter.eraseBlock(loweredFor.getBody());
 
-  SmallVector<Value> replacingValues;
-  replacingValues.push_back(loweredFor.getInductionVar());
-  replacingValues.append(iterArgsVariables.begin(), iterArgsVariables.end());
+  rewriter.inlineRegionBefore(forOp.getRegion(), loweredFor.getRegion(),
+                              loweredFor.getRegion().end());
+  Operation *terminator = loweredFor.getRegion().back().getTerminator();
+  lowerYield(variables, rewriter, cast<scf::YieldOp>(terminator));
 
-  rewriter.mergeBlocks(forOp.getBody(), loweredBody, replacingValues);
-  lowerYield(iterArgsVariables, rewriter,
-             cast<scf::YieldOp>(loweredBody->getTerminator()));
+  // Erase block arguments for iter_args.
+  loweredFor.getRegion().back().eraseArguments(1, variables.size());
 
-  // Copy iterArgs into results after the for loop.
-  assignValues(iterArgsVariables, resultVariables, rewriter, loc);
+  // Replace all users of the results with lazily created lvalue-to-rvalue
+  // ops.
+  replaceUsers(rewriter, forOp.getResults(), variables);
 
-  rewriter.replaceOp(forOp, resultVariables);
+  rewriter.eraseOp(forOp);
   return success();
 }
 
@@ -167,7 +209,7 @@ LogicalResult IfLowering::matchAndRewrite(IfOp ifOp,
 
   bool hasElseBlock = !elseRegion.empty();
 
-  auto loweredIf =
+  emitc::IfOp loweredIf =
       rewriter.create<emitc::IfOp>(loc, ifOp.getCondition(), false, false);
 
   Region &loweredThenRegion = loweredIf.getThenRegion();
@@ -178,7 +220,11 @@ LogicalResult IfLowering::matchAndRewrite(IfOp ifOp,
     lowerRegion(elseRegion, loweredElseRegion);
   }
 
-  rewriter.replaceOp(ifOp, resultVariables);
+  // Replace all users of the results with lazily created lvalue-to-rvalue
+  // ops.
+  replaceUsers(rewriter, ifOp.getResults(), resultVariables);
+
+  rewriter.eraseOp(ifOp);
   return success();
 }
 
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index ef7b7a19489d4..b8d3141d2c868 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -140,6 +140,8 @@ static LogicalResult verifyInitializationAttribute(Operation *op,
            << "string attributes are not supported, use #emitc.opaque instead";
 
   Type resultType = op->getResult(0).getType();
+  if (auto lType = dyn_cast<LValueType>(resultType))
+    resultType = lType.getValue();
   Type attrType = cast<TypedAttr>(value).getType();
 
   if (resultType != attrType)
@@ -203,18 +205,21 @@ LogicalResult ApplyOp::verify() {
 /// assigned-to variable type.
 LogicalResult emitc::AssignOp::verify() {
   Value variable = getVar();
-  Operation *variableDef = variable.getDefiningOp();
-  if (!variableDef ||
-      !llvm::isa<emitc::VariableOp, emitc::SubscriptOp>(variableDef))
+
+  if (!variable.getDefiningOp())
+    return emitOpError() << "cannot assign to block argument";
+  if (!llvm::isa<emitc::LValueType>(variable.getType()))
     return emitOpError() << "requires first operand (" << variable
-                         << ") to be a Variable or subscript";
-
-  Value value = getValue();
-  if (variable.getType() != value.getType())
-    return emitOpError() << "requires value's type (" << value.getType()
-                         << ") to match variable's type (" << variable.getType()
-                         << ")";
-  if (isa<ArrayType>(variable.getType()))
+                         << ") to be an lvalue";
+
+  Type valueType = getValue().getType();
+  Type variableType = variable.getType().cast<emitc::LValueType>().getValue();
+  if (variableType != valueType)
+    return emitOpError() << "requires value's type (" << valueType
+                         << ") to match variable's type (" << variableType
+                         << ")\n  variable: " << variable
+                         << "\n  value: " << getValue() << "\n";
+  if (isa<ArrayType>(variableType))
     return emitOpError() << "cannot assign to array type";
   return success();
 }
@@ -769,6 +774,47 @@ LogicalResult emitc::LiteralOp::verify() {
     return emitOpError() << "value must not be empty";
   return success();
 }
+
+//===----------------------------------------------------------------------===//
+// LValueToRValueOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult emitc::LValueToRValueOp::verify() {
+  Type operandType = getOperand().getType();
+  Type resultType = getResult().getType();
+  if (!llvm::isa<emitc::LValueType>(operandType))
+    return emitOpError("operand must be a lvalue");
+  if (llvm::cast<emitc::LValueType>(operandType).getValue() != resultType)
+    return emitOpError("types must match");
+
+  Value result = getResult();
+  if (!result.hasOneUse()) {
+    int numUses = std::distance(result.use_begin(), result.use_end());
+    return emitOpError("must have exactly one use, but got ") << numUses;
+  }
+
+  Block *block = result.getParentBlock();
+
+  Operation *user = *result.getUsers().begin();
+  Block *userBlock = user->getBlock();
+
+  if (block != userBlock) {
+    return emitOpError("user must be in the same block");
+  }
+
+  // for (auto it = block.begin(), e = std::prev(block.end()); it != e; it++) {
+  //   if (*it == this)
+  // }
+
+  // TODO: To model this op correctly as a memory read of the lvalue, we
+  // should additionally ensure that the single use of the op follows immediatly
+  // on this definition. Alternativly we could alter emitc ops to implicitly
+  // support lvalues. This would make it harder to do partial conversions and
+  // mix dialects though.
+
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // SubOp
 //===----------------------------------------------------------------------===//
@@ -964,6 +1010,20 @@ emitc::ArrayType::cloneWith(std::optional<ArrayRef<int64_t>> shape,
   return emitc::ArrayType::get(*shape, elementType);
 }
 
+//===----------------------------------------------------------------------===//
+// LValueType
+//===----------------------------------------------------------------------===//
+
+LogicalResult mlir::emitc::LValueType::verify(
+    llvm::function_ref<mlir::InFlightDiagnostic()> emitError,
+    mlir::Type value) {
+  if (llvm::isa<emitc::LValueType>(value)) {
+    return emitError()
+           << "!emitc.lvalue type cannot be nested inside another type";
+  }
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // OpaqueType
 //===----------------------------------------------------------------------===//
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 7db7163bac4ab..0fdf82169dd48 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -377,6 +377,13 @@ static LogicalResult printOperation(CppEmitter &emitter,
   return success();
 }
 
+static LogicalResult printOperation(CppEmitter &emitter,
+                                    emitc::LValueToRValueOp lValueToRValueOp) {
+  // Add name to cache so that `hasValueInScope` works.
+  emitter.getOrCreateName(lValueToRValueOp.getResult());
+  return success();
+}
+
 static LogicalResult printOperation(CppEmitter &emitter,
                                     emitc::SubscriptOp subscriptOp) {
   // Add name to cache so that `hasValueInScope` works.
@@ -661,10 +668,16 @@ static LogicalResult printOperation(CppEmitter &emitter,
   raw_ostream &os = emitter.ostream();
   Operation &op = *applyOp.getOperation();
 
-  if (failed(emitter.emitAssignPrefix(op)))
-    return failure();
-  os << applyOp.getApplicableOperator();
-  os << emitter.getOrCreateName(applyOp.getOperand());
+  if (applyOp.getApplicableOperator() == "&") {
+
+    if (failed(emitter.emitAssignPrefix(op)))
+      return failure();
+    os << applyOp.getApplicableOperator();
+    os << emitter.getOrCreateName(applyOp.getOperand());
+  } else {
+    // Add name to cache so that `hasValueInScope` works.
+    emitter.getOrCreateName(applyOp.getResult());
+  }
 
   return success();
 }
@@ -948,7 +961,9 @@ static LogicalResult printFunctionBody(CppEmitter &emitter,
     // regions.
     WalkResult result =
         functionOp->walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
-          if (isa<emitc::LiteralOp>(op) ||
+          if (isa<emitc::LiteralOp, emitc::LValueToRValueOp>(op) ||
+              (isa<emitc::ApplyOp>(op) &&
+               cast<emitc::ApplyOp>(op).getApplicableOperator() == "*") ||
               isa<emitc::ExpressionOp>(op->getParentOp()) ||
               (isa<emitc::ExpressionOp>(op) &&
                shouldBeInlined(cast<emitc::ExpressionOp>(op))))
@@ -1138,6 +1153,15 @@ std::string CppEmitter::getSubscriptName(emitc::SubscriptOp op) {
 StringRef CppEmitter::getOrCreateName(Value val) {
   if (auto literal = dyn_cast_if_present<emitc::LiteralOp>(val.getDefiningOp()))
     return literal.getValue();
+  if (auto lValueToRValue =
+          dyn_cast_if_present<emitc::LValueToRValueOp>(val.getDefiningOp())) {
+    Value operand = lValueToRValue.getOperand();
+    valueMapper.insert(val, "***UNUSED***");
+
+    assert(hasValueInScope(operand));
+
+    return getOrCreateName(operand);
+  }
   if (!valueMapper.count(val)) {
     if (auto subscript =
             dyn_cast_if_present<emitc::SubscriptOp>(val.getDefiningOp())) {
@@ -1145,6 +1169,15 @@ StringRef CppEmitter::getOrCreateName(Value val) {
     } else if (auto getGlobal = dyn_cast_if_present<emitc::GetGlobalOp>(
                    val.getDefiningOp())) {
       valueMapper.insert(val, getGlobal.getName().str());
+    } else if (val.getDefiningOp() &&
+               isa<emitc::ApplyOp>(val.getDefiningOp()) &&
+               cast<emitc::ApplyOp>(val.getDefiningOp())
+                       .getApplicableOperator() == "*") {
+      auto apply = cast<emitc::ApplyOp>(val.getDefiningOp());
+
+      Value operand = apply.getOperand();
+      std::string variable = getOrCreateName(operand).str();
+      valueMapper.insert(val, std::string("*") + variable);
     } else {
       valueMapper.insert(val, formatv("v{0}", ++valueInScopeCount.top()));
     }
@@ -1401,8 +1434,12 @@ LogicalResult CppEmitter::emitVariableDeclaration(OpResult result,
     return result.getDefiningOp()->emitError(
         "result variable for the operation already declared");
   }
-  if (failed(emitVariableDeclaration(result.getOwner()->getLoc(),
-                                     result.getType(),
+
+  Type type = result.getType();
+  if (auto lType = dyn_cast<emitc::LValueType>(type)) {
+    type = lType.getValue();
+  }
+  if (failed(emitVariableDeclaration(result.getOwner()->getLoc(), type,
                                      getOrCreateName(result))))
     return failure();
   if (trailingSemicolon)
@@ -1495,8 +1532,9 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::ConditionalOp, emitc::ConstantOp, emitc::DeclareFuncOp,
                 emitc::DivOp, emitc::ExpressionOp, emitc::ForOp, emitc::FuncOp,
                 emitc::GlobalOp, emitc::GetGlobalOp, emitc::IfOp,
-                emitc::IncludeOp, emitc::LogicalAndOp, emitc::LogicalNotOp,
-                emitc::LogicalOrOp, emitc::MulOp, emitc::RemOp, emitc::ReturnOp,
+                emitc::IncludeOp, emitc::LogicalAndOp,
+                emitc::LValueToRValueOp, emitc::LogicalNotOp, emitc::RemOp,
+                emitc::LogicalOrOp, emitc::MulOp, emitc::ReturnOp,
                 emitc::SubOp, emitc::SubscriptOp, emitc::UnaryMinusOp,
                 emitc::UnaryPlusOp, emitc::VariableOp, emitc::VerbatimOp>(
               [&](auto op) { return printOperation(*this, op); })
@@ -1511,7 +1549,7 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
   if (failed(status))
     return failure();
 
-  if (isa<emitc::LiteralOp, emitc::SubscriptOp, emitc::GetGlobalOp>(op))
+  if (isa<emitc::LiteralOp, emitc::LValueToRValueOp, emitc::SubscriptOp, emitc::GetGlobalOp>(op))
     return success();
 
   if (getEmittedExpression() ||
@@ -1601,6 +1639,9 @@ LogicalResult CppEmitter::emitType(Location loc, Type type) {
       os << "[" << dim << "]";
     return success();
   }
+  if (auto lType = dyn_cast<emitc::LValueType>(type)) {
+    return emitType(loc, lType.getValue());
+  }
   if (auto pType = dyn_cast<emitc::PointerType>(type)) {
     if (isa<ArrayType>(pType.getPointee()))
       return emitError(loc, "cannot emit pointer to array type ") << type;
diff --git a/mlir/test/Conversion/SCFToEmitC/for.mlir b/mlir/test/Conversion/SCFToEmitC/for.mlir
index 7f90310af2189..22c60c4e50df9 100644
--- a/mlir/test/Conversion/SCFToEmitC/for.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/for.mlir
@@ -47,20 +47,20 @@ func.func @for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> (f32, f32)
 // CHECK-SAME:      %[[VAL_0:.*]]: index, %[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index) -> (f32, f32) {
 // CHECK-NEXT:    %[[VAL_3:.*]] = arith.constant 0.000000e+00 : f32
 // CHECK-NEXT:    %[[VAL_4:.*]] = arith.constant 1.000000e+00 : f32
-// CHECK-NEXT:    %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:    %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:    %[[VAL_7:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:    %[[VAL_8:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:    emitc.assign %[[VAL_3]] : f32 to %[[VAL_7]] : f32
-// CHECK-NEXT:    emitc.assign %[[VAL_4]] : f32 to %[[VAL_8]] : f32
-// CHECK-NEXT:    emitc.for %[[VAL_9:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
-// CHECK-NEXT:      %[[VAL_10:.*]] = arith.addf %[[VAL_7]], %[[VAL_8]] : f32
-// CHECK-NEXT:      emitc.assign %[[VAL_10]] : f32 to %[[VAL_7]] : f32
-// CHECK-NEXT:      emitc.assign %[[VAL_10]] : f32 to %[[VAL_8]] : f32
+// CHECK-NEXT:    %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+// CHECK-NEXT:    %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+// CHECK-NEXT:    emitc.assign %[[VAL_3]] : f32 to %[[VAL_5]] : <f32>
+// CHECK-NEXT:    emitc.assign %[[VAL_4]] : f32 to %[[VAL_6]] : <f32>
+// CHECK-NEXT:    emitc.for %[[VAL_7:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
+// CHECK-NEXT:      %[[VAL_8:.*]] = emitc.lvalue_to_rvalue %[[VAL_5]] : <f32>
+// CHECK-NEXT:      %[[VAL_9:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>
+// CHECK-NEXT:      %[[VAL_10:.*]] = arith.addf %[[VAL_8]], %[[VAL_9]] : f32
+// CHECK-NEXT:      emitc.assign %[[VAL_10]] : f32 to %[[VAL_5]] : <f32>
+// CHECK-NEXT:      emitc.assign %[[VAL_10]] : f32 to %[[VAL_6]] : <f32>
 // CHECK-NEXT:    }
-// CHECK-NEXT:    emitc.assign %[[VAL_7]] : f32 to %[[VAL_5]] : f32
-// CHECK-NEXT:    emitc.assign %[[VAL_8]] : f32 to %[[VAL_6]] : f32
-// CHECK-NEXT:    return %[[VAL_5]], %[[VAL_6]] : f32, f32
+// CHECK-NEXT:    %[[VAL_11:.*]] = emitc.lvalue_to_rvalue %[[VAL_5]] : <f32>
+// CHECK-NEXT:    %[[VAL_12:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>
+// CHECK-NEXT:    return %[[VAL_11]], %[[VAL_12]] : f32, f32
 // CHECK-NEXT:  }
 
 func.func @nested_for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> f32 {
@@ -77,20 +77,21 @@ func.func @nested_for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> f32
 // CHECK-LABEL: func.func @nested_for_yield(
 // CHECK-SAME:      %[[VAL_0:.*]]: index, %[[VAL_1:.*]]: index, %[[VAL_2:.*]]: index) -> f32 {
 // CHECK-NEXT:    %[[VAL_3:.*]] = arith.constant 1.000000e+00 : f32
-// CHECK-NEXT:    %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:    %[[VAL_5:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:    emitc.assign %[[VAL_3]] : f32 to %[[VAL_5]] : f32
-// CHECK-NEXT:    emitc.for %[[VAL_6:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
-// CHECK-NEXT:      %[[VAL_7:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:      %[[VAL_8:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-// CHECK-NEXT:      emitc.assign %[[VAL_5]] : f32 to %[[VAL_8]] : f32
-// CHECK-NEXT:      emitc.for %[[VAL_9:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
-// CHECK-NEXT:        %[[VAL_10:.*]] = arith.addf %[[VAL_8]], %[[VAL_8]] : f32
-// CHECK-NEXT:        emitc.assign %[[VAL_10]] : f32 to %[[VAL_8]] : f32
+// CHECK-NEXT:    %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+// CHECK-NEXT:    emitc.assign %[[VAL_3]] : f32 to %[[VAL_4]] : <f32>
+// CHECK-NEXT:    emitc.for %[[VAL_5:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
+// CHECK-NEXT:      %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+// CHECK-NEXT:      %[[VAL_7:.*]] = emitc.lvalue_to_rvalue %[[VAL_4]] : <f32>
+// CHECK-NEXT:      emitc.assign %[[VAL_7]] : f32 to %[[VAL_6]] : <f32>
+// CHECK-NEXT:      emitc.for %[[VAL_8:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
+// CHECK-NEXT:        %[[VAL_9:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>  
+// CHECK-NEXT:        %[[VAL_10:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>  
+// CHECK-NEXT:        %[[VAL_11:.*]] = arith.addf %[[VAL_10]], %[[VAL_9]] : f32
+// CHECK-NEXT:        emitc.assign %[[VAL_11]] : f32 to %[[VAL_6]] : <f32>
 // CHECK-NEXT:      }
-// CHECK-NEXT:      emitc.assign %[[VAL_8]] : f32 to %[[VAL_7]] : f32
-// CHECK-NEXT:      emitc.assign %[[VAL_7]] : f32 to %[[VAL_5]] : f32
+// CHECK-NEXT:      %[[VAL_12:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>  
+// CHECK-NEXT:      emitc.assign %[[VAL_12]] : f32 to %[[VAL_4]] : <f32>
 // CHECK-NEXT:    }
-// CHECK-NEXT:    emitc.assign %[[VAL_5]] : f32 to %[[VAL_4]] : f32
-// CHECK-NEXT:    return %[[VAL_4]] : f32
+// CHECK-NEXT:    %[[VAL_13:.*]] = emitc.lvalue_to_rvalue %[[VAL_4]] : <f32>  
+// CHECK-NEXT:    return %[[VAL_13]] : f32
 // CHECK-NEXT:  }
diff --git a/mlir/test/Conversion/SCFToEmitC/if.mlir b/mlir/test/Conversion/SCFToEmitC/if.mlir
index afc9abc761eb4..330edb00b38ce 100644
--- a/mlir/test/Conversion/SCFToEmitC/if.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/if.mlir
@@ -36,7 +36,7 @@ func.func @test_if_else(%arg0: i1, %arg1: f32) {
 // CHECK-NEXT:  }
 
 
-func.func @test_if_yield(%arg0: i1, %arg1: f32) {
+func.func @test_if_yield(%arg0: i1, %arg1: f32) -> (i32, f64) {
   %0 = arith.constant 0 : i8
   %x, %y = scf.if %arg0 -> (i32, f64) {
     %1 = emitc.call_opaque "func_true_1"(%arg1) : (f32) -> i32
@@ -47,24 +47,26 @@ func.func @test_if_yield(%arg0: i1, %arg1: f32) {
     %2 = emitc.call_opaque "func_false_2"(%arg1) : (f32) -> f64
     scf.yield %1, %2 : i32, f64
   }
-  return
+  return %x, %y : i32, f64
 }
 // CHECK-LABEL: func.func @test_if_yield(
 // CHECK-SAME:                           %[[VAL_0:.*]]: i1,
-// CHECK-SAME:                           %[[VAL_1:.*]]: f32) {
+// CHECK-SAME:                           %[[VAL_1:.*]]: f32) -> (i32, f64) {
 // CHECK-NEXT:    %[[VAL_2:.*]] = arith.constant 0 : i8
-// CHECK-NEXT:    %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
-// CHECK-NEXT:    %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f64
+// CHECK-NEXT:    %[[VAL_3:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+// CHECK-NEXT:    %[[VAL_4:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f64>
 // CHECK-NEXT:    emitc.if %[[VAL_0]] {
 // CHECK-NEXT:      %[[VAL_5:.*]] = emitc.call_opaque "func_true_1"(%[[VAL_1]]) : (f32) -> i32
 // CHECK-NEXT:      %[[VAL_6:.*]] = emitc.call_opaque "func_true_2"(%[[VAL_1]]) : (f32) -> f64
-// CHECK-NEXT:      emitc.assign %[[VAL_5]] : i32 to %[[VAL_3]] : i32
-// CHECK-NEXT:      emitc.assign %[[VAL_6]] : f64 to %[[VAL_4]] : f64
+// CHECK-NEXT:      emitc.assign %[[VAL_5]] : i32 to %[[VAL_3]] : <i32>
+// CHECK-NEXT:      emitc.assign %[[VAL_6]] : f64 to %[[VAL_4]] : <f64>
 // CHECK-NEXT:    } else {
 // CHECK-NEXT:      %[[VAL_7:.*]] = emitc.call_opaque "func_false_1"(%[[VAL_1]]) : (f32) -> i32
 // CHECK-NEXT:      %[[VAL_8:.*]] = emitc.call_opaque "func_false_2"(%[[VAL_1]]) : (f32) -> f64
-// CHECK-NEXT:      emitc.assign %[[VAL_7]] : i32 to %[[VAL_3]] : i32
-// CHECK-NEXT:      emitc.assign %[[VAL_8]] : f64 to %[[VAL_4]] : f64
+// CHECK-NEXT:      emitc.assign %[[VAL_7]] : i32 to %[[VAL_3]] : <i32>
+// CHECK-NEXT:      emitc.assign %[[VAL_8]] : f64 to %[[VAL_4]] : <f64>
 // CHECK-NEXT:    }
-// CHECK-NEXT:    return
+// CHECK-NEXT:    %[[VAL_9:.*]] = emitc.lvalue_to_rvalue %[[VAL_3]] : <i32>
+// CHECK-NEXT:    %[[VAL_10:.*]] = emitc.lvalue_to_rvalue %[[VAL_4]] : <f64>
+// CHECK-NEXT:    return %[[VAL_9]], %[[VAL_10]] : i32, f64
 // CHECK-NEXT:  }
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 21ea6a5df91b9..302d6de3327ab 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -115,7 +115,7 @@ func.func @illegal_operand() {
 
 func.func @var_attribute_return_type_1() {
     // expected-error @+1 {{'emitc.variable' op requires attribute to either be an #emitc.opaque attribute or it's type ('i64') to match the op's result type ('i32')}}
-    %c0 = "emitc.variable"(){value = 42: i64} : () -> i32
+    %c0 = "emitc.variable"(){value = 42: i64} : () -> !emitc.lvalue<i32>
     return
 }
 
@@ -123,7 +123,7 @@ func.func @var_attribute_return_type_1() {
 
 func.func @var_attribute_return_type_2() {
     // expected-error @+1 {{'emitc.variable' op attribute 'value' failed to satisfy constraint: An opaque attribute or TypedAttr instance}}
-    %c0 = "emitc.variable"(){value = unit} : () -> i32
+    %c0 = "emitc.variable"(){value = unit} : () -> !emitc.lvalue<i32>
     return
 }
 
@@ -234,27 +234,27 @@ func.func @test_misplaced_yield() {
 
 // -----
 
-func.func @test_assign_to_non_variable(%arg1: f32, %arg2: f32) {
-  // expected-error @+1 {{'emitc.assign' op requires first operand (<block argument> of type 'f32' at index: 1) to be a Variable or subscript}}
-  emitc.assign %arg1 : f32 to %arg2 : f32
+func.func @test_assign_to_non_variable(%arg1: f32, %arg2: !emitc.lvalue<f32>) {
+  // expected-error @+1 {{'emitc.assign' op cannot assign to block argument}}
+  emitc.assign %arg1 : f32 to %arg2 : !emitc.lvalue<f32>
   return
 }
 
 // -----
 
 func.func @test_assign_type_mismatch(%arg1: f32) {
-  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
+  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
   // expected-error @+1 {{'emitc.assign' op requires value's type ('f32') to match variable's type ('i32')}}
-  emitc.assign %arg1 : f32 to %v : i32
+  emitc.assign %arg1 : f32 to %v : !emitc.lvalue<i32>
   return
 }
 
 // -----
 
 func.func @test_assign_to_array(%arg1: !emitc.array<4xi32>) {
-  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4xi32>
+  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.array<4xi32>>
   // expected-error @+1 {{'emitc.assign' op cannot assign to array type}}
-  emitc.assign %arg1 : !emitc.array<4xi32> to %v : !emitc.array<4xi32>
+  emitc.assign %arg1 : !emitc.array<4xi32> to %v : !emitc.lvalue<!emitc.array<4xi32>>
   return
 }
 
diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index 05510e6dddbf5..930624fc2e258 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -170,8 +170,8 @@ func.func @test_if_else(%arg0: i1, %arg1: f32) {
 }
 
 func.func @test_assign(%arg1: f32) {
-  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-  emitc.assign %arg1 : f32 to %v : f32
+  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+  emitc.assign %arg1 : f32 to %v : !emitc.lvalue<f32>
   return
 }
 
diff --git a/mlir/test/Target/Cpp/common-cpp.mlir b/mlir/test/Target/Cpp/common-cpp.mlir
index 0e24bdd19993f..3876c2297b7a0 100644
--- a/mlir/test/Target/Cpp/common-cpp.mlir
+++ b/mlir/test/Target/Cpp/common-cpp.mlir
@@ -85,8 +85,12 @@ func.func @opaque_types(%arg0: !emitc.opaque<"bool">, %arg1: !emitc.opaque<"char
 func.func @apply(%arg0: i32) -> !emitc.ptr<i32> {
   // CHECK: int32_t* [[V2]] = &[[V1]];
   %0 = emitc.apply "&"(%arg0) : (i32) -> !emitc.ptr<i32>
-  // CHECK: int32_t [[V3]] = *[[V2]];
-  %1 = emitc.apply "*"(%0) : (!emitc.ptr<i32>) -> (i32)
+  // CHECK: int32_t [[V3]];
+  // CHECK: [[V3]] = *[[V2]];
+  %1 = emitc.apply "*"(%0) : (!emitc.ptr<i32>) -> (!emitc.lvalue<i32>)
+  %2 = "emitc.variable"() {value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
+  %3 = emitc.lvalue_to_rvalue %1 : !emitc.lvalue<i32>
+  emitc.assign %3 : i32 to %2 : !emitc.lvalue<i32>
   return %0 : !emitc.ptr<i32>
 }
 
diff --git a/mlir/test/Target/Cpp/expressions.mlir b/mlir/test/Target/Cpp/expressions.mlir
index 2eda58902cb1d..e2418fe628f78 100644
--- a/mlir/test/Target/Cpp/expressions.mlir
+++ b/mlir/test/Target/Cpp/expressions.mlir
@@ -34,15 +34,16 @@ func.func @single_use(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32 {
     %d = emitc.cmp lt, %c, %arg1 :(i32, i32) -> i1
     emitc.yield %d : i1
   }
-  %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> i32
+  %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
   emitc.if %e {
-    emitc.assign %arg0 : i32 to %v : i32
+    emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   } else {
-    emitc.assign %arg0 : i32 to %v : i32
+    emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   }
-  return %v : i32
+  %v_rvalue = emitc.lvalue_to_rvalue %v : !emitc.lvalue<i32>
+  return %v_rvalue : i32
 }
 
 // CPP-DEFAULT: int32_t do_not_inline(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]]) {
@@ -120,17 +121,19 @@ func.func @multiple_uses(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32
     %d = emitc.cmp lt, %c, %arg1 :(i32, i32) -> i1
     emitc.yield %d : i1
   }
-  %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> i32
+  %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
   emitc.if %e {
-    emitc.assign %arg0 : i32 to %v : i32
+    emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   } else {
-    emitc.assign %arg0 : i32 to %v : i32
+    emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   }
-  %q = "emitc.variable"(){value = #emitc.opaque<"">} : () -> i1
-  emitc.assign %e : i1 to %q : i1
-  return %v : i32
+  %q = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i1>
+  emitc.assign %e : i1 to %q : !emitc.lvalue<i1>
+
+  %v_rvalue = emitc.lvalue_to_rvalue %v : !emitc.lvalue<i32>
+  return %v_rvalue : i32
 }
 
 // CPP-DEFAULT:      int32_t different_expressions(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]], int32_t [[VAL_4:v[0-9]+]]) {
@@ -175,15 +178,17 @@ func.func @different_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32)
     %d = emitc.cmp lt, %c, %arg1 :(i32, i32) -> i1
     emitc.yield %d : i1
   }
-  %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> i32
+  %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
   emitc.if %e3 {
-    emitc.assign %arg0 : i32 to %v : i32
+    emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   } else {
-    emitc.assign %arg0 : i32 to %v : i32
+    emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   }
-  return %v : i32
+
+  %v_rvalue = emitc.lvalue_to_rvalue %v : !emitc.lvalue<i32>
+  return %v_rvalue : i32
 }
 
 // CPP-DEFAULT:      bool expression_with_address_taken(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t* [[VAL_3]]) {
diff --git a/mlir/test/Target/Cpp/for.mlir b/mlir/test/Target/Cpp/for.mlir
index 60988bcb46556..6ca29b165cdee 100644
--- a/mlir/test/Target/Cpp/for.mlir
+++ b/mlir/test/Target/Cpp/for.mlir
@@ -40,21 +40,25 @@ func.func @test_for_yield() {
   %s0 = "emitc.constant"() <{value = 0 : i32}> : () -> i32
   %p0 = "emitc.constant"() <{value = 1.0 : f32}> : () -> f32
 
-  %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
-  %1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-  %2 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
-  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-  emitc.assign %s0 : i32 to %2 : i32
-  emitc.assign %p0 : f32 to %3 : f32
+  %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  %1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+  %2 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+  emitc.assign %s0 : i32 to %2 : !emitc.lvalue<i32>
+  emitc.assign %p0 : f32 to %3 : !emitc.lvalue<f32>
   emitc.for %iter = %start to %stop step %step {
-    %sn = emitc.call_opaque "add"(%2, %iter) : (i32, index) -> i32
-    %pn = emitc.call_opaque "mul"(%3, %iter) : (f32, index) -> f32
-    emitc.assign %sn : i32 to %2 : i32
-    emitc.assign %pn : f32 to %3 : f32
+    %4 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+    %sn = emitc.call_opaque "add"(%4, %iter) : (i32, index) -> i32
+    %5 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+    %pn = emitc.call_opaque "mul"(%5, %iter) : (f32, index) -> f32
+    emitc.assign %sn : i32 to %2 : !emitc.lvalue<i32>
+    emitc.assign %pn : f32 to %3 : !emitc.lvalue<f32>
     emitc.yield
   }
-  emitc.assign %2 : i32 to %0 : i32
-  emitc.assign %3 : f32 to %1 : f32
+  %6 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+  emitc.assign %6 : i32 to %0 : !emitc.lvalue<i32>
+  %7 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+  emitc.assign %7 : f32 to %1 : !emitc.lvalue<f32>
 
   return
 }
@@ -121,21 +125,25 @@ func.func @test_for_yield_2() {
   %s0 = emitc.literal "0" : i32
   %p0 = emitc.literal "M_PI" : f32
 
-  %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
-  %1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-  %2 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
-  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-  emitc.assign %s0 : i32 to %2 : i32
-  emitc.assign %p0 : f32 to %3 : f32
+  %0 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  %1 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+  %2 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  %3 = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
+  emitc.assign %s0 : i32 to %2 : !emitc.lvalue<i32>
+  emitc.assign %p0 : f32 to %3 : !emitc.lvalue<f32>
   emitc.for %iter = %start to %stop step %step {
-    %sn = emitc.call_opaque "add"(%2, %iter) : (i32, index) -> i32
-    %pn = emitc.call_opaque "mul"(%3, %iter) : (f32, index) -> f32
-    emitc.assign %sn : i32 to %2 : i32
-    emitc.assign %pn : f32 to %3 : f32
+    %4 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+    %sn = emitc.call_opaque "add"(%4, %iter) : (i32, index) -> i32
+    %5 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+    %pn = emitc.call_opaque "mul"(%5, %iter) : (f32, index) -> f32
+    emitc.assign %sn : i32 to %2 : !emitc.lvalue<i32>
+    emitc.assign %pn : f32 to %3 : !emitc.lvalue<f32>
     emitc.yield
   }
-  emitc.assign %2 : i32 to %0 : i32
-  emitc.assign %3 : f32 to %1 : f32
+  %6 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+  emitc.assign %6 : i32 to %0 : !emitc.lvalue<i32>
+  %7 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+  emitc.assign %7 : f32 to %1 : !emitc.lvalue<f32>
 
   return
 }
diff --git a/mlir/test/Target/Cpp/if.mlir b/mlir/test/Target/Cpp/if.mlir
index 7b0e2da85d0eb..d3b792192c8b1 100644
--- a/mlir/test/Target/Cpp/if.mlir
+++ b/mlir/test/Target/Cpp/if.mlir
@@ -50,18 +50,18 @@ func.func @test_if_else(%arg0: i1, %arg1: f32) {
 
 func.func @test_if_yield(%arg0: i1, %arg1: f32) {
   %0 = "emitc.constant"() <{value = 0 : i8}> : () -> i8
-  %x = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> i32
-  %y = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f64
+  %x = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<i32>
+  %y = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f64>
   emitc.if %arg0 {
     %1 = emitc.call_opaque "func_true_1"(%arg1) : (f32) -> i32
     %2 = emitc.call_opaque "func_true_2"(%arg1) : (f32) -> f64
-    emitc.assign %1 : i32 to %x : i32
-    emitc.assign %2 : f64 to %y : f64
+    emitc.assign %1 : i32 to %x : !emitc.lvalue<i32>
+    emitc.assign %2 : f64 to %y : !emitc.lvalue<f64>
   } else {
     %1 = emitc.call_opaque "func_false_1"(%arg1) : (f32) -> i32
     %2 = emitc.call_opaque "func_false_2"(%arg1) : (f32) -> f64
-    emitc.assign %1 : i32 to %x : i32
-    emitc.assign %2 : f64 to %y : f64
+    emitc.assign %1 : i32 to %x : !emitc.lvalue<i32>
+    emitc.assign %2 : f64 to %y : !emitc.lvalue<f64>
   }
   return
 }
diff --git a/mlir/test/Target/Cpp/invalid.mlir b/mlir/test/Target/Cpp/invalid.mlir
index 513371a09cde1..14fd0cd56170b 100644
--- a/mlir/test/Target/Cpp/invalid.mlir
+++ b/mlir/test/Target/Cpp/invalid.mlir
@@ -82,6 +82,6 @@ func.func @array_as_result(%arg: !emitc.array<4xi8>) -> (!emitc.array<4xi8>) {
 // -----
 func.func @ptr_to_array() {
   // expected-error at +1 {{cannot emit pointer to array type '!emitc.ptr<!emitc.array<9xi16>>'}}
-  %v = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.ptr<!emitc.array<9xi16>>
+  %v = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.lvalue<!emitc.ptr<!emitc.array<9xi16>>>
   return
 }
diff --git a/mlir/test/Target/Cpp/lvalue.mlir b/mlir/test/Target/Cpp/lvalue.mlir
new file mode 100644
index 0000000000000..cd5fe1fd2f7d6
--- /dev/null
+++ b/mlir/test/Target/Cpp/lvalue.mlir
@@ -0,0 +1,22 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
+
+// CHECK: int32_t lvalue_variables(
+emitc.func @lvalue_variables(%v1: i32, %v2: i32) -> i32 {
+  %val = emitc.mul %v1, %v2 : (i32, i32) -> i32
+  %variable = "emitc.variable"() {value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32> // alloc effect
+  emitc.assign %val : i32 to %variable : !emitc.lvalue<i32> // write effect
+  %addr = emitc.apply "&"(%variable) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
+  emitc.call @zero (%addr) : (!emitc.ptr<i32>) -> ()
+  %updated_val = emitc.lvalue_to_rvalue %variable : !emitc.lvalue<i32> // read effect, (noop in emitter?)
+  %neg_one = "emitc.constant"() {value = -1 : i32} : () -> i32
+  emitc.assign %neg_one : i32 to %variable : !emitc.lvalue<i32> // invalidates %updated_val
+  emitc.return %updated_val : i32
+  // dealloc effect through automatic allocation scope
+}
+
+emitc.func @zero(%arg0: !emitc.ptr<i32>) {
+    %0 =  "emitc.constant"() {value = 0 : i32} : () -> i32
+    %1 = emitc.apply "*"(%arg0) : (!emitc.ptr<i32>) -> !emitc.lvalue<i32>
+    emitc.assign %0 : i32 to %1 : !emitc.lvalue<i32>
+    emitc.return
+}
\ No newline at end of file
diff --git a/mlir/test/Target/Cpp/subscript.mlir b/mlir/test/Target/Cpp/subscript.mlir
index 0b388953c80d3..e5dcc9dc34f00 100644
--- a/mlir/test/Target/Cpp/subscript.mlir
+++ b/mlir/test/Target/Cpp/subscript.mlir
@@ -4,7 +4,8 @@
 func.func @load_store_array(%arg0: !emitc.array<4x8xf32>, %arg1: !emitc.array<3x5xf32>, %arg2: index, %arg3: index) {
   %0 = emitc.subscript %arg0[%arg2, %arg3] : (!emitc.array<4x8xf32>, index, index) -> f32
   %1 = emitc.subscript %arg1[%arg2, %arg3] : (!emitc.array<3x5xf32>, index, index) -> f32
-  emitc.assign %0 : f32 to %1 : f32
+  %2 = emitc.lvalue_to_rvalue %0 : !emitc.lvalue<f32>
+  emitc.assign %2 : f32 to %1 : !emitc.lvalue<f32>
   return
 }
 // CHECK: void load_store_array(float [[ARR1:[^ ]*]][4][8], float [[ARR2:[^ ]*]][3][5],
@@ -40,9 +41,13 @@ emitc.func @call_arg(%arg0: !emitc.array<4x8xf32>, %i: i32, %j: i16,
   %0 = emitc.subscript %arg0[%i, %j] : (!emitc.array<4x8xf32>, i32, i16) -> f32
   %1 = emitc.subscript %arg0[%j, %k] : (!emitc.array<4x8xf32>, i16, i8) -> f32
 
-  emitc.call @func1 (%0) : (f32) -> ()
-  emitc.call_opaque "func2" (%1) : (f32) -> ()
-  emitc.call_opaque "func3" (%0, %1) { args = [1 : index, 0 : index] } : (f32, f32) -> ()
+  %2 = emitc.lvalue_to_rvalue %0 : !emitc.lvalue<f32>
+  emitc.call @func1 (%2) : (f32) -> ()
+  %3 = emitc.lvalue_to_rvalue %1 : !emitc.lvalue<f32>
+  emitc.call_opaque "func2" (%3) : (f32) -> ()
+  %4 = emitc.lvalue_to_rvalue %0 : !emitc.lvalue<f32>
+  %5 = emitc.lvalue_to_rvalue %1 : !emitc.lvalue<f32>
+  emitc.call_opaque "func3" (%4, %5) { args = [1 : index, 0 : index] } : (f32, f32) -> ()
   emitc.return
 }
 // CHECK: void call_arg(float [[ARR1:[^ ]*]][4][8], int32_t [[I:[^ ]*]],
diff --git a/mlir/test/Target/Cpp/variable.mlir b/mlir/test/Target/Cpp/variable.mlir
index 126dd384b47a2..435d47e18dff5 100644
--- a/mlir/test/Target/Cpp/variable.mlir
+++ b/mlir/test/Target/Cpp/variable.mlir
@@ -2,15 +2,15 @@
 // RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
 
 func.func @emitc_variable() {
-  %c0 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> i32
-  %c1 = "emitc.variable"(){value = 42 : i32} : () -> i32
-  %c2 = "emitc.variable"(){value = -1 : i32} : () -> i32
-  %c3 = "emitc.variable"(){value = -1 : si8} : () -> si8
-  %c4 = "emitc.variable"(){value = 255 : ui8} : () -> ui8
-  %c5 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.ptr<i32>
-  %c6 = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.ptr<i32>
-  %c7 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<3x7xi32>
-  %c8 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<5x!emitc.ptr<i8>>
+  %c0 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
+  %c1 = "emitc.variable"(){value = 42 : i32} : () -> !emitc.lvalue<i32>
+  %c2 = "emitc.variable"(){value = -1 : i32} : () -> !emitc.lvalue<i32>
+  %c3 = "emitc.variable"(){value = -1 : si8} : () -> !emitc.lvalue<si8>
+  %c4 = "emitc.variable"(){value = 255 : ui8} : () -> !emitc.lvalue<ui8>
+  %c5 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<!emitc.ptr<i32>>
+  %c6 = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.lvalue<!emitc.ptr<i32>>
+  %c7 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<!emitc.array<3x7xi32>>
+  %c8 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<!emitc.array<5x!emitc.ptr<i8>>>
   return
 }
 // CPP-DEFAULT: void emitc_variable() {
@@ -41,3 +41,5 @@ func.func @emitc_variable() {
 // CPP-DECLTOP-NEXT: [[V4]] = 255;
 // CPP-DECLTOP-NEXT: ;
 // CPP-DECLTOP-NEXT: [[V6]] = NULL;
+// CPP-DECLTOP-NEXT: ;
+// CPP-DECLTOP-NEXT: ;

>From 349cd62319e5b0fc98aae1f25fd26b3201162ba0 Mon Sep 17 00:00:00 2001
From: Simon Camphausen <simon.camphausen at iml.fraunhofer.de>
Date: Tue, 28 May 2024 13:14:37 +0000
Subject: [PATCH 2/3] [wip] lvalue refactor

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   |   6 +-
 .../MemRefToEmitC/MemRefToEmitC.cpp           |  10 +-
 mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp |   4 +-
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           |  14 ++-
 mlir/lib/Target/Cpp/TranslateToCpp.cpp        |  35 +++---
 mlir/test/Conversion/SCFToEmitC/for.mlir      |  18 +--
 mlir/test/Conversion/SCFToEmitC/if.mlir       |   4 +-
 mlir/test/Dialect/EmitC/invalid_ops.mlir      |  12 +-
 mlir/test/Dialect/EmitC/invalid_types.mlir    |  24 ++--
 mlir/test/Dialect/EmitC/ops.mlir              |  11 +-
 mlir/test/Target/Cpp/common-cpp.mlir          |   7 +-
 mlir/test/Target/Cpp/expressions.mlir         |  35 ++++--
 mlir/test/Target/Cpp/for.mlir                 |  16 +--
 mlir/test/Target/Cpp/global.mlir              |  49 ++++++---
 mlir/test/Target/Cpp/lvalue.mlir              |   2 +-
 mlir/test/Target/Cpp/subscript.mlir           | 103 ++++++++++++------
 16 files changed, 208 insertions(+), 142 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 396bf37abe979..1778c22deea94 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -835,12 +835,12 @@ def EmitC_LogicalOrOp : EmitC_BinaryOp<"logical_or", [CExpression]> {
   let assemblyFormat = "operands attr-dict `:` type(operands)";
 }
 
-def EmitC_LValueToRValueOp : EmitC_Op<"lvalue_to_rvalue", [
+def EmitC_LValueLoadOp : EmitC_Op<"lvalue_load", [
   TypesMatchWith<"result type matches value type of 'operand'",
                   "operand", "result",
                   "::llvm::cast<LValueType>($_self).getValue()">
 ]> {
-  let summary = "lvalue to rvalue conversion operation";
+  let summary = "load an lvalue by assigning it to a local variable";
   let description = [{}];
 
   let arguments = (ins EmitC_LValueType:$operand);
@@ -1025,7 +1025,7 @@ def EmitC_VariableOp : EmitC_Op<"variable", []> {
   }];
 
   let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
-  let results = (outs EmitC_LValueType);
+  let results = (outs AnyTypeOf<[EmitC_ArrayType, EmitC_LValueType]>);
 
   let hasVerifier = 1;
 }
diff --git a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
index e0c421741b305..d78b32c3acbd5 100644
--- a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
+++ b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
@@ -45,7 +45,9 @@ struct ConvertAlloca final : public OpConversionPattern<memref::AllocaOp> {
       return rewriter.notifyMatchFailure(op.getLoc(), "cannot convert type");
     }
     auto noInit = emitc::OpaqueAttr::get(getContext(), "");
+
     rewriter.replaceOpWithNewOp<emitc::VariableOp>(op, resultTy, noInit);
+
     return success();
   }
 };
@@ -137,12 +139,7 @@ struct ConvertLoad final : public OpConversionPattern<memref::LoadOp> {
     auto subscript = rewriter.create<emitc::SubscriptOp>(
         op.getLoc(), arrayValue, operands.getIndices());
 
-    auto noInit = emitc::OpaqueAttr::get(getContext(), "");
-    auto var =
-        rewriter.create<emitc::VariableOp>(op.getLoc(), resultTy, noInit);
-
-    rewriter.create<emitc::AssignOp>(op.getLoc(), var, subscript);
-    rewriter.replaceOp(op, var);
+    rewriter.replaceOpWithNewOp<emitc::LValueLoadOp>(op, resultTy, subscript);
     return success();
   }
 };
@@ -161,6 +158,7 @@ struct ConvertStore final : public OpConversionPattern<memref::StoreOp> {
 
     auto subscript = rewriter.create<emitc::SubscriptOp>(
         op.getLoc(), arrayValue, operands.getIndices());
+        
     rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
                                                  operands.getValue());
     return success();
diff --git a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
index 7ed84a96057c0..4ba1efbdef232 100644
--- a/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
+++ b/mlir/lib/Conversion/SCFToEmitC/SCFToEmitC.cpp
@@ -86,7 +86,7 @@ static void assignValues(ValueRange values, SmallVector<Value> &variables,
 
     // TODO: Make sure this is safe, as this moves operations with memory
     // effects.
-    if (auto op = dyn_cast_if_present<emitc::LValueToRValueOp>(
+    if (auto op = dyn_cast_if_present<emitc::LValueLoadOp>(
             value.getDefiningOp())) {
       rewriter.moveOpBefore(op, assign);
     }
@@ -125,7 +125,7 @@ static void replaceUsers(PatternRewriter &rewriter,
 
       rewriter.setInsertionPoint(op);
       Value rValue =
-          rewriter.create<emitc::LValueToRValueOp>(loc, from.getType(), to);
+          rewriter.create<emitc::LValueLoadOp>(loc, from.getType(), to);
       operand.set(rValue);
     }
   }
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index b8d3141d2c868..4b9275cb51365 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -61,6 +61,8 @@ void mlir::emitc::buildTerminatedBody(OpBuilder &builder, Location loc) {
 bool mlir::emitc::isSupportedEmitCType(Type type) {
   if (llvm::isa<emitc::OpaqueType>(type))
     return true;
+  if (auto lType = llvm::dyn_cast<emitc::LValueType>(type))
+    return isSupportedEmitCType(lType.getValue());
   if (auto ptrType = llvm::dyn_cast<emitc::PointerType>(type))
     return isSupportedEmitCType(ptrType.getPointee());
   if (auto arrayType = llvm::dyn_cast<emitc::ArrayType>(type)) {
@@ -779,7 +781,7 @@ LogicalResult emitc::LiteralOp::verify() {
 // LValueToRValueOp
 //===----------------------------------------------------------------------===//
 
-LogicalResult emitc::LValueToRValueOp::verify() {
+LogicalResult emitc::LValueLoadOp::verify() {
   Type operandType = getOperand().getType();
   Type resultType = getResult().getType();
   if (!llvm::isa<emitc::LValueType>(operandType))
@@ -888,9 +890,10 @@ LogicalResult emitc::SubscriptOp::verify() {
     }
     // Check element type.
     Type elementType = arrayType.getElementType();
-    if (elementType != getType()) {
+    Type resultType = getType().getValue();
+    if (elementType != resultType) {
       return emitOpError() << "on array operand requires element type ("
-                           << elementType << ") and result type (" << getType()
+                           << elementType << ") and result type (" << resultType
                            << ") to match";
     }
     return success();
@@ -914,9 +917,10 @@ LogicalResult emitc::SubscriptOp::verify() {
     }
     // Check pointee type.
     Type pointeeType = pointerType.getPointee();
-    if (pointeeType != getType()) {
+    Type resultType = getType().getValue();
+    if (pointeeType != resultType) {
       return emitOpError() << "on pointer operand requires pointee type ("
-                           << pointeeType << ") and result type (" << getType()
+                           << pointeeType << ") and result type (" << resultType
                            << ") to match";
     }
     return success();
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 0fdf82169dd48..e10410c3e83ef 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -378,10 +378,11 @@ static LogicalResult printOperation(CppEmitter &emitter,
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
-                                    emitc::LValueToRValueOp lValueToRValueOp) {
-  // Add name to cache so that `hasValueInScope` works.
-  emitter.getOrCreateName(lValueToRValueOp.getResult());
-  return success();
+                                    emitc::LValueLoadOp lValueLoadOp) {
+  if (failed(emitter.emitAssignPrefix(*lValueLoadOp)))
+    return failure();
+
+  return emitter.emitOperand(lValueLoadOp.getOperand());
 }
 
 static LogicalResult printOperation(CppEmitter &emitter,
@@ -961,7 +962,7 @@ static LogicalResult printFunctionBody(CppEmitter &emitter,
     // regions.
     WalkResult result =
         functionOp->walk<WalkOrder::PreOrder>([&](Operation *op) -> WalkResult {
-          if (isa<emitc::LiteralOp, emitc::LValueToRValueOp>(op) ||
+          if (isa<emitc::LiteralOp>(op) ||
               (isa<emitc::ApplyOp>(op) &&
                cast<emitc::ApplyOp>(op).getApplicableOperator() == "*") ||
               isa<emitc::ExpressionOp>(op->getParentOp()) ||
@@ -1153,15 +1154,6 @@ std::string CppEmitter::getSubscriptName(emitc::SubscriptOp op) {
 StringRef CppEmitter::getOrCreateName(Value val) {
   if (auto literal = dyn_cast_if_present<emitc::LiteralOp>(val.getDefiningOp()))
     return literal.getValue();
-  if (auto lValueToRValue =
-          dyn_cast_if_present<emitc::LValueToRValueOp>(val.getDefiningOp())) {
-    Value operand = lValueToRValue.getOperand();
-    valueMapper.insert(val, "***UNUSED***");
-
-    assert(hasValueInScope(operand));
-
-    return getOrCreateName(operand);
-  }
   if (!valueMapper.count(val)) {
     if (auto subscript =
             dyn_cast_if_present<emitc::SubscriptOp>(val.getDefiningOp())) {
@@ -1428,7 +1420,7 @@ LogicalResult CppEmitter::emitVariableAssignment(OpResult result) {
 
 LogicalResult CppEmitter::emitVariableDeclaration(OpResult result,
                                                   bool trailingSemicolon) {
-  if (isa<emitc::SubscriptOp>(result.getDefiningOp()))
+  if (isa<emitc::SubscriptOp, emitc::GetGlobalOp>(result.getDefiningOp()))
     return success();
   if (hasValueInScope(result)) {
     return result.getDefiningOp()->emitError(
@@ -1532,11 +1524,11 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::ConditionalOp, emitc::ConstantOp, emitc::DeclareFuncOp,
                 emitc::DivOp, emitc::ExpressionOp, emitc::ForOp, emitc::FuncOp,
                 emitc::GlobalOp, emitc::GetGlobalOp, emitc::IfOp,
-                emitc::IncludeOp, emitc::LogicalAndOp,
-                emitc::LValueToRValueOp, emitc::LogicalNotOp, emitc::RemOp,
-                emitc::LogicalOrOp, emitc::MulOp, emitc::ReturnOp,
-                emitc::SubOp, emitc::SubscriptOp, emitc::UnaryMinusOp,
-                emitc::UnaryPlusOp, emitc::VariableOp, emitc::VerbatimOp>(
+                emitc::IncludeOp, emitc::LogicalAndOp, emitc::LogicalNotOp,
+                emitc::LValueLoadOp, emitc::RemOp, emitc::LogicalOrOp,
+                emitc::MulOp, emitc::ReturnOp, emitc::SubOp, emitc::SubscriptOp,
+                emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp,
+                emitc::VerbatimOp>(
               [&](auto op) { return printOperation(*this, op); })
           // Func ops.
           .Case<func::CallOp, func::FuncOp, func::ReturnOp>(
@@ -1549,7 +1541,8 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
   if (failed(status))
     return failure();
 
-  if (isa<emitc::LiteralOp, emitc::LValueToRValueOp, emitc::SubscriptOp, emitc::GetGlobalOp>(op))
+  if (isa<emitc::LiteralOp, emitc::SubscriptOp,
+          emitc::GetGlobalOp>(op))
     return success();
 
   if (getEmittedExpression() ||
diff --git a/mlir/test/Conversion/SCFToEmitC/for.mlir b/mlir/test/Conversion/SCFToEmitC/for.mlir
index 22c60c4e50df9..37654bdf649ae 100644
--- a/mlir/test/Conversion/SCFToEmitC/for.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/for.mlir
@@ -52,14 +52,14 @@ func.func @for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> (f32, f32)
 // CHECK-NEXT:    emitc.assign %[[VAL_3]] : f32 to %[[VAL_5]] : <f32>
 // CHECK-NEXT:    emitc.assign %[[VAL_4]] : f32 to %[[VAL_6]] : <f32>
 // CHECK-NEXT:    emitc.for %[[VAL_7:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
-// CHECK-NEXT:      %[[VAL_8:.*]] = emitc.lvalue_to_rvalue %[[VAL_5]] : <f32>
-// CHECK-NEXT:      %[[VAL_9:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>
+// CHECK-NEXT:      %[[VAL_8:.*]] = emitc.lvalue_load %[[VAL_5]] : <f32>
+// CHECK-NEXT:      %[[VAL_9:.*]] = emitc.lvalue_load %[[VAL_6]] : <f32>
 // CHECK-NEXT:      %[[VAL_10:.*]] = arith.addf %[[VAL_8]], %[[VAL_9]] : f32
 // CHECK-NEXT:      emitc.assign %[[VAL_10]] : f32 to %[[VAL_5]] : <f32>
 // CHECK-NEXT:      emitc.assign %[[VAL_10]] : f32 to %[[VAL_6]] : <f32>
 // CHECK-NEXT:    }
-// CHECK-NEXT:    %[[VAL_11:.*]] = emitc.lvalue_to_rvalue %[[VAL_5]] : <f32>
-// CHECK-NEXT:    %[[VAL_12:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>
+// CHECK-NEXT:    %[[VAL_11:.*]] = emitc.lvalue_load %[[VAL_5]] : <f32>
+// CHECK-NEXT:    %[[VAL_12:.*]] = emitc.lvalue_load %[[VAL_6]] : <f32>
 // CHECK-NEXT:    return %[[VAL_11]], %[[VAL_12]] : f32, f32
 // CHECK-NEXT:  }
 
@@ -81,17 +81,17 @@ func.func @nested_for_yield(%arg0 : index, %arg1 : index, %arg2 : index) -> f32
 // CHECK-NEXT:    emitc.assign %[[VAL_3]] : f32 to %[[VAL_4]] : <f32>
 // CHECK-NEXT:    emitc.for %[[VAL_5:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
 // CHECK-NEXT:      %[[VAL_6:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<f32>
-// CHECK-NEXT:      %[[VAL_7:.*]] = emitc.lvalue_to_rvalue %[[VAL_4]] : <f32>
+// CHECK-NEXT:      %[[VAL_7:.*]] = emitc.lvalue_load %[[VAL_4]] : <f32>
 // CHECK-NEXT:      emitc.assign %[[VAL_7]] : f32 to %[[VAL_6]] : <f32>
 // CHECK-NEXT:      emitc.for %[[VAL_8:.*]] = %[[VAL_0]] to %[[VAL_1]] step %[[VAL_2]] {
-// CHECK-NEXT:        %[[VAL_9:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>  
-// CHECK-NEXT:        %[[VAL_10:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>  
+// CHECK-NEXT:        %[[VAL_9:.*]] = emitc.lvalue_load %[[VAL_6]] : <f32>  
+// CHECK-NEXT:        %[[VAL_10:.*]] = emitc.lvalue_load %[[VAL_6]] : <f32>  
 // CHECK-NEXT:        %[[VAL_11:.*]] = arith.addf %[[VAL_10]], %[[VAL_9]] : f32
 // CHECK-NEXT:        emitc.assign %[[VAL_11]] : f32 to %[[VAL_6]] : <f32>
 // CHECK-NEXT:      }
-// CHECK-NEXT:      %[[VAL_12:.*]] = emitc.lvalue_to_rvalue %[[VAL_6]] : <f32>  
+// CHECK-NEXT:      %[[VAL_12:.*]] = emitc.lvalue_load %[[VAL_6]] : <f32>  
 // CHECK-NEXT:      emitc.assign %[[VAL_12]] : f32 to %[[VAL_4]] : <f32>
 // CHECK-NEXT:    }
-// CHECK-NEXT:    %[[VAL_13:.*]] = emitc.lvalue_to_rvalue %[[VAL_4]] : <f32>  
+// CHECK-NEXT:    %[[VAL_13:.*]] = emitc.lvalue_load %[[VAL_4]] : <f32>  
 // CHECK-NEXT:    return %[[VAL_13]] : f32
 // CHECK-NEXT:  }
diff --git a/mlir/test/Conversion/SCFToEmitC/if.mlir b/mlir/test/Conversion/SCFToEmitC/if.mlir
index 330edb00b38ce..463cf1d3d9645 100644
--- a/mlir/test/Conversion/SCFToEmitC/if.mlir
+++ b/mlir/test/Conversion/SCFToEmitC/if.mlir
@@ -66,7 +66,7 @@ func.func @test_if_yield(%arg0: i1, %arg1: f32) -> (i32, f64) {
 // CHECK-NEXT:      emitc.assign %[[VAL_7]] : i32 to %[[VAL_3]] : <i32>
 // CHECK-NEXT:      emitc.assign %[[VAL_8]] : f64 to %[[VAL_4]] : <f64>
 // CHECK-NEXT:    }
-// CHECK-NEXT:    %[[VAL_9:.*]] = emitc.lvalue_to_rvalue %[[VAL_3]] : <i32>
-// CHECK-NEXT:    %[[VAL_10:.*]] = emitc.lvalue_to_rvalue %[[VAL_4]] : <f64>
+// CHECK-NEXT:    %[[VAL_9:.*]] = emitc.lvalue_load %[[VAL_3]] : <i32>
+// CHECK-NEXT:    %[[VAL_10:.*]] = emitc.lvalue_load %[[VAL_4]] : <f64>
 // CHECK-NEXT:    return %[[VAL_9]], %[[VAL_10]] : i32, f64
 // CHECK-NEXT:  }
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index 302d6de3327ab..d58d13c9f907d 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -392,7 +392,7 @@ func.func @logical_or_resulterror(%arg0: i32, %arg1: i32) {
 
 func.func @test_subscript_array_indices_mismatch(%arg0: !emitc.array<4x8xf32>, %arg1: index) {
   // expected-error @+1 {{'emitc.subscript' op on array operand requires number of indices (1) to match the rank of the array type (2)}}
-  %0 = emitc.subscript %arg0[%arg1] : (!emitc.array<4x8xf32>, index) -> f32
+  %0 = emitc.subscript %arg0[%arg1] : (!emitc.array<4x8xf32>, index) -> !emitc.lvalue<f32>
   return
 }
 
@@ -400,7 +400,7 @@ func.func @test_subscript_array_indices_mismatch(%arg0: !emitc.array<4x8xf32>, %
 
 func.func @test_subscript_array_index_type_mismatch(%arg0: !emitc.array<4x8xf32>, %arg1: index, %arg2: f32) {
   // expected-error @+1 {{'emitc.subscript' op on array operand requires index operand 1 to be integer-like, but got 'f32'}}
-  %0 = emitc.subscript %arg0[%arg1, %arg2] : (!emitc.array<4x8xf32>, index, f32) -> f32
+  %0 = emitc.subscript %arg0[%arg1, %arg2] : (!emitc.array<4x8xf32>, index, f32) -> !emitc.lvalue<f32>
   return
 }
 
@@ -408,7 +408,7 @@ func.func @test_subscript_array_index_type_mismatch(%arg0: !emitc.array<4x8xf32>
 
 func.func @test_subscript_array_type_mismatch(%arg0: !emitc.array<4x8xf32>, %arg1: index, %arg2: index) {
   // expected-error @+1 {{'emitc.subscript' op on array operand requires element type ('f32') and result type ('i32') to match}}
-  %0 = emitc.subscript %arg0[%arg1, %arg2] : (!emitc.array<4x8xf32>, index, index) -> i32
+  %0 = emitc.subscript %arg0[%arg1, %arg2] : (!emitc.array<4x8xf32>, index, index) -> !emitc.lvalue<i32>
   return
 }
 
@@ -416,7 +416,7 @@ func.func @test_subscript_array_type_mismatch(%arg0: !emitc.array<4x8xf32>, %arg
 
 func.func @test_subscript_ptr_indices_mismatch(%arg0: !emitc.ptr<f32>, %arg1: index) {
   // expected-error @+1 {{'emitc.subscript' op on pointer operand requires one index operand, but got 2}}
-  %0 = emitc.subscript %arg0[%arg1, %arg1] : (!emitc.ptr<f32>, index, index) -> f32
+  %0 = emitc.subscript %arg0[%arg1, %arg1] : (!emitc.ptr<f32>, index, index) -> !emitc.lvalue<f32>
   return
 }
 
@@ -424,7 +424,7 @@ func.func @test_subscript_ptr_indices_mismatch(%arg0: !emitc.ptr<f32>, %arg1: in
 
 func.func @test_subscript_ptr_index_type_mismatch(%arg0: !emitc.ptr<f32>, %arg1: f64) {
   // expected-error @+1 {{'emitc.subscript' op on pointer operand requires index operand to be integer-like, but got 'f64'}}
-  %0 = emitc.subscript %arg0[%arg1] : (!emitc.ptr<f32>, f64) -> f32
+  %0 = emitc.subscript %arg0[%arg1] : (!emitc.ptr<f32>, f64) -> !emitc.lvalue<f32>
   return
 }
 
@@ -432,7 +432,7 @@ func.func @test_subscript_ptr_index_type_mismatch(%arg0: !emitc.ptr<f32>, %arg1:
 
 func.func @test_subscript_ptr_type_mismatch(%arg0: !emitc.ptr<f32>, %arg1: index) {
   // expected-error @+1 {{'emitc.subscript' op on pointer operand requires pointee type ('f32') and result type ('f64') to match}}
-  %0 = emitc.subscript %arg0[%arg1] : (!emitc.ptr<f32>, index) -> f64
+  %0 = emitc.subscript %arg0[%arg1] : (!emitc.ptr<f32>, index) -> !emitc.lvalue<f64>
   return
 }
 
diff --git a/mlir/test/Dialect/EmitC/invalid_types.mlir b/mlir/test/Dialect/EmitC/invalid_types.mlir
index 0ad8d4eabe6b8..e192b557d8480 100644
--- a/mlir/test/Dialect/EmitC/invalid_types.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_types.mlir
@@ -101,47 +101,47 @@ func.func @illegal_float_type(%arg0: f80, %arg1: f80) {
 // -----
 
 func.func @illegal_pointee_type() {
-    // expected-error @+1 {{'emitc.variable' op result #0 must be type supported by EmitC, but got '!emitc.ptr<i11>'}}
-    %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.ptr<i11>
+    // expected-error @+1 {{'emitc.constant' op result #0 must be type supported by EmitC, but got '!emitc.ptr<i11>'}}
+    %v = "emitc.constant"(){value = #emitc.opaque<"">} : () -> !emitc.ptr<i11>
     return
 }
 
 // -----
 
 func.func @illegal_non_static_tensor_shape_type() {
-    // expected-error @+1 {{'emitc.variable' op result #0 must be type supported by EmitC, but got 'tensor<?xf32>'}}
-    %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> tensor<?xf32>
+    // expected-error @+1 {{'emitc.constant' op result #0 must be type supported by EmitC, but got 'tensor<?xf32>'}}
+    %v = "emitc.constant"(){value = #emitc.opaque<"">} : () -> tensor<?xf32>
     return
 }
 
 // -----
 
 func.func @illegal_tensor_array_element_type() {
-    // expected-error @+1 {{'emitc.variable' op result #0 must be type supported by EmitC, but got 'tensor<!emitc.array<9xi16>>'}}
-    %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> tensor<!emitc.array<9xi16>>
+    // expected-error @+1 {{'emitc.constant' op result #0 must be type supported by EmitC, but got 'tensor<!emitc.array<9xi16>>'}}
+    %v = "emitc.constant"(){value = #emitc.opaque<"">} : () -> tensor<!emitc.array<9xi16>>
     return
 }
 
 // -----
 
 func.func @illegal_tensor_integer_element_type() {
-    // expected-error @+1 {{'emitc.variable' op result #0 must be type supported by EmitC, but got 'tensor<9xi11>'}}
-    %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> tensor<9xi11>
+    // expected-error @+1 {{'emitc.constant' op result #0 must be type supported by EmitC, but got 'tensor<9xi11>'}}
+    %v = "emitc.constant"(){value = #emitc.opaque<"">} : () -> tensor<9xi11>
     return
 }
 
 // -----
 
 func.func @illegal_tuple_array_element_type() {
-    // expected-error @+1 {{'emitc.variable' op result #0 must be type supported by EmitC, but got 'tuple<!emitc.array<9xf32>, f32>'}}
-    %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> tuple<!emitc.array<9xf32>, f32>
+    // expected-error @+1 {{'emitc.constant' op result #0 must be type supported by EmitC, but got 'tuple<!emitc.array<9xf32>, f32>'}}
+    %v = "emitc.constant"(){value = #emitc.opaque<"">} : () -> tuple<!emitc.array<9xf32>, f32>
     return
 }
 
 // -----
 
 func.func @illegal_tuple_float_element_type() {
-    // expected-error @+1 {{'emitc.variable' op result #0 must be type supported by EmitC, but got 'tuple<i32, f80>'}}
-    %v = "emitc.variable"(){value = #emitc.opaque<"">} : () -> tuple<i32, f80>
+    // expected-error @+1 {{'emitc.constant' op result #0 must be type supported by EmitC, but got 'tuple<i32, f80>'}}
+    %v = "emitc.constant"(){value = #emitc.opaque<"">} : () -> tuple<i32, f80>
     return
 }
diff --git a/mlir/test/Dialect/EmitC/ops.mlir b/mlir/test/Dialect/EmitC/ops.mlir
index 930624fc2e258..6d984dab4c5be 100644
--- a/mlir/test/Dialect/EmitC/ops.mlir
+++ b/mlir/test/Dialect/EmitC/ops.mlir
@@ -215,9 +215,9 @@ func.func @test_for_not_index_induction(%arg0 : i16, %arg1 : i16, %arg2 : i16) {
 }
 
 func.func @test_subscript(%arg0 : !emitc.array<2x3xf32>, %arg1 : !emitc.ptr<i32>, %arg2 : !emitc.opaque<"std::map<char, int>">, %idx0 : index, %idx1 : i32, %idx2 : !emitc.opaque<"char">) {
-  %0 = emitc.subscript %arg0[%idx0, %idx1] : (!emitc.array<2x3xf32>, index, i32) -> f32
-  %1 = emitc.subscript %arg1[%idx0] : (!emitc.ptr<i32>, index) -> i32
-  %2 = emitc.subscript %arg2[%idx2] : (!emitc.opaque<"std::map<char, int>">, !emitc.opaque<"char">) -> !emitc.opaque<"int">
+  %0 = emitc.subscript %arg0[%idx0, %idx1] : (!emitc.array<2x3xf32>, index, i32) -> !emitc.lvalue<f32>
+  %1 = emitc.subscript %arg1[%idx0] : (!emitc.ptr<i32>, index) -> !emitc.lvalue<i32>
+  %2 = emitc.subscript %arg2[%idx2] : (!emitc.opaque<"std::map<char, int>">, !emitc.opaque<"char">) -> !emitc.lvalue<!emitc.opaque<"int">>
   return
 }
 
@@ -242,6 +242,7 @@ emitc.global const @myconstant : !emitc.array<2xi16> = dense<2>
 
 func.func @use_global(%i: index) -> f32 {
   %0 = emitc.get_global @myglobal : !emitc.array<2xf32>
-  %1 = emitc.subscript %0[%i] : (!emitc.array<2xf32>, index) -> f32
-  return %1 : f32
+  %1 = emitc.subscript %0[%i] : (!emitc.array<2xf32>, index) -> !emitc.lvalue<f32>
+  %2 = emitc.lvalue_load %1 : <f32>
+  return %2 : f32
 }
diff --git a/mlir/test/Target/Cpp/common-cpp.mlir b/mlir/test/Target/Cpp/common-cpp.mlir
index 3876c2297b7a0..8e069df52d5a4 100644
--- a/mlir/test/Target/Cpp/common-cpp.mlir
+++ b/mlir/test/Target/Cpp/common-cpp.mlir
@@ -86,10 +86,11 @@ func.func @apply(%arg0: i32) -> !emitc.ptr<i32> {
   // CHECK: int32_t* [[V2]] = &[[V1]];
   %0 = emitc.apply "&"(%arg0) : (i32) -> !emitc.ptr<i32>
   // CHECK: int32_t [[V3]];
-  // CHECK: [[V3]] = *[[V2]];
-  %1 = emitc.apply "*"(%0) : (!emitc.ptr<i32>) -> (!emitc.lvalue<i32>)
   %2 = "emitc.variable"() {value = #emitc.opaque<"">} : () -> !emitc.lvalue<i32>
-  %3 = emitc.lvalue_to_rvalue %1 : !emitc.lvalue<i32>
+  %1 = emitc.apply "*"(%0) : (!emitc.ptr<i32>) -> (!emitc.lvalue<i32>)
+  %3 = emitc.lvalue_load %1 : !emitc.lvalue<i32>
+  // CHECK: int32_t [[V4]] = *[[V2]];
+  // CHECK: [[V3]] = [[V4]];
   emitc.assign %3 : i32 to %2 : !emitc.lvalue<i32>
   return %0 : !emitc.ptr<i32>
 }
diff --git a/mlir/test/Target/Cpp/expressions.mlir b/mlir/test/Target/Cpp/expressions.mlir
index e2418fe628f78..7cf99a7697e4b 100644
--- a/mlir/test/Target/Cpp/expressions.mlir
+++ b/mlir/test/Target/Cpp/expressions.mlir
@@ -9,12 +9,14 @@
 // CPP-DEFAULT-NEXT:   } else {
 // CPP-DEFAULT-NEXT:     [[VAL_6]] = [[VAL_1]];
 // CPP-DEFAULT-NEXT:   }
-// CPP-DEFAULT-NEXT:   return [[VAL_6]];
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_7:v[0-9]+]] = [[VAL_6]];
+// CPP-DEFAULT-NEXT:   return [[VAL_7]];
 // CPP-DEFAULT-NEXT: }
 
 // CPP-DECLTOP:      int32_t single_use(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]], int32_t [[VAL_4:v[0-9]+]]) {
 // CPP-DECLTOP-NEXT:   bool [[VAL_5:v[0-9]+]];
 // CPP-DECLTOP-NEXT:   int32_t [[VAL_6:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_7:v[0-9]+]];
 // CPP-DECLTOP-NEXT:   [[VAL_5]] = bar([[VAL_1]] * M_PI, [[VAL_3]]) - [[VAL_4]] < [[VAL_2]];
 // CPP-DECLTOP-NEXT:   ;
 // CPP-DECLTOP-NEXT:   if ([[VAL_5]]) {
@@ -22,7 +24,8 @@
 // CPP-DECLTOP-NEXT:   } else {
 // CPP-DECLTOP-NEXT:     [[VAL_6]] = [[VAL_1]];
 // CPP-DECLTOP-NEXT:   }
-// CPP-DECLTOP-NEXT:   return [[VAL_6]];
+// CPP-DECLTOP-NEXT:   [[VAL_7]] = [[VAL_6]];
+// CPP-DECLTOP-NEXT:   return [[VAL_7]];
 // CPP-DECLTOP-NEXT: }
 
 func.func @single_use(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32 {
@@ -42,7 +45,7 @@ func.func @single_use(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32 {
     emitc.assign %arg0 : i32 to %v : !emitc.lvalue<i32>
     emitc.yield
   }
-  %v_rvalue = emitc.lvalue_to_rvalue %v : !emitc.lvalue<i32>
+  %v_rvalue = emitc.lvalue_load %v : !emitc.lvalue<i32>
   return %v_rvalue : i32
 }
 
@@ -94,13 +97,15 @@ func.func @paranthesis_for_low_precedence(%arg0: i32, %arg1: i32, %arg2: i32) ->
 // CPP-DEFAULT-NEXT:   }
 // CPP-DEFAULT-NEXT:   bool [[VAL_7:v[0-9]+]];
 // CPP-DEFAULT-NEXT:   [[VAL_7]] = [[VAL_5]];
-// CPP-DEFAULT-NEXT:   return [[VAL_6]];
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_8:v[0-9]+]] = [[VAL_6]];
+// CPP-DEFAULT-NEXT:   return [[VAL_8]];
 // CPP-DEFAULT-NEXT: }
 
 // CPP-DECLTOP:      int32_t multiple_uses(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]], int32_t [[VAL_4:v[0-9]+]]) {
 // CPP-DECLTOP-NEXT:   bool [[VAL_5:v[0-9]+]];
 // CPP-DECLTOP-NEXT:   int32_t [[VAL_6:v[0-9]+]];
 // CPP-DECLTOP-NEXT:   bool [[VAL_7:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_8:v[0-9]+]];
 // CPP-DECLTOP-NEXT:   [[VAL_5]] = bar([[VAL_1]] * [[VAL_2]], [[VAL_3]]) - [[VAL_4]] < [[VAL_2]];
 // CPP-DECLTOP-NEXT:   ;
 // CPP-DECLTOP-NEXT:   if ([[VAL_5]]) {
@@ -110,7 +115,8 @@ func.func @paranthesis_for_low_precedence(%arg0: i32, %arg1: i32, %arg2: i32) ->
 // CPP-DECLTOP-NEXT:   }
 // CPP-DECLTOP-NEXT:   ;
 // CPP-DECLTOP-NEXT:   [[VAL_7]] = [[VAL_5]];
-// CPP-DECLTOP-NEXT:   return [[VAL_6]];
+// CPP-DECLTOP-NEXT:   [[VAL_8]] = [[VAL_6]];
+// CPP-DECLTOP-NEXT:   return [[VAL_8]];
 // CPP-DECLTOP-NEXT: }
 
 func.func @multiple_uses(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32 {
@@ -132,7 +138,7 @@ func.func @multiple_uses(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32
   %q = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<i1>
   emitc.assign %e : i1 to %q : !emitc.lvalue<i1>
 
-  %v_rvalue = emitc.lvalue_to_rvalue %v : !emitc.lvalue<i32>
+  %v_rvalue = emitc.lvalue_load %v : !emitc.lvalue<i32>
   return %v_rvalue : i32
 }
 
@@ -145,13 +151,15 @@ func.func @multiple_uses(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32
 // CPP-DEFAULT-NEXT:   } else {
 // CPP-DEFAULT-NEXT:     [[VAL_7]] = [[VAL_1]];
 // CPP-DEFAULT-NEXT:   }
-// CPP-DEFAULT-NEXT:   return [[VAL_7]];
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_8:v[0-9]+]] = [[VAL_7]];
+// CPP-DEFAULT-NEXT:   return [[VAL_8]];
 // CPP-DEFAULT-NEXT: }
 
 // CPP-DECLTOP:      int32_t different_expressions(int32_t [[VAL_1:v[0-9]+]], int32_t [[VAL_2:v[0-9]+]], int32_t [[VAL_3:v[0-9]+]], int32_t [[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:   int32_t [[VAL_7:v[0-9]+]];
+// CPP-DECLTOP-NEXT:   int32_t [[VAL_8:v[0-9]+]];
 // CPP-DECLTOP-NEXT:   [[VAL_5]] = [[VAL_3]] % [[VAL_4]];
 // CPP-DECLTOP-NEXT:   [[VAL_6]] = bar([[VAL_5]], [[VAL_1]] * [[VAL_2]]);
 // CPP-DECLTOP-NEXT:   ;
@@ -160,7 +168,8 @@ func.func @multiple_uses(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32
 // CPP-DECLTOP-NEXT:   } else {
 // CPP-DECLTOP-NEXT:     [[VAL_7]] = [[VAL_1]];
 // CPP-DECLTOP-NEXT:   }
-// CPP-DECLTOP-NEXT:   return [[VAL_7]];
+// CPP-DECLTOP-NEXT:   [[VAL_8]] = [[VAL_7]];
+// CPP-DECLTOP-NEXT:   return [[VAL_8]];
 // CPP-DECLTOP-NEXT: }
 
 func.func @different_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32) -> i32 {
@@ -187,7 +196,7 @@ func.func @different_expressions(%arg0: i32, %arg1: i32, %arg2: i32, %arg3: i32)
     emitc.yield
   }
 
-  %v_rvalue = emitc.lvalue_to_rvalue %v : !emitc.lvalue<i32>
+  %v_rvalue = emitc.lvalue_load %v : !emitc.lvalue<i32>
   return %v_rvalue : i32
 }
 
@@ -219,7 +228,8 @@ func.func @expression_with_address_taken(%arg0: i32, %arg1: i32, %arg2: !emitc.p
 // 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]];
-// CPP-DEFAULT-NEXT:   return [[VAL_3]][[[VAL_2]]];
+// CPP-DEFAULT-NEXT:   int32_t [[VAL_4:v.+]] = [[VAL_3]][[[VAL_2]]];
+// CPP-DEFAULT-NEXT:   return [[VAL_4]];
 
 func.func @expression_with_subscript_user(%arg0: !emitc.ptr<!emitc.opaque<"void">>) -> i32 {
   %c0 = "emitc.constant"() {value = 0 : i64} : () -> i64
@@ -227,6 +237,7 @@ func.func @expression_with_subscript_user(%arg0: !emitc.ptr<!emitc.opaque<"void"
     %0 = emitc.cast %arg0 : !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<i32>
     emitc.yield %0 : !emitc.ptr<i32>
   }
-  %1 = emitc.subscript %0[%c0] : (!emitc.ptr<i32>, i64) -> i32
-  return %1 : i32
+  %1 = emitc.subscript %0[%c0] : (!emitc.ptr<i32>, i64) -> !emitc.lvalue<i32>
+  %2 = emitc.lvalue_load %1 : <i32>
+  return %2 : i32
 }
diff --git a/mlir/test/Target/Cpp/for.mlir b/mlir/test/Target/Cpp/for.mlir
index 6ca29b165cdee..f8faf83983494 100644
--- a/mlir/test/Target/Cpp/for.mlir
+++ b/mlir/test/Target/Cpp/for.mlir
@@ -47,17 +47,17 @@ func.func @test_for_yield() {
   emitc.assign %s0 : i32 to %2 : !emitc.lvalue<i32>
   emitc.assign %p0 : f32 to %3 : !emitc.lvalue<f32>
   emitc.for %iter = %start to %stop step %step {
-    %4 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+    %4 = emitc.lvalue_load %2 : !emitc.lvalue<i32>
     %sn = emitc.call_opaque "add"(%4, %iter) : (i32, index) -> i32
-    %5 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+    %5 = emitc.lvalue_load %3 : !emitc.lvalue<f32>
     %pn = emitc.call_opaque "mul"(%5, %iter) : (f32, index) -> f32
     emitc.assign %sn : i32 to %2 : !emitc.lvalue<i32>
     emitc.assign %pn : f32 to %3 : !emitc.lvalue<f32>
     emitc.yield
   }
-  %6 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+  %6 = emitc.lvalue_load %2 : !emitc.lvalue<i32>
   emitc.assign %6 : i32 to %0 : !emitc.lvalue<i32>
-  %7 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+  %7 = emitc.lvalue_load %3 : !emitc.lvalue<f32>
   emitc.assign %7 : f32 to %1 : !emitc.lvalue<f32>
 
   return
@@ -132,17 +132,17 @@ func.func @test_for_yield_2() {
   emitc.assign %s0 : i32 to %2 : !emitc.lvalue<i32>
   emitc.assign %p0 : f32 to %3 : !emitc.lvalue<f32>
   emitc.for %iter = %start to %stop step %step {
-    %4 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+    %4 = emitc.lvalue_load %2 : !emitc.lvalue<i32>
     %sn = emitc.call_opaque "add"(%4, %iter) : (i32, index) -> i32
-    %5 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+    %5 = emitc.lvalue_load %3 : !emitc.lvalue<f32>
     %pn = emitc.call_opaque "mul"(%5, %iter) : (f32, index) -> f32
     emitc.assign %sn : i32 to %2 : !emitc.lvalue<i32>
     emitc.assign %pn : f32 to %3 : !emitc.lvalue<f32>
     emitc.yield
   }
-  %6 = emitc.lvalue_to_rvalue %2 : !emitc.lvalue<i32>
+  %6 = emitc.lvalue_load %2 : !emitc.lvalue<i32>
   emitc.assign %6 : i32 to %0 : !emitc.lvalue<i32>
-  %7 = emitc.lvalue_to_rvalue %3 : !emitc.lvalue<f32>
+  %7 = emitc.lvalue_load %3 : !emitc.lvalue<f32>
   emitc.assign %7 : f32 to %1 : !emitc.lvalue<f32>
 
   return
diff --git a/mlir/test/Target/Cpp/global.mlir b/mlir/test/Target/Cpp/global.mlir
index f0d92e862ae32..f8bfe0c53626e 100644
--- a/mlir/test/Target/Cpp/global.mlir
+++ b/mlir/test/Target/Cpp/global.mlir
@@ -1,38 +1,55 @@
-// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
-// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
+// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
 
 emitc.global extern @decl : i8
-// CHECK: extern int8_t decl;
+// CPP-DEFAULT: extern int8_t decl;
+// CPP-DECLTOP: extern int8_t decl;
 
 emitc.global @uninit : i32
-// CHECK: int32_t uninit;
+// CPP-DEFAULT: int32_t uninit;
+// CPP-DECLTOP: int32_t uninit;
 
 emitc.global @myglobal_int : i32 = 4
-// CHECK: int32_t myglobal_int = 4;
+// CPP-DEFAULT: int32_t myglobal_int = 4;
+// CPP-DECLTOP: int32_t myglobal_int = 4;
 
 emitc.global @myglobal : !emitc.array<2xf32> = dense<4.000000e+00>
-// CHECK: float myglobal[2] = {4.000000000e+00f, 4.000000000e+00f};
+// CPP-DEFAULT: float myglobal[2] = {4.000000000e+00f, 4.000000000e+00f};
+// CPP-DECLTOP: float myglobal[2] = {4.000000000e+00f, 4.000000000e+00f};
 
 emitc.global const @myconstant : !emitc.array<2xi16> = dense<2>
-// CHECK: const int16_t myconstant[2] = {2, 2};
+// CPP-DEFAULT: const int16_t myconstant[2] = {2, 2};
+// CPP-DECLTOP: const int16_t myconstant[2] = {2, 2};
 
 emitc.global extern const @extern_constant : !emitc.array<2xi16>
-// CHECK: extern const int16_t extern_constant[2];
+// CPP-DEFAULT: extern const int16_t extern_constant[2];
+// CPP-DECLTOP: extern const int16_t extern_constant[2];
 
 emitc.global static @static_var : f32
-// CHECK: static float static_var;
+// CPP-DEFAULT: static float static_var;
+// CPP-DECLTOP: static float static_var;
 
 emitc.global static @static_const : f32 = 3.0
-// CHECK: static float static_const = 3.000000000e+00f;
+// CPP-DEFAULT: static float static_const = 3.000000000e+00f;
+// CPP-DECLTOP: static float static_const = 3.000000000e+00f;
 
 emitc.global @opaque_init : !emitc.opaque<"char"> = #emitc.opaque<"CHAR_MIN">
-// CHECK: char opaque_init = CHAR_MIN;
+// CPP-DEFAULT: char opaque_init = CHAR_MIN;
+// CPP-DECLTOP: char opaque_init = CHAR_MIN;
 
 func.func @use_global(%i: index) -> f32 {
   %0 = emitc.get_global @myglobal : !emitc.array<2xf32>
-  %1 = emitc.subscript %0[%i] : (!emitc.array<2xf32>, index) -> f32
-  return %1 : f32
-  // CHECK-LABEL: use_global
-  // CHECK-SAME: (size_t [[V1:.*]])
-  // CHECK:   return myglobal[[[V1]]];
+  %1 = emitc.subscript %0[%i] : (!emitc.array<2xf32>, index) -> !emitc.lvalue<f32>
+  %2 = emitc.lvalue_load %1 : <f32>
+  return %2 : f32
 }
+// CPP-DEFAULT-LABEL: use_global
+// CPP-DEFAULT-SAME: (size_t [[V1:.*]])
+// CPP-DEFAULT-NEXT: float [[V2:[^ ]*]] = myglobal[[[V1]]];
+// CPP-DEFAULT-NEXT: return [[V2]];
+
+// CPP-DECLTOP-LABEL: use_global
+// CPP-DECLTOP-SAME: (size_t [[V1:.*]])
+// CPP-DECLTOP-NEXT: float [[V2:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[V2]] = myglobal[[[V1]]];
+// CPP-DECLTOP-NEXT: return [[V2]];
diff --git a/mlir/test/Target/Cpp/lvalue.mlir b/mlir/test/Target/Cpp/lvalue.mlir
index cd5fe1fd2f7d6..5c370117f4387 100644
--- a/mlir/test/Target/Cpp/lvalue.mlir
+++ b/mlir/test/Target/Cpp/lvalue.mlir
@@ -7,7 +7,7 @@ emitc.func @lvalue_variables(%v1: i32, %v2: i32) -> i32 {
   emitc.assign %val : i32 to %variable : !emitc.lvalue<i32> // write effect
   %addr = emitc.apply "&"(%variable) : (!emitc.lvalue<i32>) -> !emitc.ptr<i32>
   emitc.call @zero (%addr) : (!emitc.ptr<i32>) -> ()
-  %updated_val = emitc.lvalue_to_rvalue %variable : !emitc.lvalue<i32> // read effect, (noop in emitter?)
+  %updated_val = emitc.lvalue_load %variable : !emitc.lvalue<i32> // read effect, (noop in emitter?)
   %neg_one = "emitc.constant"() {value = -1 : i32} : () -> i32
   emitc.assign %neg_one : i32 to %variable : !emitc.lvalue<i32> // invalidates %updated_val
   emitc.return %updated_val : i32
diff --git a/mlir/test/Target/Cpp/subscript.mlir b/mlir/test/Target/Cpp/subscript.mlir
index e5dcc9dc34f00..b5c94c146712e 100644
--- a/mlir/test/Target/Cpp/subscript.mlir
+++ b/mlir/test/Target/Cpp/subscript.mlir
@@ -1,36 +1,59 @@
-// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
-// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s -check-prefix=CPP-DEFAULT
+// RUN: mlir-translate -mlir-to-cpp -declare-variables-at-top %s | FileCheck %s -check-prefix=CPP-DECLTOP
 
 func.func @load_store_array(%arg0: !emitc.array<4x8xf32>, %arg1: !emitc.array<3x5xf32>, %arg2: index, %arg3: index) {
-  %0 = emitc.subscript %arg0[%arg2, %arg3] : (!emitc.array<4x8xf32>, index, index) -> f32
-  %1 = emitc.subscript %arg1[%arg2, %arg3] : (!emitc.array<3x5xf32>, index, index) -> f32
-  %2 = emitc.lvalue_to_rvalue %0 : !emitc.lvalue<f32>
+  %0 = emitc.subscript %arg0[%arg2, %arg3] : (!emitc.array<4x8xf32>, index, index) -> !emitc.lvalue<f32>
+  %1 = emitc.subscript %arg1[%arg2, %arg3] : (!emitc.array<3x5xf32>, index, index) -> !emitc.lvalue<f32>
+  %2 = emitc.lvalue_load %0 : <f32>
   emitc.assign %2 : f32 to %1 : !emitc.lvalue<f32>
   return
 }
-// CHECK: void load_store_array(float [[ARR1:[^ ]*]][4][8], float [[ARR2:[^ ]*]][3][5],
-// CHECK-SAME:            size_t [[I:[^ ]*]], size_t [[J:[^ ]*]])
-// CHECK-NEXT: [[ARR2]][[[I]]][[[J]]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DEFAULT: void load_store_array(float [[ARR1:[^ ]*]][4][8], float [[ARR2:[^ ]*]][3][5],
+// CPP-DEFAULT-SAME:            size_t [[I:[^ ]*]], size_t [[J:[^ ]*]])
+// CPP-DEFAULT-NEXT: float [[VAL:[^ ]*]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DEFAULT-NEXT: [[ARR2]][[[I]]][[[J]]] = [[VAL]];
+
+// CPP-DECLTOP: void load_store_array(float [[ARR1:[^ ]*]][4][8], float [[ARR2:[^ ]*]][3][5],
+// CPP-DECLTOP-SAME:            size_t [[I:[^ ]*]], size_t [[J:[^ ]*]])
+// CPP-DECLTOP-NEXT: float [[VAL:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[VAL]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DECLTOP-NEXT: [[ARR2]][[[I]]][[[J]]] = [[VAL]];
 
 func.func @load_store_pointer(%arg0: !emitc.ptr<f32>, %arg1: !emitc.ptr<f32>, %arg2: index, %arg3: index) {
-  %0 = emitc.subscript %arg0[%arg2] : (!emitc.ptr<f32>, index) -> f32
-  %1 = emitc.subscript %arg1[%arg3] : (!emitc.ptr<f32>, index) -> f32
-  emitc.assign %0 : f32 to %1 : f32
+  %0 = emitc.subscript %arg0[%arg2] : (!emitc.ptr<f32>, index) -> !emitc.lvalue<f32>
+  %1 = emitc.subscript %arg1[%arg3] : (!emitc.ptr<f32>, index) -> !emitc.lvalue<f32>
+  %2 = emitc.lvalue_load %0 : <f32>
+  emitc.assign %2 : f32 to %1 : <f32>
   return
 }
-// CHECK: void load_store_pointer(float* [[PTR1:[^ ]*]], float* [[PTR2:[^ ]*]],
-// CHECK-SAME:            size_t [[I:[^ ]*]], size_t [[J:[^ ]*]])
-// CHECK-NEXT: [[PTR2]][[[J]]] = [[PTR1]][[[I]]];
+// CPP-DEFAULT: void load_store_pointer(float* [[PTR1:[^ ]*]], float* [[PTR2:[^ ]*]],
+// CPP-DEFAULT-SAME:            size_t [[I:[^ ]*]], size_t [[J:[^ ]*]])
+// CPP-DEFAULT-NEXT: float [[VAL:[^ ]*]] = [[PTR1]][[[I]]];
+// CPP-DEFAULT-NEXT: [[PTR2]][[[J]]] = [[VAL]];
+
+// CPP-DECLTOP: void load_store_pointer(float* [[PTR1:[^ ]*]], float* [[PTR2:[^ ]*]],
+// CPP-DECLTOP-SAME:            size_t [[I:[^ ]*]], size_t [[J:[^ ]*]])
+// CPP-DECLTOP-NEXT: float [[VAL:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[VAL]] = [[PTR1]][[[I]]];
+// CPP-DECLTOP-NEXT: [[PTR2]][[[J]]] = [[VAL]];
 
 func.func @load_store_opaque(%arg0: !emitc.opaque<"std::map<char, int>">, %arg1: !emitc.opaque<"std::map<char, int>">, %arg2: !emitc.opaque<"char">, %arg3: !emitc.opaque<"char">) {
-  %0 = emitc.subscript %arg0[%arg2] : (!emitc.opaque<"std::map<char, int>">, !emitc.opaque<"char">) -> !emitc.opaque<"int">
-  %1 = emitc.subscript %arg1[%arg3] : (!emitc.opaque<"std::map<char, int>">, !emitc.opaque<"char">) -> !emitc.opaque<"int">
-  emitc.assign %0 : !emitc.opaque<"int"> to %1 : !emitc.opaque<"int">
+  %0 = emitc.subscript %arg0[%arg2] : (!emitc.opaque<"std::map<char, int>">, !emitc.opaque<"char">) -> !emitc.lvalue<!emitc.opaque<"int">>
+  %1 = emitc.subscript %arg1[%arg3] : (!emitc.opaque<"std::map<char, int>">, !emitc.opaque<"char">) -> !emitc.lvalue<!emitc.opaque<"int">>
+  %2 = emitc.lvalue_load %0 : <!emitc.opaque<"int">>
+  emitc.assign %2 : !emitc.opaque<"int"> to %1 : <!emitc.opaque<"int">>
   return
 }
-// CHECK: void load_store_opaque(std::map<char, int> [[MAP1:[^ ]*]], std::map<char, int> [[MAP2:[^ ]*]],
-// CHECK-SAME:            char [[I:[^ ]*]], char [[J:[^ ]*]])
-// CHECK-NEXT: [[MAP2]][[[J]]] = [[MAP1]][[[I]]];
+// CPP-DEFAULT: void load_store_opaque(std::map<char, int> [[MAP1:[^ ]*]], std::map<char, int> [[MAP2:[^ ]*]],
+// CPP-DEFAULT-SAME:            char [[I:[^ ]*]], char [[J:[^ ]*]])
+// CPP-DEFAULT-NEXT: int [[VAL:[^ ]*]] = [[MAP1]][[[I]]];
+// CPP-DEFAULT-NEXT: [[MAP2]][[[J]]] = [[VAL]];
+
+// CPP-DECLTOP: void load_store_opaque(std::map<char, int> [[MAP1:[^ ]*]], std::map<char, int> [[MAP2:[^ ]*]],
+// CPP-DECLTOP-SAME:            char [[I:[^ ]*]], char [[J:[^ ]*]])
+// CPP-DECLTOP-NEXT: int [[VAL:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[VAL]] = [[MAP1]][[[I]]];
+// CPP-DECLTOP-NEXT: [[MAP2]][[[J]]] = [[VAL]];
 
 emitc.func @func1(%arg0 : f32) {
   emitc.return
@@ -38,20 +61,38 @@ emitc.func @func1(%arg0 : f32) {
 
 emitc.func @call_arg(%arg0: !emitc.array<4x8xf32>, %i: i32, %j: i16,
                      %k: i8) {
-  %0 = emitc.subscript %arg0[%i, %j] : (!emitc.array<4x8xf32>, i32, i16) -> f32
-  %1 = emitc.subscript %arg0[%j, %k] : (!emitc.array<4x8xf32>, i16, i8) -> f32
+  %0 = emitc.subscript %arg0[%i, %j] : (!emitc.array<4x8xf32>, i32, i16) -> !emitc.lvalue<f32>
+  %1 = emitc.subscript %arg0[%j, %k] : (!emitc.array<4x8xf32>, i16, i8) -> !emitc.lvalue<f32>
 
-  %2 = emitc.lvalue_to_rvalue %0 : !emitc.lvalue<f32>
+  %2 = emitc.lvalue_load %0 : <f32>
   emitc.call @func1 (%2) : (f32) -> ()
-  %3 = emitc.lvalue_to_rvalue %1 : !emitc.lvalue<f32>
+  %3 = emitc.lvalue_load %1 : <f32>
   emitc.call_opaque "func2" (%3) : (f32) -> ()
-  %4 = emitc.lvalue_to_rvalue %0 : !emitc.lvalue<f32>
-  %5 = emitc.lvalue_to_rvalue %1 : !emitc.lvalue<f32>
+  %4 = emitc.lvalue_load %0 : <f32>
+  %5 = emitc.lvalue_load %1 : <f32>
   emitc.call_opaque "func3" (%4, %5) { args = [1 : index, 0 : index] } : (f32, f32) -> ()
   emitc.return
 }
-// CHECK: void call_arg(float [[ARR1:[^ ]*]][4][8], int32_t [[I:[^ ]*]],
-// CHECK-SAME:          int16_t [[J:[^ ]*]], int8_t [[K:[^ ]*]])
-// CHECK-NEXT: func1([[ARR1]][[[I]]][[[J]]]);
-// CHECK-NEXT: func2([[ARR1]][[[J]]][[[K]]]);
-// CHECK-NEXT: func3([[ARR1]][[[J]]][[[K]]], [[ARR1]][[[I]]][[[J]]]);
+// CPP-DEFAULT: void call_arg(float [[ARR1:[^ ]*]][4][8], int32_t [[I:[^ ]*]],
+// CPP-DEFAULT-SAME:          int16_t [[J:[^ ]*]], int8_t [[K:[^ ]*]])
+// CPP-DEFAULT-NEXT: float [[VAL0:[^ ]*]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DEFAULT-NEXT: func1([[VAL0]]);
+// CPP-DEFAULT-NEXT: float [[VAL1:[^ ]*]] = [[ARR1]][[[J]]][[[K]]];
+// CPP-DEFAULT-NEXT: func2([[VAL1]]);
+// CPP-DEFAULT-NEXT: float [[VAL2:[^ ]*]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DEFAULT-NEXT: float [[VAL3:[^ ]*]] = [[ARR1]][[[J]]][[[K]]];
+// CPP-DEFAULT-NEXT: func3([[VAL3]], [[VAL2]]);
+
+// CPP-DECLTOP: void call_arg(float [[ARR1:[^ ]*]][4][8], int32_t [[I:[^ ]*]],
+// CPP-DECLTOP-SAME:          int16_t [[J:[^ ]*]], int8_t [[K:[^ ]*]])
+// CPP-DECLTOP-NEXT: float [[VAL0:[^ ]*]];
+// CPP-DECLTOP-NEXT: float [[VAL1:[^ ]*]];
+// CPP-DECLTOP-NEXT: float [[VAL2:[^ ]*]];
+// CPP-DECLTOP-NEXT: float [[VAL3:[^ ]*]];
+// CPP-DECLTOP-NEXT: [[VAL0]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DECLTOP-NEXT: func1([[VAL0]]);
+// CPP-DECLTOP-NEXT: [[VAL1]] = [[ARR1]][[[J]]][[[K]]];
+// CPP-DECLTOP-NEXT: func2([[VAL1]]);
+// CPP-DECLTOP-NEXT: [[VAL2]] = [[ARR1]][[[I]]][[[J]]];
+// CPP-DECLTOP-NEXT: [[VAL3]] = [[ARR1]][[[J]]][[[K]]];
+// CPP-DECLTOP-NEXT: func3([[VAL3]], [[VAL2]]);

>From c61d8287826787a654f182e3a95ec03a6e8de370 Mon Sep 17 00:00:00 2001
From: Simon Camphausen <simon.camphausen at iml.fraunhofer.de>
Date: Mon, 3 Jun 2024 14:33:55 +0000
Subject: [PATCH 3/3] Fix tests

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   | 17 ++++++++--
 .../MemRefToEmitC/MemRefToEmitC.cpp           |  8 +++--
 mlir/lib/Dialect/EmitC/IR/EmitC.cpp           | 15 ++++-----
 .../MemRefToEmitC/memref-to-emitc.mlir        | 24 +++++++-------
 mlir/test/Dialect/EmitC/invalid_ops.mlir      |  6 ++--
 mlir/test/Target/Cpp/for.mlir                 | 32 +++++++++++++------
 mlir/test/Target/Cpp/variable.mlir            |  4 +--
 7 files changed, 66 insertions(+), 40 deletions(-)

diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
index 1778c22deea94..776d59a55a732 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -1263,11 +1263,22 @@ def EmitC_SubscriptOp : EmitC_Op<"subscript", []> {
 
   let builders = [
     OpBuilder<(ins "TypedValue<ArrayType>":$array, "ValueRange":$indices), [{
-      build($_builder, $_state, array.getType().getElementType(), array, indices);
+      build(
+        $_builder,
+        $_state,
+        emitc::LValueType::get(array.getType().getElementType()),
+        array,
+        indices
+      );
     }]>,
     OpBuilder<(ins "TypedValue<PointerType>":$pointer, "Value":$index), [{
-      build($_builder, $_state, pointer.getType().getPointee(), pointer,
-            ValueRange{index});
+      build(
+        $_builder,
+        $_state,
+        emitc::LValueType::get(pointer.getType().getPointee()),
+        pointer,
+        ValueRange{index}
+      );
     }]>
   ];
 
diff --git a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
index d78b32c3acbd5..30e0fc77a4513 100644
--- a/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
+++ b/mlir/lib/Conversion/MemRefToEmitC/MemRefToEmitC.cpp
@@ -158,8 +158,12 @@ struct ConvertStore final : public OpConversionPattern<memref::StoreOp> {
 
     auto subscript = rewriter.create<emitc::SubscriptOp>(
         op.getLoc(), arrayValue, operands.getIndices());
-        
-    rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript,
+
+    subscript.dump();
+    subscript.getResult().dump();
+    operands.getValue().dump();
+
+    rewriter.replaceOpWithNewOp<emitc::AssignOp>(op, subscript.getResult(),
                                                  operands.getValue());
     return success();
   }
diff --git a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
index 4b9275cb51365..14e7a62411652 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -206,23 +206,18 @@ LogicalResult ApplyOp::verify() {
 /// The assign op requires that the assigned value's type matches the
 /// assigned-to variable type.
 LogicalResult emitc::AssignOp::verify() {
-  Value variable = getVar();
+  TypedValue<emitc::LValueType> variable = getVar();
 
   if (!variable.getDefiningOp())
     return emitOpError() << "cannot assign to block argument";
-  if (!llvm::isa<emitc::LValueType>(variable.getType()))
-    return emitOpError() << "requires first operand (" << variable
-                         << ") to be an lvalue";
 
   Type valueType = getValue().getType();
-  Type variableType = variable.getType().cast<emitc::LValueType>().getValue();
+  Type variableType = variable.getType().getValue();
   if (variableType != valueType)
     return emitOpError() << "requires value's type (" << valueType
                          << ") to match variable's type (" << variableType
                          << ")\n  variable: " << variable
                          << "\n  value: " << getValue() << "\n";
-  if (isa<ArrayType>(variableType))
-    return emitOpError() << "cannot assign to array type";
   return success();
 }
 
@@ -1021,9 +1016,13 @@ emitc::ArrayType::cloneWith(std::optional<ArrayRef<int64_t>> shape,
 LogicalResult mlir::emitc::LValueType::verify(
     llvm::function_ref<mlir::InFlightDiagnostic()> emitError,
     mlir::Type value) {
+  if (llvm::isa<emitc::ArrayType>(value)) {
+    return emitError()
+           << "!emitc.lvalue cannot wrap !emitc.array type";
+  }
   if (llvm::isa<emitc::LValueType>(value)) {
     return emitError()
-           << "!emitc.lvalue type cannot be nested inside another type";
+           << "!emitc.lvalue types cannot be nested";
   }
   return success();
 }
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc.mlir
index bc40ef48268eb..15ffd815250d2 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc.mlir
@@ -3,11 +3,11 @@
 // CHECK-LABEL: memref_store
 // CHECK-SAME:  %[[v:.*]]: f32, %[[i:.*]]: index, %[[j:.*]]: index
 func.func @memref_store(%v : f32, %i: index, %j: index) {
-  // CHECK: %[[ALLOCA:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4x8xf32>
+  // CHECK-NEXT: %[[ALLOCA:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4x8xf32>
   %0 = memref.alloca() : memref<4x8xf32>
 
-  // CHECK: %[[SUBSCRIPT:.*]] = emitc.subscript %[[ALLOCA]][%[[i]], %[[j]]] : (!emitc.array<4x8xf32>, index, index) -> f32
-  // CHECK: emitc.assign %[[v]] : f32 to %[[SUBSCRIPT:.*]] : f32
+  // CHECK-NEXT: %[[SUBSCRIPT:.*]] = emitc.subscript %[[ALLOCA]][%[[i]], %[[j]]] : (!emitc.array<4x8xf32>, index, index) -> !emitc.lvalue<f32>
+  // CHECK-NEXT: emitc.assign %[[v]] : f32 to %[[SUBSCRIPT]] : <f32>
   memref.store %v, %0[%i, %j] : memref<4x8xf32>
   return
 }
@@ -17,14 +17,13 @@ func.func @memref_store(%v : f32, %i: index, %j: index) {
 // CHECK-LABEL: memref_load
 // CHECK-SAME:  %[[i:.*]]: index, %[[j:.*]]: index
 func.func @memref_load(%i: index, %j: index) -> f32 {
-  // CHECK: %[[ALLOCA:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4x8xf32>
+  // CHECK-NEXT: %[[ALLOCA:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4x8xf32>
   %0 = memref.alloca() : memref<4x8xf32>
 
-  // CHECK: %[[LOAD:.*]] = emitc.subscript %[[ALLOCA]][%[[i]], %[[j]]] : (!emitc.array<4x8xf32>, index, index) -> f32
-  // CHECK: %[[VAR:.*]] = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> f32
-  // CHECK: emitc.assign %[[LOAD]] : f32 to %[[VAR]] : f32
+  // CHECK-NEXT: %[[SUBSCRIPT:.*]] = emitc.subscript %[[ALLOCA]][%[[i]], %[[j]]] : (!emitc.array<4x8xf32>, index, index) -> !emitc.lvalue<f32>
+  // CHECK-NEXT: %[[LOAD:.*]] = emitc.lvalue_load %[[SUBSCRIPT]] : <f32>
   %1 = memref.load %0[%i, %j] : memref<4x8xf32>
-  // CHECK: return %[[VAR]] : f32
+  // CHECK-NEXT: return %[[LOAD]] : f32
   return %1 : f32
 }
 
@@ -33,14 +32,15 @@ func.func @memref_load(%i: index, %j: index) -> f32 {
 // CHECK-LABEL: globals
 module @globals {
   memref.global "private" constant @internal_global : memref<3x7xf32> = dense<4.0>
-  // CHECK: emitc.global static const @internal_global : !emitc.array<3x7xf32> = dense<4.000000e+00>
+  // CHECK-NEXT: emitc.global static const @internal_global : !emitc.array<3x7xf32> = dense<4.000000e+00>
   memref.global @public_global : memref<3x7xf32>
-  // CHECK: emitc.global extern @public_global : !emitc.array<3x7xf32>
+  // CHECK-NEXT: emitc.global extern @public_global : !emitc.array<3x7xf32>
   memref.global @uninitialized_global : memref<3x7xf32> = uninitialized
-  // CHECK: emitc.global extern @uninitialized_global : !emitc.array<3x7xf32>
+  // CHECK-NEXT: emitc.global extern @uninitialized_global : !emitc.array<3x7xf32>
 
+  // CHECK-LABEL: use_global
   func.func @use_global() {
-    // CHECK: emitc.get_global @public_global : !emitc.array<3x7xf32>
+    // CHECK-NEXT: emitc.get_global @public_global : !emitc.array<3x7xf32>
     %0 = memref.get_global @public_global : memref<3x7xf32>
     return
   }
diff --git a/mlir/test/Dialect/EmitC/invalid_ops.mlir b/mlir/test/Dialect/EmitC/invalid_ops.mlir
index d58d13c9f907d..d96ae2b146f1c 100644
--- a/mlir/test/Dialect/EmitC/invalid_ops.mlir
+++ b/mlir/test/Dialect/EmitC/invalid_ops.mlir
@@ -252,9 +252,9 @@ func.func @test_assign_type_mismatch(%arg1: f32) {
 // -----
 
 func.func @test_assign_to_array(%arg1: !emitc.array<4xi32>) {
-  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.lvalue<!emitc.array<4xi32>>
-  // expected-error @+1 {{'emitc.assign' op cannot assign to array type}}
-  emitc.assign %arg1 : !emitc.array<4xi32> to %v : !emitc.lvalue<!emitc.array<4xi32>>
+  %v = "emitc.variable"() <{value = #emitc.opaque<"">}> : () -> !emitc.array<4xi32>
+  // expected-error @+1 {{invalid kind of Type specified}}
+  emitc.assign %arg1 : !emitc.array<4xi32> to %v : !emitc.array<4xi32>
   return
 }
 
diff --git a/mlir/test/Target/Cpp/for.mlir b/mlir/test/Target/Cpp/for.mlir
index f8faf83983494..14d3f05d6a08e 100644
--- a/mlir/test/Target/Cpp/for.mlir
+++ b/mlir/test/Target/Cpp/for.mlir
@@ -75,13 +75,17 @@ func.func @test_for_yield() {
 // CPP-DEFAULT-NEXT: [[SI:[^ ]*]] = [[S0]];
 // CPP-DEFAULT-NEXT: [[PI:[^ ]*]] = [[P0]];
 // CPP-DEFAULT-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
-// CPP-DEFAULT-NEXT: int32_t [[SN:[^ ]*]] = add([[SI]], [[ITER]]);
-// CPP-DEFAULT-NEXT: float [[PN:[^ ]*]] = mul([[PI]], [[ITER]]);
+// CPP-DEFAULT-NEXT: int32_t [[SI_LOAD:[^ ]*]] = [[SI]];
+// CPP-DEFAULT-NEXT: int32_t [[SN:[^ ]*]] = add([[SI_LOAD]], [[ITER]]);
+// CPP-DEFAULT-NEXT: float [[PI_LOAD:[^ ]*]] = [[PI]];
+// CPP-DEFAULT-NEXT: float [[PN:[^ ]*]] = mul([[PI_LOAD]], [[ITER]]);
 // CPP-DEFAULT-NEXT: [[SI]] = [[SN]];
 // CPP-DEFAULT-NEXT: [[PI]] = [[PN]];
 // CPP-DEFAULT-NEXT: }
-// CPP-DEFAULT-NEXT: [[SE]] = [[SI]];
-// CPP-DEFAULT-NEXT: [[PE]] = [[PI]];
+// CPP-DEFAULT-NEXT: int32_t [[SI_LOAD2:[^ ]*]] = [[SI]];
+// CPP-DEFAULT-NEXT: [[SE]] = [[SI_LOAD2]];
+// CPP-DEFAULT-NEXT: float [[PI_LOAD2:[^ ]*]] = [[PI]];
+// CPP-DEFAULT-NEXT: [[PE]] = [[PI_LOAD2]];
 // CPP-DEFAULT-NEXT: return;
 
 // CPP-DECLTOP: void test_for_yield() {
@@ -94,8 +98,12 @@ func.func @test_for_yield() {
 // CPP-DECLTOP-NEXT: float [[PE:[^ ]*]];
 // CPP-DECLTOP-NEXT: int32_t [[SI:[^ ]*]];
 // CPP-DECLTOP-NEXT: float [[PI:[^ ]*]];
+// CPP-DECLTOP-NEXT: int32_t [[SI_LOAD:[^ ]*]];
 // CPP-DECLTOP-NEXT: int32_t [[SN:[^ ]*]];
+// CPP-DECLTOP-NEXT: float [[PI_LOAD:[^ ]*]];
 // CPP-DECLTOP-NEXT: float [[PN:[^ ]*]];
+// CPP-DECLTOP-NEXT: int32_t [[SI_LOAD2:[^ ]*]];
+// CPP-DECLTOP-NEXT: float [[PI_LOAD2:[^ ]*]];
 // CPP-DECLTOP-NEXT: [[START]] = 0;
 // CPP-DECLTOP-NEXT: [[STOP]] = 10;
 // CPP-DECLTOP-NEXT: [[STEP]] = 1;
@@ -105,16 +113,20 @@ func.func @test_for_yield() {
 // CPP-DECLTOP-NEXT: ;
 // CPP-DECLTOP-NEXT: ;
 // CPP-DECLTOP-NEXT: ;
-// CPP-DECLTOP-NEXT: [[SI:[^ ]*]] = [[S0]];
-// CPP-DECLTOP-NEXT: [[PI:[^ ]*]] = [[P0]];
+// CPP-DECLTOP-NEXT: [[SI]] = [[S0]];
+// CPP-DECLTOP-NEXT: [[PI]] = [[P0]];
 // CPP-DECLTOP-NEXT: for (size_t [[ITER:[^ ]*]] = [[START]]; [[ITER]] < [[STOP]]; [[ITER]] += [[STEP]]) {
-// CPP-DECLTOP-NEXT: [[SN]] = add([[SI]], [[ITER]]);
-// CPP-DECLTOP-NEXT: [[PN]] = mul([[PI]], [[ITER]]);
+// CPP-DECLTOP-NEXT: [[SI_LOAD]] = [[SI]];
+// CPP-DECLTOP-NEXT: [[SN]] = add([[SI_LOAD]], [[ITER]]);
+// CPP-DECLTOP-NEXT: [[PI_LOAD]] = [[PI]];
+// CPP-DECLTOP-NEXT: [[PN]] = mul([[PI_LOAD]], [[ITER]]);
 // CPP-DECLTOP-NEXT: [[SI]] = [[SN]];
 // CPP-DECLTOP-NEXT: [[PI]] = [[PN]];
 // CPP-DECLTOP-NEXT: }
-// CPP-DECLTOP-NEXT: [[SE]] = [[SI]];
-// CPP-DECLTOP-NEXT: [[PE]] = [[PI]];
+// CPP-DECLTOP-NEXT: [[SI_LOAD2]] = [[SI]];
+// CPP-DECLTOP-NEXT: [[SE]] = [[SI_LOAD2]];
+// CPP-DECLTOP-NEXT: [[PI_LOAD2]] = [[PI]];
+// CPP-DECLTOP-NEXT: [[PE]] = [[PI_LOAD2]];
 // CPP-DECLTOP-NEXT: return;
 
 func.func @test_for_yield_2() {
diff --git a/mlir/test/Target/Cpp/variable.mlir b/mlir/test/Target/Cpp/variable.mlir
index 435d47e18dff5..a26d724127cf2 100644
--- a/mlir/test/Target/Cpp/variable.mlir
+++ b/mlir/test/Target/Cpp/variable.mlir
@@ -9,8 +9,8 @@ func.func @emitc_variable() {
   %c4 = "emitc.variable"(){value = 255 : ui8} : () -> !emitc.lvalue<ui8>
   %c5 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<!emitc.ptr<i32>>
   %c6 = "emitc.variable"(){value = #emitc.opaque<"NULL">} : () -> !emitc.lvalue<!emitc.ptr<i32>>
-  %c7 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<!emitc.array<3x7xi32>>
-  %c8 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.lvalue<!emitc.array<5x!emitc.ptr<i8>>>
+  %c7 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<3x7xi32>
+  %c8 = "emitc.variable"(){value = #emitc.opaque<"">} : () -> !emitc.array<5x!emitc.ptr<i8>>
   return
 }
 // CPP-DEFAULT: void emitc_variable() {



More information about the Mlir-commits mailing list