[Mlir-commits] [mlir] [MLIR][Target/Cpp] Natural induction variable naming. (PR #136102)
Niklas Degener
llvmlistbot at llvm.org
Mon May 5 00:25:49 PDT 2025
https://github.com/ndegener-amd updated https://github.com/llvm/llvm-project/pull/136102
>From c5cd08cc12e992dea108d7712ae13f38788269a3 Mon Sep 17 00:00:00 2001
From: ndegener-amd <niklas.degener at amd.com>
Date: Tue, 8 Apr 2025 06:35:19 -0600
Subject: [PATCH 1/4] Natural induction variable naming.
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.
---
mlir/lib/Target/Cpp/TranslateToCpp.cpp | 113 ++++++++++++++----
.../Target/Cpp/for_loop_induction_vars.mlir | 85 +++++++++++++
2 files changed, 172 insertions(+), 26 deletions(-)
create mode 100644 mlir/test/Target/Cpp/for_loop_induction_vars.mlir
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
+}
>From 367a8dde8d44f6cf8645f3eff7e76260cab8c5c3 Mon Sep 17 00:00:00 2001
From: ndegener-amd <niklas.degener at amd.com>
Date: Mon, 28 Apr 2025 02:51:51 -0600
Subject: [PATCH 2/4] Updated naming of getOrCreateName func for forOps
---
mlir/lib/Target/Cpp/TranslateToCpp.cpp | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 43a820a3c7161..6aae665c3075f 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -187,7 +187,7 @@ struct CppEmitter {
/// Return the existing or a new name for a loop induction variable of an
/// emitc::ForOp.
- StringRef getOrCreateName(emitc::ForOp forOp);
+ StringRef getOrCreateInductionVarName(Value val);
// Returns the textual representation of a subscript operation.
std::string getSubscriptName(emitc::SubscriptOp op);
@@ -906,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);
+ os << emitter.getOrCreateInductionVarName(forOp.getInductionVar());
os << " = ";
if (failed(emitter.emitOperand(forOp.getLowerBound())))
return failure();
os << "; ";
- os << emitter.getOrCreateName(forOp);
+ os << emitter.getOrCreateName(forOp.getInductionVar());
os << " < ";
Value upperBound = forOp.getUpperBound();
bool upperBoundRequiresParentheses = requiresParentheses(upperBound);
@@ -922,7 +922,7 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
if (upperBoundRequiresParentheses)
os << ")";
os << "; ";
- os << emitter.getOrCreateName(forOp);
+ os << emitter.getOrCreateName(forOp.getInductionVar());
os << " += ";
if (failed(emitter.emitOperand(forOp.getStep())))
return failure();
@@ -1286,9 +1286,7 @@ StringRef CppEmitter::getOrCreateName(Value 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();
-
+StringRef CppEmitter::getOrCreateInductionVarName(Value val) {
if (!valueMapper.count(val)) {
int64_t identifier = 'i' + loopNestingLevel;
>From ab73ced9465b6e474d3a4d07e676a555be9d23b8 Mon Sep 17 00:00:00 2001
From: ndegener-amd <niklas.degener at amd.com>
Date: Fri, 2 May 2025 06:55:45 -0600
Subject: [PATCH 3/4] Adjusted naming to change at 't' rather than 'z'
---
mlir/lib/Target/Cpp/TranslateToCpp.cpp | 4 ++--
.../Target/Cpp/for_loop_induction_vars.mlir | 24 +++++--------------
2 files changed, 8 insertions(+), 20 deletions(-)
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 6aae665c3075f..55b9151a09886 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -1291,13 +1291,13 @@ StringRef CppEmitter::getOrCreateInductionVarName(Value val) {
int64_t identifier = 'i' + loopNestingLevel;
- if (identifier >= 'i' && identifier <= 'z') {
+ if (identifier >= 'i' && identifier <= 't') {
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));
+ val, formatv("u{0}_{1}", identifier - 't' - 1, ++valueCount));
}
}
return *valueMapper.begin(val);
diff --git a/mlir/test/Target/Cpp/for_loop_induction_vars.mlir b/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
index 3995a4a904ed4..e47724e0b86bf 100644
--- a/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
+++ b/mlir/test/Target/Cpp/for_loop_induction_vars.mlir
@@ -37,7 +37,7 @@ func.func @test_for_nesting() {
%stop = emitc.literal "10" : index
%step = emitc.literal "1" : index
- // CHECK-COUNT-18: for (size_t [[ITER:[i-z]_[0-9]*]] = {{.*}}; [[ITER]] < {{.*}}; [[ITER]] += {{.*}}) {
+ // CHECK-COUNT-12: for (size_t [[ITER:[i-t]_[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 {
@@ -50,23 +50,11 @@ func.func @test_for_nesting() {
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
- }
- }
- }
- }
- }
- }
+ // CHECK: for (size_t [[ITERu0:u0_[0-9]*]] = {{.*}}; [[ITERu0]] < {{.*}}; [[ITERu0]] += {{.*}}) {
+ emitc.for %i14 = %start to %stop step %step {
+ // CHECK: for (size_t [[ITERu1:u1_[0-9]*]] = {{.*}}; [[ITERu1]] < {{.*}}; [[ITERu1]] += {{.*}}) {
+ emitc.for %i15 = %start to %stop step %step {
+ %0 = emitc.call_opaque "f"() : () -> i32
}
}
}
>From 41ccc87931d0e9e1da90e1f87c8d01532cec92ba Mon Sep 17 00:00:00 2001
From: ndegener-amd <niklas.degener at amd.com>
Date: Mon, 5 May 2025 01:25:31 -0600
Subject: [PATCH 4/4] Change to correct getName func for induction vars
---
mlir/lib/Target/Cpp/TranslateToCpp.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/mlir/lib/Target/Cpp/TranslateToCpp.cpp b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
index 55b9151a09886..a423903644c91 100644
--- a/mlir/lib/Target/Cpp/TranslateToCpp.cpp
+++ b/mlir/lib/Target/Cpp/TranslateToCpp.cpp
@@ -911,7 +911,7 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
if (failed(emitter.emitOperand(forOp.getLowerBound())))
return failure();
os << "; ";
- os << emitter.getOrCreateName(forOp.getInductionVar());
+ os << emitter.getOrCreateInductionVarName(forOp.getInductionVar());
os << " < ";
Value upperBound = forOp.getUpperBound();
bool upperBoundRequiresParentheses = requiresParentheses(upperBound);
@@ -922,7 +922,7 @@ static LogicalResult printOperation(CppEmitter &emitter, emitc::ForOp forOp) {
if (upperBoundRequiresParentheses)
os << ")";
os << "; ";
- os << emitter.getOrCreateName(forOp.getInductionVar());
+ os << emitter.getOrCreateInductionVarName(forOp.getInductionVar());
os << " += ";
if (failed(emitter.emitOperand(forOp.getStep())))
return failure();
More information about the Mlir-commits
mailing list