[llvm] [DirectX] Add emulation for fp16 types of llvm.is.fpclass (PR #157505)
Farzon Lotfi via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 8 10:09:17 PDT 2025
https://github.com/farzonl updated https://github.com/llvm/llvm-project/pull/157505
>From b198a1e1736ed263fc2c1ed97d05cd8b7ac221a9 Mon Sep 17 00:00:00 2001
From: Farzon Lotfi <farzonlotfi at microsoft.com>
Date: Mon, 8 Sep 2025 12:20:47 -0400
Subject: [PATCH 1/2] [DirectX] Add emulation for fp16 types of llvm.is.fpclass
fixes #157504
This changes adds the emulation we need for IsNaN, IsNormal, & IsFinite
This change only applies these emulations to the llvm.is.fpclass cases
of fp16.
Since there is no DX intrinsics yet for these cases, applying the
emulation to the necessary intrinsics is left for future implementers of
- https://github.com/llvm/llvm-project/issues/99132
- https://github.com/llvm/llvm-project/issues/156069
- https://github.com/llvm/llvm-project/issues/99131
---
.../Target/DirectX/DXILIntrinsicExpansion.cpp | 112 +++++++++++++
llvm/test/CodeGen/DirectX/is_fpclass.ll | 150 +++++++++++++++++-
2 files changed, 261 insertions(+), 1 deletion(-)
diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index c613b351d85b6..b19f4d94a0bb6 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -86,6 +86,115 @@ static Value *expand16BitIsInf(CallInst *Orig) {
return B3;
}
+static Value *expand16BitIsNaN(CallInst *Orig) {
+ Module *M = Orig->getModule();
+ if (M->getTargetTriple().getDXILVersion() >= VersionTuple(1, 9))
+ return nullptr;
+
+ Value *Val = Orig->getOperand(0);
+ Type *ValTy = Val->getType();
+ if (!ValTy->getScalarType()->isHalfTy())
+ return nullptr;
+
+ IRBuilder<> Builder(Orig);
+ Type *IType = Type::getInt16Ty(M->getContext());
+
+ Constant *ExpBitMask =
+ ValTy->isVectorTy()
+ ? ConstantVector::getSplat(
+ ElementCount::getFixed(
+ cast<FixedVectorType>(ValTy)->getNumElements()),
+ ConstantInt::get(IType, 0x7c00))
+ : ConstantInt::get(IType, 0x7c00);
+ Constant *SigBitMask =
+ ValTy->isVectorTy()
+ ? ConstantVector::getSplat(
+ ElementCount::getFixed(
+ cast<FixedVectorType>(ValTy)->getNumElements()),
+ ConstantInt::get(IType, 0x3ff))
+ : ConstantInt::get(IType, 0x3ff);
+
+ Constant *Zero =
+ ValTy->isVectorTy()
+ ? ConstantVector::getSplat(
+ ElementCount::getFixed(
+ cast<FixedVectorType>(ValTy)->getNumElements()),
+ ConstantInt::get(IType, 0))
+ : ConstantInt::get(IType, 0);
+
+ Value *IVal = Builder.CreateBitCast(Val, ExpBitMask->getType());
+ Value *Exp = Builder.CreateAnd(IVal, ExpBitMask);
+ Value *B1 = Builder.CreateICmpEQ(Exp, ExpBitMask);
+
+ Value *Sig = Builder.CreateAnd(IVal, SigBitMask);
+ Value *B2 = Builder.CreateICmpNE(Sig, Zero);
+ Value *B3 = Builder.CreateAnd(B1, B2);
+ return B3;
+}
+
+static Value *expand16BitIsFinite(CallInst *Orig) {
+ Module *M = Orig->getModule();
+ if (M->getTargetTriple().getDXILVersion() >= VersionTuple(1, 9))
+ return nullptr;
+
+ Value *Val = Orig->getOperand(0);
+ Type *ValTy = Val->getType();
+ if (!ValTy->getScalarType()->isHalfTy())
+ return nullptr;
+
+ IRBuilder<> Builder(Orig);
+ Type *IType = Type::getInt16Ty(M->getContext());
+
+ Constant *ExpBitMask =
+ ValTy->isVectorTy()
+ ? ConstantVector::getSplat(
+ ElementCount::getFixed(
+ cast<FixedVectorType>(ValTy)->getNumElements()),
+ ConstantInt::get(IType, 0x7c00))
+ : ConstantInt::get(IType, 0x7c00);
+
+ Value *IVal = Builder.CreateBitCast(Val, ExpBitMask->getType());
+ Value *Exp = Builder.CreateAnd(IVal, ExpBitMask);
+ Value *B1 = Builder.CreateICmpNE(Exp, ExpBitMask);
+ return B1;
+}
+
+static Value *expand16BitIsNormal(CallInst *Orig) {
+ Module *M = Orig->getModule();
+ if (M->getTargetTriple().getDXILVersion() >= VersionTuple(1, 9))
+ return nullptr;
+
+ Value *Val = Orig->getOperand(0);
+ Type *ValTy = Val->getType();
+ if (!ValTy->getScalarType()->isHalfTy())
+ return nullptr;
+
+ IRBuilder<> Builder(Orig);
+ Type *IType = Type::getInt16Ty(M->getContext());
+
+ Constant *ExpBitMask =
+ ValTy->isVectorTy()
+ ? ConstantVector::getSplat(
+ ElementCount::getFixed(
+ cast<FixedVectorType>(ValTy)->getNumElements()),
+ ConstantInt::get(IType, 0x7c00))
+ : ConstantInt::get(IType, 0x7c00);
+ Constant *Zero =
+ ValTy->isVectorTy()
+ ? ConstantVector::getSplat(
+ ElementCount::getFixed(
+ cast<FixedVectorType>(ValTy)->getNumElements()),
+ ConstantInt::get(IType, 0))
+ : ConstantInt::get(IType, 0);
+
+ Value *IVal = Builder.CreateBitCast(Val, ExpBitMask->getType());
+ Value *Exp = Builder.CreateAnd(IVal, ExpBitMask);
+ Value *NotAllZeroes = Builder.CreateICmpNE(Exp, Zero);
+ Value *NotAllOnes = Builder.CreateICmpNE(Exp, ExpBitMask);
+ Value *B1 = Builder.CreateAnd(NotAllZeroes, NotAllOnes);
+ return B1;
+}
+
static bool isIntrinsicExpansion(Function &F) {
switch (F.getIntrinsicID()) {
case Intrinsic::abs:
@@ -342,8 +451,11 @@ static Value *expandIsFPClass(CallInst *Orig) {
case FPClassTest::fcInf:
return expand16BitIsInf(Orig);
case FPClassTest::fcNan:
+ return expand16BitIsNaN(Orig);
case FPClassTest::fcNormal:
+ return expand16BitIsNormal(Orig);
case FPClassTest::fcFinite:
+ return expand16BitIsFinite(Orig);
return nullptr;
}
diff --git a/llvm/test/CodeGen/DirectX/is_fpclass.ll b/llvm/test/CodeGen/DirectX/is_fpclass.ll
index 1796e8bd794d8..f5804f3418853 100644
--- a/llvm/test/CodeGen/DirectX/is_fpclass.ll
+++ b/llvm/test/CodeGen/DirectX/is_fpclass.ll
@@ -46,6 +46,25 @@ entry:
ret i1 %0
}
+define noundef i1 @isnanh(half noundef %a) {
+; CHECK-LABEL: define noundef i1 @isnanh(
+; CHECK-SAME: half noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; SM69CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 8, half [[A]]) #[[ATTR0:[0-9]+]]
+; SMOLDCHECK-NEXT: [[BITCAST:%.*]] = bitcast half [[A]] to i16
+; SMOLDCHECK-NEXT: [[AND:%.*]] = and i16 [[BITCAST]], 31744
+; SMOLDCHECK-NEXT: [[CMPHIGH:%.*]] = icmp eq i16 [[AND]], 31744
+; SMOLDCHECK-NEXT: [[ANDLOW:%.*]] = and i16 [[BITCAST]], 1023
+; SMOLDCHECK-NEXT: [[CMPZERO:%.*]] = icmp ne i16 [[ANDLOW]], 0
+; SMOLDCHECK-NEXT: [[ANDLOW:%.*]] = and i1 [[CMPHIGH]], [[CMPZERO]]
+; SMOLDCHECK-NEXT: ret i1 [[ANDLOW]]
+; SM69CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f16(half %a, i32 3)
+ ret i1 %0
+}
+
define noundef <2 x i1> @isnanv2(<2 x float> noundef %a) {
; CHECK-LABEL: define noundef <2 x i1> @isnanv2(
; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
@@ -63,6 +82,40 @@ entry:
ret <2 x i1> %0
}
+define noundef <2 x i1> @isnanhv2(<2 x half> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isnanhv2(
+; CHECK-SAME: <2 x half> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x half> [[A]], i64 0
+; SM69CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 8, half [[A_I0]]) #[[ATTR0:[0-9]+]]
+; SM69CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x half> [[A]], i64 1
+; SM69CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 8, half [[A_I1]]) #[[ATTR0]]
+; SM69CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; SM69CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; SM69CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+; SMOLDCHECK-NEXT: [[DOTI0:%.*]] = bitcast half [[A_I0]] to i16
+; SMOLDCHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x half> [[A]], i64 1
+; SMOLDCHECK-NEXT: [[DOTI1:%.*]] = bitcast half [[A_I1]] to i16
+; SMOLDCHECK-NEXT: [[DOTI01:%.*]] = and i16 [[DOTI0]], 31744
+; SMOLDCHECK-NEXT: [[DOTI12:%.*]] = and i16 [[DOTI1]], 31744
+; SMOLDCHECK-NEXT: [[DOTI03:%.*]] = icmp eq i16 [[DOTI01]], 31744
+; SMOLDCHECK-NEXT: [[DOTI14:%.*]] = icmp eq i16 [[DOTI12]], 31744
+; SMOLDCHECK-NEXT: [[DOTI05:%.*]] = and i16 [[DOTI0]], 1023
+; SMOLDCHECK-NEXT: [[DOTI16:%.*]] = and i16 [[DOTI1]], 1023
+; SMOLDCHECK-NEXT: [[DOTI07:%.*]] = icmp ne i16 [[DOTI05]], 0
+; SMOLDCHECK-NEXT: [[DOTI18:%.*]] = icmp ne i16 [[DOTI16]], 0
+; SMOLDCHECK-NEXT: [[DOTI09:%.*]] = and i1 [[DOTI03]], [[DOTI07]]
+; SMOLDCHECK-NEXT: [[DOTI110:%.*]] = and i1 [[DOTI14]], [[DOTI18]]
+; SMOLDCHECK-NEXT: [[DOTUPTO015:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI09]], i64 0
+; SMOLDCHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO015]], i1 [[DOTI110]], i64 1
+; SMOLDCHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f16(<2 x half> %a, i32 3)
+ ret <2 x i1> %0
+}
+
define noundef i1 @isinf(float noundef %a) {
; CHECK-LABEL: define noundef i1 @isinf(
; CHECK-SAME: float noundef [[A:%.*]]) {
@@ -80,7 +133,7 @@ define noundef i1 @isinfh(half noundef %a) {
; CHECK-SAME: half noundef [[A:%.*]]) {
; CHECK-NEXT: [[ENTRY:.*:]]
; SM69CHECK-NEXT: [[ISINF:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 9, half [[A]]) #[[ATTR0]]
-; SMOLDCHECK-NEXT: [[BITCAST:%.*]] = bitcast half %a to i16
+; SMOLDCHECK-NEXT: [[BITCAST:%.*]] = bitcast half [[A]] to i16
; SMOLDCHECK-NEXT: [[CMPHIGH:%.*]] = icmp eq i16 [[BITCAST]], 31744
; SMOLDCHECK-NEXT: [[CMPLOW:%.*]] = icmp eq i16 [[BITCAST]], -1024
; SMOLDCHECK-NEXT: [[OR:%.*]] = or i1 [[CMPHIGH]], [[CMPLOW]]
@@ -121,6 +174,22 @@ entry:
ret i1 %0
}
+define noundef i1 @isfiniteh(half noundef %a) {
+; CHECK-LABEL: define noundef i1 @isfiniteh(
+; CHECK-SAME: half noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; SM69CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 10, half [[A]]) #[[ATTR0]]
+; SMOLDCHECK-NEXT: [[BITCAST:%.*]] = bitcast half [[A]] to i16
+; SMOLDCHECK-NEXT: [[AND:%.*]] = and i16 [[BITCAST]], 31744
+; SMOLDCHECK-NEXT: [[CMPHIGH:%.*]] = icmp ne i16 [[AND]], 31744
+; SMOLDCHECK-NEXT: ret i1 [[CMPHIGH]]
+; SM69CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f16(half %a, i32 504)
+ ret i1 %0
+}
+
define noundef <2 x i1> @isfinitev2(<2 x float> noundef %a) {
; CHECK-LABEL: define noundef <2 x i1> @isfinitev2(
; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
@@ -138,6 +207,35 @@ entry:
ret <2 x i1> %0
}
+define noundef <2 x i1> @isfinitehv2(<2 x half> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isfinitehv2(
+; CHECK-SAME: <2 x half> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; SM69CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x half> [[A]], i64 0
+; SM69CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 10, half [[A_I0]]) #[[ATTR0:[0-9]+]]
+; SM69CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x half> [[A]], i64 1
+; SM69CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 10, half [[A_I1]]) #[[ATTR0]]
+; SM69CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; SM69CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; SM69CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+; SMOLDCHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x half> [[A]], i64 0
+; SMOLDCHECK-NEXT: [[DOTI0:%.*]] = bitcast half [[A_I0]] to i16
+; SMOLDCHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x half> [[A]], i64 1
+; SMOLDCHECK-NEXT: [[DOTI1:%.*]] = bitcast half [[A_I1]] to i16
+; SMOLDCHECK-NEXT: [[DOTI01:%.*]] = and i16 [[DOTI0]], 31744
+; SMOLDCHECK-NEXT: [[DOTI12:%.*]] = and i16 [[DOTI1]], 31744
+; SMOLDCHECK-NEXT: [[DOTI03:%.*]] = icmp ne i16 [[DOTI01]], 31744
+; SMOLDCHECK-NEXT: [[DOTI14:%.*]] = icmp ne i16 [[DOTI12]], 31744
+; SMOLDCHECK-NEXT: [[DOTUPTO06:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI03]], i64 0
+; SMOLDCHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO06]], i1 [[DOTI14]], i64 1
+; SMOLDCHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f16(<2 x half> %a, i32 504)
+ ret <2 x i1> %0
+}
+
define noundef i1 @isnormal(float noundef %a) {
; CHECK-LABEL: define noundef i1 @isnormal(
; CHECK-SAME: float noundef [[A:%.*]]) {
@@ -150,6 +248,24 @@ entry:
ret i1 %0
}
+define noundef i1 @isnormalh(half noundef %a) {
+; CHECK-LABEL: define noundef i1 @isnormalh(
+; CHECK-SAME: half noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; SM69CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 11, half [[A]]) #[[ATTR0]]
+; SMOLDCHECK-NEXT: [[BITCAST:%.*]] = bitcast half [[A]] to i16
+; SMOLDCHECK-NEXT: [[AND:%.*]] = and i16 [[BITCAST]], 31744
+; SMOLDCHECK-NEXT: [[CMPZERO:%.*]] = icmp ne i16 [[AND]], 0
+; SMOLDCHECK-NEXT: [[CMPHIGH:%.*]] = icmp ne i16 [[AND]], 31744
+; SMOLDCHECK-NEXT: [[ANDCMP:%.*]] = and i1 [[CMPZERO]], [[CMPHIGH]]
+; SMOLDCHECK-NEXT: ret i1 [[ANDCMP]]
+; SM69CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f16(half %a, i32 264)
+ ret i1 %0
+}
+
define noundef <2 x i1> @isnormalv2(<2 x float> noundef %a) {
; CHECK-LABEL: define noundef <2 x i1> @isnormalv2(
; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
@@ -167,5 +283,37 @@ entry:
ret <2 x i1> %0
}
+define noundef <2 x i1> @isnormalhv2(<2 x half> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isnormalhv2(
+; CHECK-SAME: <2 x half> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x half> [[A]], i64 0
+; SM69CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 11, half [[A_I0]]) #[[ATTR0:[0-9]+]]
+; SM69CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x half> [[A]], i64 1
+; SM69CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f16(i32 11, half [[A_I1]]) #[[ATTR0]]
+; SM69CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; SM69CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; SM69CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+; SMOLDCHECK-NEXT: [[DOTI0:%.*]] = bitcast half [[A_I0]] to i16
+; SMOLDCHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x half> [[A]], i64 1
+; SMOLDCHECK-NEXT: [[DOTI1:%.*]] = bitcast half [[A_I1]] to i16
+; SMOLDCHECK-NEXT: [[DOTI01:%.*]] = and i16 [[DOTI0]], 31744
+; SMOLDCHECK-NEXT: [[DOTI12:%.*]] = and i16 [[DOTI1]], 31744
+; SMOLDCHECK-NEXT: [[DOTI03:%.*]] = icmp ne i16 [[DOTI01]], 0
+; SMOLDCHECK-NEXT: [[DOTI14:%.*]] = icmp ne i16 [[DOTI12]], 0
+; SMOLDCHECK-NEXT: [[DOTI05:%.*]] = icmp ne i16 [[DOTI01]], 31744
+; SMOLDCHECK-NEXT: [[DOTI16:%.*]] = icmp ne i16 [[DOTI12]], 31744
+; SMOLDCHECK-NEXT: [[DOTI07:%.*]] = and i1 [[DOTI03]], [[DOTI05]]
+; SMOLDCHECK-NEXT: [[DOTI18:%.*]] = and i1 [[DOTI14]], [[DOTI16]]
+; SMOLDCHECK-NEXT: [[DOTUPTO012:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI07]], i64 0
+; SMOLDCHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO012]], i1 [[DOTI18]], i64 1
+; SMOLDCHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f16(<2 x half> %a, i32 264)
+ ret <2 x i1> %0
+}
+
declare i1 @llvm.is.fpclass.f32(float, i32 immarg)
declare <2 x i1> @llvm.is.fpclass.v2f32(<2 x float>, i32 immarg)
>From fa1373e20788a8804d9eec209c04b73bc666f0b4 Mon Sep 17 00:00:00 2001
From: Farzon Lotfi <farzonlotfi at microsoft.com>
Date: Mon, 8 Sep 2025 13:08:58 -0400
Subject: [PATCH 2/2] fix bad merge
---
llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp | 1 -
1 file changed, 1 deletion(-)
diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index b19f4d94a0bb6..e2469d8df957f 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -456,7 +456,6 @@ static Value *expandIsFPClass(CallInst *Orig) {
return expand16BitIsNormal(Orig);
case FPClassTest::fcFinite:
return expand16BitIsFinite(Orig);
- return nullptr;
}
IRBuilder<> Builder(Orig);
More information about the llvm-commits
mailing list