Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[MLIR][Target/Cpp] Natural induction variable naming. #136102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ndegener-amd
Copy link

Changed naming of loop induction variables to follow natural naming (i, j, k, ...). This helps readability and locating positions referred to. Created new scopes to represent different behavior at function and loop level, to still enable re-using value names between different functions (as before). Removed unused scoping at other levels.

Changed naming of loop induction variables to follow natural naming (i, j, k, ...). This helps readability and locating positions referred to.
Created new scopes to represent different behavior at function and loop level, to still enable re-using value names between different functions (as before).
Removed unused scoping at other levels.
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot
Copy link
Member

llvmbot commented Apr 17, 2025

@llvm/pr-subscribers-mlir

Author: Niklas Degener (ndegener-amd)

Changes

Changed naming of loop induction variables to follow natural naming (i, j, k, ...). This helps readability and locating positions referred to. Created new scopes to represent different behavior at function and loop level, to still enable re-using value names between different functions (as before). Removed unused scoping at other levels.


Full diff: https://github.com/llvm/llvm-project/pull/136102.diff

2 Files Affected:

  • (modified) mlir/lib/Target/Cpp/TranslateToCpp.cpp (+87-26)
  • (added) mlir/test/Target/Cpp/for_loop_induction_vars.mlir (+85)
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 81016ed2e9a8d..43a820a3c7161 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -185,6 +185,10 @@ struct CppEmitter {
   /// Return the existing or a new name for a Value.
   StringRef getOrCreateName(Value val);
 
+  /// Return the existing or a new name for a loop induction variable of an
+  /// emitc::ForOp.
+  StringRef getOrCreateName(emitc::ForOp forOp);
+
   // Returns the textual representation of a subscript operation.
   std::string getSubscriptName(emitc::SubscriptOp op);
 
@@ -200,23 +204,39 @@ struct CppEmitter {
   /// Whether to map an mlir integer to a unsigned integer in C++.
   bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
 
-  /// RAII helper function to manage entering/exiting C++ scopes.
+  /// Abstract RAII helper function to manage entering/exiting C++ scopes.
   struct Scope {
+    ~Scope() { emitter.labelInScopeCount.pop(); }
+
+  private:
+    llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
+    llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
+
+  protected:
     Scope(CppEmitter &emitter)
         : valueMapperScope(emitter.valueMapper),
           blockMapperScope(emitter.blockMapper), emitter(emitter) {
-      emitter.valueInScopeCount.push(emitter.valueInScopeCount.top());
       emitter.labelInScopeCount.push(emitter.labelInScopeCount.top());
     }
-    ~Scope() {
-      emitter.valueInScopeCount.pop();
-      emitter.labelInScopeCount.pop();
+    CppEmitter &emitter;
+  };
+
+  /// RAII helper function to manage entering/exiting functions, while re-using
+  /// value names.
+  struct FunctionScope : Scope {
+    FunctionScope(CppEmitter &emitter) : Scope(emitter) {
+      // Re-use value names
+      emitter.resetValueCounter();
     }
+  };
 
-  private:
-    llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
-    llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
-    CppEmitter &emitter;
+  /// RAII helper function to manage entering/exiting emitc::forOp loops and
+  /// handle induction variable naming.
+  struct LoopScope : Scope {
+    LoopScope(CppEmitter &emitter) : Scope(emitter) {
+      emitter.increaseLoopNestingLevel();
+    }
+    ~LoopScope() { emitter.decreaseLoopNestingLevel(); }
   };
 
   /// Returns wether the Value is assigned to a C++ variable in the scope.
@@ -252,6 +272,15 @@ struct CppEmitter {
     return operandExpression == emittedExpression;
   };
 
+  // Resets the value counter to 0
+  void resetValueCounter();
+
+  // Increases the loop nesting level by 1
+  void increaseLoopNestingLevel();
+
+  // Decreases the loop nesting level by 1
+  void decreaseLoopNestingLevel();
+
 private:
   using ValueMapper = llvm::ScopedHashTable<Value, std::string>;
   using BlockMapper = llvm::ScopedHashTable<Block *, std::string>;
@@ -273,11 +302,19 @@ struct CppEmitter {
   /// Map from block to name of C++ label.
   BlockMapper blockMapper;
 
-  /// The number of values in the current scope. This is used to declare the
-  /// names of values in a scope.
-  std::stack<int64_t> valueInScopeCount;
+  /// Default values representing outermost scope
+  llvm::ScopedHashTableScope<Value, std::string> defaultValueMapperScope;
+  llvm::ScopedHashTableScope<Block *, std::string> defaultBlockMapperScope;
+
   std::stack<int64_t> labelInScopeCount;
 
+  /// Keeps track of the amount of nested loops the emitter currently operates
+  /// in.
+  uint64_t loopNestingLevel{0};
+
+  /// Emitter-level count of created values to enable unique identifiers.
+  unsigned int valueCount{0};
+
   /// State of the current expression being emitted.
   ExpressionOp emittedExpression;
   SmallVector<int> emittedExpressionPrecedence;
@@ -851,7 +888,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 }
 
 static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
-
   raw_indented_ostream &os = emitter.ostream();
 
   // Utility function to determine whether a value is an expression that will be
@@ -870,12 +906,12 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
           emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
     return failure();
   os << " ";
-  os << emitter.getOrCreateName(forOp.getInductionVar());
+  os << emitter.getOrCreateName(forOp);
   os << " = ";
   if (failed(emitter.emitOperand(forOp.getLowerBound())))
     return failure();
   os << "; ";
-  os << emitter.getOrCreateName(forOp.getInductionVar());
+  os << emitter.getOrCreateName(forOp);
   os << " < ";
   Value upperBound = forOp.getUpperBound();
   bool upperBoundRequiresParentheses = requiresParentheses(upperBound);
@@ -886,13 +922,15 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
   if (upperBoundRequiresParentheses)
     os << ")";
   os << "; ";
-  os << emitter.getOrCreateName(forOp.getInductionVar());
+  os << emitter.getOrCreateName(forOp);
   os << " += ";
   if (failed(emitter.emitOperand(forOp.getStep())))
     return failure();
   os << ") {\n";
   os.indent();
 
+  CppEmitter::LoopScope lScope(emitter);
+
   Region &forRegion = forOp.getRegion();
   auto regionOps = forRegion.getOps();
 
@@ -979,8 +1017,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 }
 
 static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) {
-  CppEmitter::Scope scope(emitter);
-
   for (Operation &op : moduleOp) {
     if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
       return failure();
@@ -992,8 +1028,6 @@ static LogicalResult printOperation(CppEmitter &emitter, FileOp file) {
   if (!emitter.shouldEmitFile(file))
     return success();
 
-  CppEmitter::Scope scope(emitter);
-
   for (Operation &op : file) {
     if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
       return failure();
@@ -1109,7 +1143,7 @@ static LogicalResult printOperation(CppEmitter &emitter,
     return functionOp.emitOpError() << "cannot emit array type as result type";
   }
 
-  CppEmitter::Scope scope(emitter);
+  CppEmitter::FunctionScope scope(emitter);
   raw_indented_ostream &os = emitter.ostream();
   if (failed(emitter.emitTypes(functionOp.getLoc(),
                                functionOp.getFunctionType().getResults())))
@@ -1137,7 +1171,7 @@ static LogicalResult printOperation(CppEmitter &emitter,
         "with multiple blocks needs variables declared at top");
   }
 
-  CppEmitter::Scope scope(emitter);
+  CppEmitter::FunctionScope scope(emitter);
   raw_indented_ostream &os = emitter.ostream();
   if (functionOp.getSpecifiers()) {
     for (Attribute specifier : functionOp.getSpecifiersAttr()) {
@@ -1171,7 +1205,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     DeclareFuncOp declareFuncOp) {
-  CppEmitter::Scope scope(emitter);
   raw_indented_ostream &os = emitter.ostream();
 
   auto functionOp = SymbolTable::lookupNearestSymbolFrom<emitc::FuncOp>(
@@ -1203,8 +1236,8 @@ static LogicalResult printOperation(CppEmitter &emitter,
 CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop,
                        StringRef fileId)
     : os(os), declareVariablesAtTop(declareVariablesAtTop),
-      fileId(fileId.str()) {
-  valueInScopeCount.push(0);
+      fileId(fileId.str()), defaultValueMapperScope(valueMapper),
+      defaultBlockMapperScope(blockMapper) {
   labelInScopeCount.push(0);
 }
 
@@ -1245,7 +1278,29 @@ StringRef CppEmitter::getOrCreateName(Value val) {
     assert(!hasDeferredEmission(val.getDefiningOp()) &&
            "cacheDeferredOpResult should have been called on this value, "
            "update the emitOperation function.");
-    valueMapper.insert(val, formatv("v{0}", ++valueInScopeCount.top()));
+
+    valueMapper.insert(val, formatv("v{0}", ++valueCount));
+  }
+  return *valueMapper.begin(val);
+}
+
+/// Return the existing or a new name for a loop induction variable Value.
+/// Loop induction variables follow natural naming: i, j, k,...
+StringRef CppEmitter::getOrCreateName(emitc::ForOp forOp) {
+  Value val = forOp.getInductionVar();
+
+  if (!valueMapper.count(val)) {
+
+    int64_t identifier = 'i' + loopNestingLevel;
+
+    if (identifier >= 'i' && identifier <= 'z') {
+      valueMapper.insert(val,
+                         formatv("{0}_{1}", (char)identifier, ++valueCount));
+    } else {
+      // If running out of letters, continue with zX
+      valueMapper.insert(
+          val, formatv("z{0}_{1}", identifier - 'z' - 1, ++valueCount));
+    }
   }
   return *valueMapper.begin(val);
 }
@@ -1784,6 +1839,12 @@ LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef<Type> types) {
   return success();
 }
 
+void CppEmitter::resetValueCounter() { valueCount = 0; }
+
+void CppEmitter::increaseLoopNestingLevel() { loopNestingLevel++; }
+
+void CppEmitter::decreaseLoopNestingLevel() { loopNestingLevel--; }
+
 LogicalResult emitc::translateToCpp(Operation *op, raw_ostream &os,
                                     bool declareVariablesAtTop,
                                     StringRef fileId) {
diff --git a/mlir/test/Target/Cpp/for_loop_induction_vars.mlir b/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
new file mode 100644
index 0000000000000..3995a4a904ed4
--- /dev/null
+++ b/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
@@ -0,0 +1,85 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
+
+// CHECK-LABEL: test_for_siblings
+func.func @test_for_siblings() {
+  %start = emitc.literal "0" : index
+  %stop = emitc.literal "10" : index
+  %step = emitc.literal "1" : index
+
+  %var1 = "emitc.variable"() <{value = 0 : index}> : () -> !emitc.lvalue<index>
+  %var2 = "emitc.variable"() <{value = 0 : index}> : () -> !emitc.lvalue<index>
+
+  // CHECK: for (size_t [[ITER0:i_[0-9]*]] = {{.*}}; [[ITER0]] < {{.*}}; [[ITER0]] += {{.*}}) {
+  emitc.for %i0 = %start to %stop step %step {
+    // CHECK: for (size_t [[ITER1:j_[0-9]*]] = {{.*}}; [[ITER1]] < {{.*}}; [[ITER1]] += {{.*}}) {
+    emitc.for %i1 = %start to %stop step %step {
+      // CHECK: {{.*}} = [[ITER0]];
+      //"emitc.assign"(%var1,%i0) : (!emitc.lvalue<!emitc.size_t>, !emitc.size_t) -> ()
+      emitc.assign %i0 : index to %var1 : !emitc.lvalue<index>
+      // CHECK: {{.*}} = [[ITER1]];
+      //"emitc.assign"(%var2,%i1) : (!emitc.lvalue<!emitc.size_t>, !emitc.size_t) -> ()
+      emitc.assign %i1 : index to %var2 : !emitc.lvalue<index>
+    }
+  }
+  // CHECK: for (size_t [[ITER2:i_[0-9]*]] = {{.*}}; [[ITER2]] < {{.*}}; [[ITER2]] += {{.*}})
+  emitc.for %ki2 = %start to %stop step %step {
+    // CHECK: for (size_t [[ITER3:j_[0-9]*]] = {{.*}}; [[ITER3]] < {{.*}}; [[ITER3]] += {{.*}})
+    emitc.for %i3 = %start to %stop step %step {
+      %1 = emitc.call_opaque "f"() : () -> i32
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: test_for_nesting
+func.func @test_for_nesting() {
+  %start = emitc.literal "0" : index
+  %stop = emitc.literal "10" : index
+  %step = emitc.literal "1" : index
+
+  // CHECK-COUNT-18: for (size_t [[ITER:[i-z]_[0-9]*]] = {{.*}}; [[ITER]] < {{.*}}; [[ITER]] += {{.*}}) {
+  emitc.for %i0 = %start to %stop step %step {
+    emitc.for %i1 = %start to %stop step %step {
+      emitc.for %i2 = %start to %stop step %step {
+        emitc.for %i3 = %start to %stop step %step {
+          emitc.for %i4 = %start to %stop step %step {
+            emitc.for %i5 = %start to %stop step %step {
+              emitc.for %i6 = %start to %stop step %step {
+                emitc.for %i7 = %start to %stop step %step {
+                  emitc.for %i8 = %start to %stop step %step {
+                    emitc.for %i9 = %start to %stop step %step {
+                      emitc.for %i10 = %start to %stop step %step {
+                        emitc.for %i11 = %start to %stop step %step {
+                          emitc.for %i12 = %start to %stop step %step {
+                            emitc.for %i13 = %start to %stop step %step {
+                              emitc.for %i14 = %start to %stop step %step {
+                                emitc.for %i15 = %start to %stop step %step {
+                                  emitc.for %i16 = %start to %stop step %step {
+                                    emitc.for %i17 = %start to %stop step %step {
+                                      // CHECK: for (size_t [[ITERz0:z0_[0-9]*]] = {{.*}}; [[ITERz0]] < {{.*}}; [[ITERz0]] += {{.*}}) {
+                                      emitc.for %i18 = %start to %stop step %step {
+                                        // CHECK: for (size_t [[ITERz1:z1_[0-9]*]] = {{.*}}; [[ITERz1]] < {{.*}}; [[ITERz1]] += {{.*}}) {
+                                        emitc.for %i19 = %start to %stop step %step {
+                                          %0 = emitc.call_opaque "f"() : () -> i32
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  return
+}

@llvmbot
Copy link
Member

llvmbot commented Apr 17, 2025

@llvm/pr-subscribers-mlir-emitc

Author: Niklas Degener (ndegener-amd)

Changes

Changed naming of loop induction variables to follow natural naming (i, j, k, ...). This helps readability and locating positions referred to. Created new scopes to represent different behavior at function and loop level, to still enable re-using value names between different functions (as before). Removed unused scoping at other levels.


Full diff: https://github.com/llvm/llvm-project/pull/136102.diff

2 Files Affected:

  • (modified) mlir/lib/Target/Cpp/TranslateToCpp.cpp (+87-26)
  • (added) mlir/test/Target/Cpp/for_loop_induction_vars.mlir (+85)
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 81016ed2e9a8d..43a820a3c7161 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -185,6 +185,10 @@ struct CppEmitter {
   /// Return the existing or a new name for a Value.
   StringRef getOrCreateName(Value val);
 
+  /// Return the existing or a new name for a loop induction variable of an
+  /// emitc::ForOp.
+  StringRef getOrCreateName(emitc::ForOp forOp);
+
   // Returns the textual representation of a subscript operation.
   std::string getSubscriptName(emitc::SubscriptOp op);
 
@@ -200,23 +204,39 @@ struct CppEmitter {
   /// Whether to map an mlir integer to a unsigned integer in C++.
   bool shouldMapToUnsigned(IntegerType::SignednessSemantics val);
 
-  /// RAII helper function to manage entering/exiting C++ scopes.
+  /// Abstract RAII helper function to manage entering/exiting C++ scopes.
   struct Scope {
+    ~Scope() { emitter.labelInScopeCount.pop(); }
+
+  private:
+    llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
+    llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
+
+  protected:
     Scope(CppEmitter &emitter)
         : valueMapperScope(emitter.valueMapper),
           blockMapperScope(emitter.blockMapper), emitter(emitter) {
-      emitter.valueInScopeCount.push(emitter.valueInScopeCount.top());
       emitter.labelInScopeCount.push(emitter.labelInScopeCount.top());
     }
-    ~Scope() {
-      emitter.valueInScopeCount.pop();
-      emitter.labelInScopeCount.pop();
+    CppEmitter &emitter;
+  };
+
+  /// RAII helper function to manage entering/exiting functions, while re-using
+  /// value names.
+  struct FunctionScope : Scope {
+    FunctionScope(CppEmitter &emitter) : Scope(emitter) {
+      // Re-use value names
+      emitter.resetValueCounter();
     }
+  };
 
-  private:
-    llvm::ScopedHashTableScope<Value, std::string> valueMapperScope;
-    llvm::ScopedHashTableScope<Block *, std::string> blockMapperScope;
-    CppEmitter &emitter;
+  /// RAII helper function to manage entering/exiting emitc::forOp loops and
+  /// handle induction variable naming.
+  struct LoopScope : Scope {
+    LoopScope(CppEmitter &emitter) : Scope(emitter) {
+      emitter.increaseLoopNestingLevel();
+    }
+    ~LoopScope() { emitter.decreaseLoopNestingLevel(); }
   };
 
   /// Returns wether the Value is assigned to a C++ variable in the scope.
@@ -252,6 +272,15 @@ struct CppEmitter {
     return operandExpression == emittedExpression;
   };
 
+  // Resets the value counter to 0
+  void resetValueCounter();
+
+  // Increases the loop nesting level by 1
+  void increaseLoopNestingLevel();
+
+  // Decreases the loop nesting level by 1
+  void decreaseLoopNestingLevel();
+
 private:
   using ValueMapper = llvm::ScopedHashTable<Value, std::string>;
   using BlockMapper = llvm::ScopedHashTable<Block *, std::string>;
@@ -273,11 +302,19 @@ struct CppEmitter {
   /// Map from block to name of C++ label.
   BlockMapper blockMapper;
 
-  /// The number of values in the current scope. This is used to declare the
-  /// names of values in a scope.
-  std::stack<int64_t> valueInScopeCount;
+  /// Default values representing outermost scope
+  llvm::ScopedHashTableScope<Value, std::string> defaultValueMapperScope;
+  llvm::ScopedHashTableScope<Block *, std::string> defaultBlockMapperScope;
+
   std::stack<int64_t> labelInScopeCount;
 
+  /// Keeps track of the amount of nested loops the emitter currently operates
+  /// in.
+  uint64_t loopNestingLevel{0};
+
+  /// Emitter-level count of created values to enable unique identifiers.
+  unsigned int valueCount{0};
+
   /// State of the current expression being emitted.
   ExpressionOp emittedExpression;
   SmallVector<int> emittedExpressionPrecedence;
@@ -851,7 +888,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 }
 
 static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
-
   raw_indented_ostream &os = emitter.ostream();
 
   // Utility function to determine whether a value is an expression that will be
@@ -870,12 +906,12 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
           emitter.emitType(forOp.getLoc(), forOp.getInductionVar().getType())))
     return failure();
   os << " ";
-  os << emitter.getOrCreateName(forOp.getInductionVar());
+  os << emitter.getOrCreateName(forOp);
   os << " = ";
   if (failed(emitter.emitOperand(forOp.getLowerBound())))
     return failure();
   os << "; ";
-  os << emitter.getOrCreateName(forOp.getInductionVar());
+  os << emitter.getOrCreateName(forOp);
   os << " < ";
   Value upperBound = forOp.getUpperBound();
   bool upperBoundRequiresParentheses = requiresParentheses(upperBound);
@@ -886,13 +922,15 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
   if (upperBoundRequiresParentheses)
     os << ")";
   os << "; ";
-  os << emitter.getOrCreateName(forOp.getInductionVar());
+  os << emitter.getOrCreateName(forOp);
   os << " += ";
   if (failed(emitter.emitOperand(forOp.getStep())))
     return failure();
   os << ") {\n";
   os.indent();
 
+  CppEmitter::LoopScope lScope(emitter);
+
   Region &forRegion = forOp.getRegion();
   auto regionOps = forRegion.getOps();
 
@@ -979,8 +1017,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 }
 
 static LogicalResult printOperation(CppEmitter &emitter, ModuleOp moduleOp) {
-  CppEmitter::Scope scope(emitter);
-
   for (Operation &op : moduleOp) {
     if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
       return failure();
@@ -992,8 +1028,6 @@ static LogicalResult printOperation(CppEmitter &emitter, FileOp file) {
   if (!emitter.shouldEmitFile(file))
     return success();
 
-  CppEmitter::Scope scope(emitter);
-
   for (Operation &op : file) {
     if (failed(emitter.emitOperation(op, /*trailingSemicolon=*/false)))
       return failure();
@@ -1109,7 +1143,7 @@ static LogicalResult printOperation(CppEmitter &emitter,
     return functionOp.emitOpError() << "cannot emit array type as result type";
   }
 
-  CppEmitter::Scope scope(emitter);
+  CppEmitter::FunctionScope scope(emitter);
   raw_indented_ostream &os = emitter.ostream();
   if (failed(emitter.emitTypes(functionOp.getLoc(),
                                functionOp.getFunctionType().getResults())))
@@ -1137,7 +1171,7 @@ static LogicalResult printOperation(CppEmitter &emitter,
         "with multiple blocks needs variables declared at top");
   }
 
-  CppEmitter::Scope scope(emitter);
+  CppEmitter::FunctionScope scope(emitter);
   raw_indented_ostream &os = emitter.ostream();
   if (functionOp.getSpecifiers()) {
     for (Attribute specifier : functionOp.getSpecifiersAttr()) {
@@ -1171,7 +1205,6 @@ static LogicalResult printOperation(CppEmitter &emitter,
 
 static LogicalResult printOperation(CppEmitter &emitter,
                                     DeclareFuncOp declareFuncOp) {
-  CppEmitter::Scope scope(emitter);
   raw_indented_ostream &os = emitter.ostream();
 
   auto functionOp = SymbolTable::lookupNearestSymbolFrom<emitc::FuncOp>(
@@ -1203,8 +1236,8 @@ static LogicalResult printOperation(CppEmitter &emitter,
 CppEmitter::CppEmitter(raw_ostream &os, bool declareVariablesAtTop,
                        StringRef fileId)
     : os(os), declareVariablesAtTop(declareVariablesAtTop),
-      fileId(fileId.str()) {
-  valueInScopeCount.push(0);
+      fileId(fileId.str()), defaultValueMapperScope(valueMapper),
+      defaultBlockMapperScope(blockMapper) {
   labelInScopeCount.push(0);
 }
 
@@ -1245,7 +1278,29 @@ StringRef CppEmitter::getOrCreateName(Value val) {
     assert(!hasDeferredEmission(val.getDefiningOp()) &&
            "cacheDeferredOpResult should have been called on this value, "
            "update the emitOperation function.");
-    valueMapper.insert(val, formatv("v{0}", ++valueInScopeCount.top()));
+
+    valueMapper.insert(val, formatv("v{0}", ++valueCount));
+  }
+  return *valueMapper.begin(val);
+}
+
+/// Return the existing or a new name for a loop induction variable Value.
+/// Loop induction variables follow natural naming: i, j, k,...
+StringRef CppEmitter::getOrCreateName(emitc::ForOp forOp) {
+  Value val = forOp.getInductionVar();
+
+  if (!valueMapper.count(val)) {
+
+    int64_t identifier = 'i' + loopNestingLevel;
+
+    if (identifier >= 'i' && identifier <= 'z') {
+      valueMapper.insert(val,
+                         formatv("{0}_{1}", (char)identifier, ++valueCount));
+    } else {
+      // If running out of letters, continue with zX
+      valueMapper.insert(
+          val, formatv("z{0}_{1}", identifier - 'z' - 1, ++valueCount));
+    }
   }
   return *valueMapper.begin(val);
 }
@@ -1784,6 +1839,12 @@ LogicalResult CppEmitter::emitTupleType(Location loc, ArrayRef<Type> types) {
   return success();
 }
 
+void CppEmitter::resetValueCounter() { valueCount = 0; }
+
+void CppEmitter::increaseLoopNestingLevel() { loopNestingLevel++; }
+
+void CppEmitter::decreaseLoopNestingLevel() { loopNestingLevel--; }
+
 LogicalResult emitc::translateToCpp(Operation *op, raw_ostream &os,
                                     bool declareVariablesAtTop,
                                     StringRef fileId) {
diff --git a/mlir/test/Target/Cpp/for_loop_induction_vars.mlir b/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
new file mode 100644
index 0000000000000..3995a4a904ed4
--- /dev/null
+++ b/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
@@ -0,0 +1,85 @@
+// RUN: mlir-translate -mlir-to-cpp %s | FileCheck %s
+
+// CHECK-LABEL: test_for_siblings
+func.func @test_for_siblings() {
+  %start = emitc.literal "0" : index
+  %stop = emitc.literal "10" : index
+  %step = emitc.literal "1" : index
+
+  %var1 = "emitc.variable"() <{value = 0 : index}> : () -> !emitc.lvalue<index>
+  %var2 = "emitc.variable"() <{value = 0 : index}> : () -> !emitc.lvalue<index>
+
+  // CHECK: for (size_t [[ITER0:i_[0-9]*]] = {{.*}}; [[ITER0]] < {{.*}}; [[ITER0]] += {{.*}}) {
+  emitc.for %i0 = %start to %stop step %step {
+    // CHECK: for (size_t [[ITER1:j_[0-9]*]] = {{.*}}; [[ITER1]] < {{.*}}; [[ITER1]] += {{.*}}) {
+    emitc.for %i1 = %start to %stop step %step {
+      // CHECK: {{.*}} = [[ITER0]];
+      //"emitc.assign"(%var1,%i0) : (!emitc.lvalue<!emitc.size_t>, !emitc.size_t) -> ()
+      emitc.assign %i0 : index to %var1 : !emitc.lvalue<index>
+      // CHECK: {{.*}} = [[ITER1]];
+      //"emitc.assign"(%var2,%i1) : (!emitc.lvalue<!emitc.size_t>, !emitc.size_t) -> ()
+      emitc.assign %i1 : index to %var2 : !emitc.lvalue<index>
+    }
+  }
+  // CHECK: for (size_t [[ITER2:i_[0-9]*]] = {{.*}}; [[ITER2]] < {{.*}}; [[ITER2]] += {{.*}})
+  emitc.for %ki2 = %start to %stop step %step {
+    // CHECK: for (size_t [[ITER3:j_[0-9]*]] = {{.*}}; [[ITER3]] < {{.*}}; [[ITER3]] += {{.*}})
+    emitc.for %i3 = %start to %stop step %step {
+      %1 = emitc.call_opaque "f"() : () -> i32
+    }
+  }
+  return
+}
+
+// CHECK-LABEL: test_for_nesting
+func.func @test_for_nesting() {
+  %start = emitc.literal "0" : index
+  %stop = emitc.literal "10" : index
+  %step = emitc.literal "1" : index
+
+  // CHECK-COUNT-18: for (size_t [[ITER:[i-z]_[0-9]*]] = {{.*}}; [[ITER]] < {{.*}}; [[ITER]] += {{.*}}) {
+  emitc.for %i0 = %start to %stop step %step {
+    emitc.for %i1 = %start to %stop step %step {
+      emitc.for %i2 = %start to %stop step %step {
+        emitc.for %i3 = %start to %stop step %step {
+          emitc.for %i4 = %start to %stop step %step {
+            emitc.for %i5 = %start to %stop step %step {
+              emitc.for %i6 = %start to %stop step %step {
+                emitc.for %i7 = %start to %stop step %step {
+                  emitc.for %i8 = %start to %stop step %step {
+                    emitc.for %i9 = %start to %stop step %step {
+                      emitc.for %i10 = %start to %stop step %step {
+                        emitc.for %i11 = %start to %stop step %step {
+                          emitc.for %i12 = %start to %stop step %step {
+                            emitc.for %i13 = %start to %stop step %step {
+                              emitc.for %i14 = %start to %stop step %step {
+                                emitc.for %i15 = %start to %stop step %step {
+                                  emitc.for %i16 = %start to %stop step %step {
+                                    emitc.for %i17 = %start to %stop step %step {
+                                      // CHECK: for (size_t [[ITERz0:z0_[0-9]*]] = {{.*}}; [[ITERz0]] < {{.*}}; [[ITERz0]] += {{.*}}) {
+                                      emitc.for %i18 = %start to %stop step %step {
+                                        // CHECK: for (size_t [[ITERz1:z1_[0-9]*]] = {{.*}}; [[ITERz1]] < {{.*}}; [[ITERz1]] += {{.*}}) {
+                                        emitc.for %i19 = %start to %stop step %step {
+                                          %0 = emitc.call_opaque "f"() : () -> i32
+                                        }
+                                      }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+  return
+}

@ndegener-amd ndegener-amd changed the title Natural induction variable naming. [MLIR][Target/Cpp] Natural induction variable naming. Apr 17, 2025
@ndegener-amd
Copy link
Author

Requesting review from @simon-camp @marbre .
Thanks in advance!

@marbre
Copy link
Member

marbre commented Apr 24, 2025

Requesting review from @simon-camp @marbre . Thanks in advance!

Thanks for your PR @ndegener-amd. I unfortunately cannot spend too much time on reviews. @mgehre-amd do you mind to take a look as well to move this forward? Happy to spend some time next week on my side.

@mgehre-amd
Copy link
Contributor

Requesting review from @simon-camp @marbre . Thanks in advance!

Thanks for your PR @ndegener-amd. I unfortunately cannot spend too much time on reviews. @mgehre-amd do you mind to take a look as well to move this forward? Happy to spend some time next week on my side.

@ndegener-amd is in my team and I already reviewed internally. That's why I think some external opinion would be nice.

@simon-camp
Copy link
Contributor

I will take a look next week.

Copy link
Contributor

@aniragil aniragil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for readability improvements! Some comments inside.

Comment on lines +1297 to +1298
valueMapper.insert(val,
formatv("{0}_{1}", (char)identifier, ++valueCount));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could rely on C's scope rules and drop valueCount? (induction variables seem to ignore declareAtTop anyway)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C's scope rules were not used before. I suspect that general re-using of variable names increases debugging difficulty as locating the correct vars gets harder. Therefore, I stuck with the current scheme, where variables are only re-used between functions. This inconsistency leads to the valueCount being necessary to track the values, while letting the function scope reset it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function-level numbering kind of makes sense as the numbering scheme for variables that carry no scope-related semantics (or any other semantics, which is why they're all named v).
Induction variables OTOH are loop-scoped, so function-level numbering them seems unnatural to me (I don't define int i0 and int i1 as induction variables when writing sibling loops but rather define int i twice). It also leads to less readable expressions, e.g. v89[i22 + 3][j74 * 2][k38 - 1] vs v89[i + 3][j * 2][k - 1]). @simon-camp, @marbre, WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion with this; I'm fine with either choice.


int64_t identifier = 'i' + loopNestingLevel;

if (identifier >= 'i' && identifier <= 'z') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v is used for non-induction-variables, which may be confusing (any other letters worth reserving for other types of variables, e.g. p for pointers?)
Are Fortran's i..n 6 nesting levels enough? Better to double them to i..t?

Copy link
Author

@ndegener-amd ndegener-amd Apr 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In practice, the 14 nesting levels to reach v should rarely be necessary. But emitc does not restrict deep nesting, so it should be handled properly. I had hope, that the differentiation between regular vars (v0) and induction vars in the context of loop nests (v_0) would be enough. It would also be possible to start the z-continuation already at u. So the scheme would be i..u and afterwards u0, u1, ..., uX. What do you think of this?
Regarding other variable types: As there are also other changes to naming of special types possible (e.g. references), I would not keep it in mind for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would also be possible to start the z-continuation already at u. So the scheme would be i..u and afterwards u0, u1, ..., uX. What do you think of this?

Sure, since the letter to stop at is quite arbitrary u makes as good a choice as any (to me btw it looks more natural to have i..t and then numbered u0, u1).

Regarding other variable types: As there are also other changes to naming of special types possible (e.g. references), I would not keep it in mind for now.

Sure, we can later narrow the range further if needed.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that idea, the discontinuity between t and u then also better signals the change in naming behavior. I will adjust the naming to fit this scheme.

Copy link
Contributor

@simon-camp simon-camp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Thanks for the improvement.

Comment on lines +1297 to +1298
valueMapper.insert(val,
formatv("{0}_{1}", (char)identifier, ++valueCount));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion with this; I'm fine with either choice.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants