[clang] [llvm] [AMDGPU] Add dot product patterns with saturating add (clamp) (PR #187945)

via cfe-commits cfe-commits at lists.llvm.org
Tue May 5 04:57:34 PDT 2026


================
@@ -2405,6 +2412,148 @@ bool AMDGPUCodeGenPrepareImpl::visitMbcntHi(IntrinsicInst &I) const {
   return tryReplaceWithWorkitemId(I, Wave);
 }
 
+/// Check if type is <4 x i8>.
+static bool isV4I8(Type *Ty) {
+  FixedVectorType *VTy = dyn_cast<FixedVectorType>(Ty);
+  return VTy && VTy->getNumElements() == 4 &&
+         VTy->getElementType()->isIntegerTy(8);
+}
+
+/// Helper to match the dot4 pattern: mul(zext/sext <4 x i8>, zext/sext <4 x
+/// i8>) Returns true if pattern matches and signedness matches IsSigned.
+/// Sets A, B to the <4 x i8> sources.
+static bool matchDot4Pattern(Value *MulOp, Value *&A, Value *&B,
+                             bool IsSigned) {
+  Value *Src0, *Src1;
+  if (!match(MulOp, m_Mul(m_Value(Src0), m_Value(Src1))))
+    return false;
+
+  // Check that result type is <4 x i32>
+  FixedVectorType *MulTy = dyn_cast<FixedVectorType>(MulOp->getType());
+  if (!MulTy || MulTy->getNumElements() != 4 ||
+      !MulTy->getElementType()->isIntegerTy(32))
+    return false;
+
+  // Match zext or sext based on IsSigned
+  Value *ExtSrc0, *ExtSrc1;
+  if (IsSigned) {
+    if (!match(Src0, m_SExt(m_Value(ExtSrc0))) || !isV4I8(ExtSrc0->getType()))
+      return false;
+    if (!match(Src1, m_SExt(m_Value(ExtSrc1))) || !isV4I8(ExtSrc1->getType()))
+      return false;
+  } else {
+    if (!match(Src0, m_ZExt(m_Value(ExtSrc0))) || !isV4I8(ExtSrc0->getType()))
+      return false;
+    if (!match(Src1, m_ZExt(m_Value(ExtSrc1))) || !isV4I8(ExtSrc1->getType()))
+      return false;
+  }
+
+  A = ExtSrc0;
+  B = ExtSrc1;
+  return true;
+}
+
+/// Try to convert vector.reduce.add(mul(zext/sext <4 x i8>, zext/sext <4 x
+/// i8>)) to a dot4 intrinsic call (non-saturating case only).
+bool AMDGPUCodeGenPrepareImpl::visitVectorReduceAdd(IntrinsicInst &I) {
+  // Check if we have dot4 instructions available
+  if (!ST.hasDot7Insts() || (!ST.hasDot1Insts() && !ST.hasDot8Insts()))
+    return false;
+
+  Value *A = nullptr, *B = nullptr;
+
+  // Try unsigned first, then signed
+  bool IsSigned = false;
+  if (!matchDot4Pattern(I.getArgOperand(0), A, B, /*IsSigned=*/false)) {
+    if (!matchDot4Pattern(I.getArgOperand(0), A, B, /*IsSigned=*/true))
+      return false;
+    IsSigned = true;
+  }
+
+  LLVMContext &Ctx = I.getContext();
+  Type *I32Ty = Type::getInt32Ty(Ctx);
+  IRBuilder<> Builder(&I);
+
+  // Bitcast <4 x i8> to i32
+  Value *ASrc = Builder.CreateBitCast(A, I32Ty);
+  Value *BSrc = Builder.CreateBitCast(B, I32Ty);
+
+  // Non-saturating case: accumulator is 0, clamp is false
+  Value *Acc = ConstantInt::get(I32Ty, 0);
+  Value *Clamp = ConstantInt::getFalse(Ctx);
----------------
addmisol wrote:

I tested this ir on latest main:
```
define i32 @test(i32 %a, i32 %b, i32 %acc) {
   %dot = call i32 @llvm.amdgcn.udot4(i32 %a, i32 %b, i32 0, i1 false)
   %result = call i32 @llvm.uadd.sat.i32(i32 %dot, i32 %acc)
   ret i32 %result
}
```
It generates 2 instructions:
```
  v_dot4_u32_u8 v0, v0, v1, 0
  v_add_u32_e64 v0, v0, v2 clamp
```

https://github.com/llvm/llvm-project/pull/187945


More information about the cfe-commits mailing list