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

Simon Camphausen llvmlistbot at llvm.org
Wed May 8 07:12:25 PDT 2024


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

This is an early unpolished version of what has been previously discussed on [discourse](https://discourse.llvm.org/t/rfc-separate-variables-from-ssa-values-in-emitc/75224/9). See this as a starting point for further discussions.

>From aecefecdaa4c1354aa43fe766b8c1abc89e51836 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] [WIP][mlir][EmitC] Model lvalues as a type in EmitC

---
 mlir/include/mlir/Dialect/EmitC/IR/EmitC.td   |  24 +++-
 .../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, 391 insertions(+), 162 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 78bfd561171f5..0e1c526507bc0 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitC.td
@@ -836,6 +836,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 = [{
@@ -1011,7 +1027,7 @@ def EmitC_VariableOp : EmitC_Op<"variable", []> {
   }];
 
   let arguments = (ins EmitC_OpaqueOrTypedAttr:$value);
-  let results = (outs AnyType);
+  let results = (outs EmitC_LValueType);
 
   let hasVerifier = 1;
 }
@@ -1070,7 +1086,7 @@ def EmitC_AssignOp : EmitC_Op<"assign", []> {
     ```
   }];
 
-  let arguments = (ins AnyType:$var, AnyType:$value);
+  let arguments = (ins EmitC_LValueType:$var, AnyType:$value);
   let results = (outs);
 
   let hasVerifier = 1;
@@ -1158,7 +1174,7 @@ def EmitC_IfOp : EmitC_Op<"if",
 def EmitC_SubscriptOp : EmitC_Op<"subscript",
   [TypesMatchWith<"result type matches element type of 'array'",
                   "array", "result",
-                  "::llvm::cast<ArrayType>($_self).getElementType()">]> {
+                  "LValueType::get(::llvm::cast<ArrayType>($_self).getElementType())">]> {
   let summary = "Array subscript operation";
   let description = [{
     With the `subscript` operation the subscript operator `[]` can be applied
@@ -1174,7 +1190,7 @@ def EmitC_SubscriptOp : EmitC_Op<"subscript",
   }];
   let arguments = (ins Arg<EmitC_ArrayType, "the reference to load from">:$array,
                        Variadic<IntegerIndexOrOpaqueType>:$indices);
-  let results = (outs AnyType:$result);
+  let results = (outs EmitC_LValueType:$result);
 
   let builders = [
     OpBuilder<(ins "Value":$array, "ValueRange":$indices), [{
diff --git a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
index a2ba45a1f6a12..79bb2b4af4e46 100644
--- a/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
+++ b/mlir/include/mlir/Dialect/EmitC/IR/EmitCTypes.td
@@ -74,6 +74,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 e401a83bcb42e..5b297ef4085f2 100644
--- a/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
+++ b/mlir/lib/Dialect/EmitC/IR/EmitC.cpp
@@ -68,6 +68,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)
@@ -131,18 +133,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();
 }
@@ -698,6 +703,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
 //===----------------------------------------------------------------------===//
@@ -851,6 +897,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 bc49d7cd67126..787215ddde449 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -354,6 +354,13 @@ static LogicalResult printOperation(CppEmitter &emitter,
   return emitter.emitOperand(assignOp.getValue());
 }
 
+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.
@@ -638,10 +645,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();
 }
@@ -925,7 +938,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))))
@@ -1116,10 +1131,28 @@ 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())) {
       valueMapper.insert(val, getSubscriptName(subscript));
+    } 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()));
     }
@@ -1369,8 +1402,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)
@@ -1439,8 +1476,9 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
                 emitc::ConditionalOp, emitc::ConstantOp, emitc::DeclareFuncOp,
                 emitc::DivOp, emitc::ExpressionOp, emitc::ForOp, emitc::FuncOp,
                 emitc::IfOp, emitc::IncludeOp, emitc::LogicalAndOp,
-                emitc::LogicalNotOp, emitc::LogicalOrOp, emitc::MulOp,
-                emitc::RemOp, emitc::ReturnOp, emitc::SubOp, emitc::SubscriptOp,
+                emitc::LogicalNotOp, emitc::LogicalOrOp,
+                emitc::LValueToRValueOp, emitc::MulOp, emitc::RemOp,
+                emitc::ReturnOp, emitc::SubOp, emitc::SubscriptOp,
                 emitc::UnaryMinusOp, emitc::UnaryPlusOp, emitc::VariableOp,
                 emitc::VerbatimOp>(
               [&](auto op) { return printOperation(*this, op); })
@@ -1455,7 +1493,7 @@ LogicalResult CppEmitter::emitOperation(Operation &op, bool trailingSemicolon) {
   if (failed(status))
     return failure();
 
-  if (isa<emitc::LiteralOp, emitc::SubscriptOp>(op))
+  if (isa<emitc::LiteralOp, emitc::LValueToRValueOp, emitc::SubscriptOp>(op))
     return success();
 
   if (getEmittedExpression() ||
@@ -1545,6 +1583,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 6294c853d9993..1ea3420353a9d 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 5f00a295ed740..ca5be5619688d 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 a87b33a10844d..b4cb23e7bb85a 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 9ec9dcc3c6a84..0b93b666fa63a 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 5225f3ddaff25..9d41a478ad057 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 a6c82df9111a7..9bb0740fa4602 100644
--- a/mlir/test/Target/Cpp/subscript.mlir
+++ b/mlir/test/Target/Cpp/subscript.mlir
@@ -4,7 +4,8 @@
 func.func @load_store(%arg0: !emitc.array<4x8xf32>, %arg1: !emitc.array<3x5xf32>, %arg2: index, %arg3: index) {
   %0 = emitc.subscript %arg0[%arg2, %arg3] : <4x8xf32>, index, index
   %1 = emitc.subscript %arg1[%arg2, %arg3] : <3x5xf32>, index, index
-  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(float [[ARR1:[^ ]*]][4][8], float [[ARR2:[^ ]*]][3][5],
@@ -20,9 +21,13 @@ emitc.func @call_arg(%arg0: !emitc.array<4x8xf32>, %i: i32, %j: i16,
   %0 = emitc.subscript %arg0[%i, %j] : <4x8xf32>, i32, i16
   %1 = emitc.subscript %arg0[%j, %k] : <4x8xf32>, i16, i8
 
-  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: ;



More information about the Mlir-commits mailing list