https://github.com/adams381 updated 
https://github.com/llvm/llvm-project/pull/203112

>From 1dc5a8355a5ad173417f5ef77164d0a6b09e7c59 Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Wed, 10 Jun 2026 14:55:12 -0700
Subject: [PATCH 1/2] [CIR] Lower elementwise saturating add/sub builtins

__builtin_elementwise_add_sat and __builtin_elementwise_sub_sat were
sitting in the errorBuiltinNYI batch in emitBuiltinExpr, so any use hit
"unimplemented builtin call".  This blocks C++26 std::add_sat/std::sub_sat
(libc++ <__numeric/saturation_arithmetic.h>), which lower straight onto
these builtins.

Lower them the same way classic CodeGen does (CGBuiltin.cpp): evaluate
both operands, take the element type when the argument is a vector, and
pick the signed or unsigned saturating intrinsic from the element's
signedness -- sadd.sat/uadd.sat for add, ssub.sat/usub.sat for sub --
emitted via emitIntrinsicCallOp with the operand type as the result type.
The cases stay ahead of the elementwise_max/min group so the ordering
still follows classic CodeGen.

Scalar and vector, signed and unsigned coverage is added to
builtins-elementwise.c, checking the CIR cir.call_llvm_intrinsic and the
lowered @llvm.{s,u}{add,sub}.sat calls against both the CIR and classic
-emit-llvm paths.
---
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp       | 19 +++++-
 .../CodeGenBuiltins/builtins-elementwise.c    | 65 +++++++++++++++++++
 2 files changed, 83 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp 
b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index e6de3954a8c69..6dfbd5c735750 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -1674,7 +1674,24 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl 
&gd, unsigned builtinID,
                                                    mlir::ValueRange{a, b, c}));
   }
   case Builtin::BI__builtin_elementwise_add_sat:
-  case Builtin::BI__builtin_elementwise_sub_sat:
+  case Builtin::BI__builtin_elementwise_sub_sat: {
+    mlir::Location loc = getLoc(e->getExprLoc());
+    mlir::Value op0 = emitScalarExpr(e->getArg(0));
+    mlir::Value op1 = emitScalarExpr(e->getArg(1));
+    QualType ty = e->getArg(0)->getType();
+    if (const auto *vecTy = ty->getAs<clang::VectorType>())
+      ty = vecTy->getElementType();
+    assert(ty->isIntegerType() &&
+           "elementwise saturating builtins require integer operands");
+    bool isSigned = ty->isSignedIntegerType();
+    llvm::StringRef name;
+    if (builtinIDIfNoAsmLabel == Builtin::BI__builtin_elementwise_add_sat)
+      name = isSigned ? "sadd.sat" : "uadd.sat";
+    else
+      name = isSigned ? "ssub.sat" : "usub.sat";
+    return RValue::get(builder.emitIntrinsicCallOp(loc, name, op0.getType(),
+                                                   mlir::ValueRange{op0, 
op1}));
+  }
   case Builtin::BI__builtin_elementwise_max:
   case Builtin::BI__builtin_elementwise_min:
   case Builtin::BI__builtin_elementwise_maxnum:
diff --git a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c 
b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c
index c04739d737632..eef16addd392c 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c
+++ b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c
@@ -7,6 +7,7 @@
 // RUN: FileCheck --check-prefix=LLVM --input-file=%t-ogcg.ll %s
 
 typedef int vint4 __attribute__((ext_vector_type(4)));
+typedef unsigned int vuint4 __attribute__((ext_vector_type(4)));
 typedef short vshort8 __attribute__((ext_vector_type(8)));
 typedef float vfloat4 __attribute__((ext_vector_type(4)));
 typedef double vdouble4 __attribute__((ext_vector_type(4)));
@@ -509,3 +510,67 @@ void test_builtin_elementwise_fshr(long long int i1, long 
long int i2,
   // LLVM: call <4 x i32> @llvm.fshr.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}}, <4 x i32> %{{.*}})
   vu1 = __builtin_elementwise_fshr(vu1, vu2, vu3);
 }
+
+void test_builtin_elementwise_add_sat(int i1, int i2, unsigned u1, unsigned u2,
+                                      short s1, short s2, vint4 vi1, vint4 vi2,
+                                      vuint4 vu1, vuint4 vu2, vshort8 vs1,
+                                      vshort8 vs2) {
+  // CIR-LABEL: test_builtin_elementwise_add_sat
+  // LLVM-LABEL: test_builtin_elementwise_add_sat
+
+  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!s32i, !s32i) 
-> !s32i
+  // LLVM: call i32 @llvm.sadd.sat.i32(i32 %{{.*}}, i32 %{{.*}})
+  i1 = __builtin_elementwise_add_sat(i1, i2);
+
+  // CIR: cir.call_llvm_intrinsic "uadd.sat" %{{.*}}, %{{.*}} : (!u32i, !u32i) 
-> !u32i
+  // LLVM: call i32 @llvm.uadd.sat.i32(i32 %{{.*}}, i32 %{{.*}})
+  u1 = __builtin_elementwise_add_sat(u1, u2);
+
+  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!s16i, !s16i) 
-> !s16i
+  // LLVM: call i16 @llvm.sadd.sat.i16(i16 %{{.*}}, i16 %{{.*}})
+  s1 = __builtin_elementwise_add_sat(s1, s2);
+
+  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !s32i>, !cir.vector<4 x !s32i>) -> !cir.vector<4 x !s32i>
+  // LLVM: call <4 x i32> @llvm.sadd.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
+  vi1 = __builtin_elementwise_add_sat(vi1, vi2);
+
+  // CIR: cir.call_llvm_intrinsic "uadd.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !u32i>, !cir.vector<4 x !u32i>) -> !cir.vector<4 x !u32i>
+  // LLVM: call <4 x i32> @llvm.uadd.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
+  vu1 = __builtin_elementwise_add_sat(vu1, vu2);
+
+  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!cir.vector<8 
x !s16i>, !cir.vector<8 x !s16i>) -> !cir.vector<8 x !s16i>
+  // LLVM: call <8 x i16> @llvm.sadd.sat.v8i16(<8 x i16> %{{.*}}, <8 x i16> 
%{{.*}})
+  vs1 = __builtin_elementwise_add_sat(vs1, vs2);
+}
+
+void test_builtin_elementwise_sub_sat(int i1, int i2, unsigned u1, unsigned u2,
+                                      short s1, short s2, vint4 vi1, vint4 vi2,
+                                      vuint4 vu1, vuint4 vu2, vshort8 vs1,
+                                      vshort8 vs2) {
+  // CIR-LABEL: test_builtin_elementwise_sub_sat
+  // LLVM-LABEL: test_builtin_elementwise_sub_sat
+
+  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!s32i, !s32i) 
-> !s32i
+  // LLVM: call i32 @llvm.ssub.sat.i32(i32 %{{.*}}, i32 %{{.*}})
+  i1 = __builtin_elementwise_sub_sat(i1, i2);
+
+  // CIR: cir.call_llvm_intrinsic "usub.sat" %{{.*}}, %{{.*}} : (!u32i, !u32i) 
-> !u32i
+  // LLVM: call i32 @llvm.usub.sat.i32(i32 %{{.*}}, i32 %{{.*}})
+  u1 = __builtin_elementwise_sub_sat(u1, u2);
+
+  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!s16i, !s16i) 
-> !s16i
+  // LLVM: call i16 @llvm.ssub.sat.i16(i16 %{{.*}}, i16 %{{.*}})
+  s1 = __builtin_elementwise_sub_sat(s1, s2);
+
+  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !s32i>, !cir.vector<4 x !s32i>) -> !cir.vector<4 x !s32i>
+  // LLVM: call <4 x i32> @llvm.ssub.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
+  vi1 = __builtin_elementwise_sub_sat(vi1, vi2);
+
+  // CIR: cir.call_llvm_intrinsic "usub.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !u32i>, !cir.vector<4 x !u32i>) -> !cir.vector<4 x !u32i>
+  // LLVM: call <4 x i32> @llvm.usub.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
+  vu1 = __builtin_elementwise_sub_sat(vu1, vu2);
+
+  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!cir.vector<8 
x !s16i>, !cir.vector<8 x !s16i>) -> !cir.vector<8 x !s16i>
+  // LLVM: call <8 x i16> @llvm.ssub.sat.v8i16(<8 x i16> %{{.*}}, <8 x i16> 
%{{.*}})
+  vs1 = __builtin_elementwise_sub_sat(vs1, vs2);
+}

>From 4335d0ee220cb1c6124fd8d73f07a5fb326d379e Mon Sep 17 00:00:00 2001
From: Adam Smith <[email protected]>
Date: Mon, 15 Jun 2026 13:52:14 -0700
Subject: [PATCH 2/2] [CIR] Model elementwise sat builtins as saturated
 cir.add/sub

Route __builtin_elementwise_add_sat/sub_sat through createAdd/createSub
with OverflowBehavior::Saturated instead of emitting the llvm.*.sat
intrinsics directly.  The saturated cir.add/cir.sub lower to the same
intrinsics, choosing signed vs unsigned from the operand type.

cir.add/cir.sub do not model i1 arithmetic, so a bool operand (scalar
or ext-vector) is reported with errorNYI before emitScalarExpr rather
than crashing the op verifier (or the NYI bool-vector load).

Addresses review feedback on PR #203112.
---
 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp       | 32 +++++++++++--------
 .../builtins-elementwise-bool-nyi.c           | 14 ++++++++
 .../CodeGenBuiltins/builtins-elementwise.c    | 24 +++++++-------
 3 files changed, 45 insertions(+), 25 deletions(-)
 create mode 100644 
clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c

diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp 
b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 6dfbd5c735750..6c3bae04a8bb3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -1675,22 +1675,28 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl 
&gd, unsigned builtinID,
   }
   case Builtin::BI__builtin_elementwise_add_sat:
   case Builtin::BI__builtin_elementwise_sub_sat: {
+    // cir.add/cir.sub do not model i1 arithmetic, so a bool-element
+    // saturating add/sub is not representable through the saturated op.
+    // Check the AST element type and bail before emitScalarExpr: an
+    // ext-vector-of-bool operand would otherwise hit the NYI bool-vector
+    // load, which returns a null value and would crash op0.getType().
+    QualType argTy = e->getArg(0)->getType();
+    if (const auto *vecTy = argTy->getAs<clang::VectorType>())
+      argTy = vecTy->getElementType();
+    if (argTy->isBooleanType()) {
+      cgm.errorNYI(e->getSourceRange(),
+                   "saturating add/sub on a boolean operand");
+      return RValue::get(nullptr);
+    }
     mlir::Location loc = getLoc(e->getExprLoc());
     mlir::Value op0 = emitScalarExpr(e->getArg(0));
     mlir::Value op1 = emitScalarExpr(e->getArg(1));
-    QualType ty = e->getArg(0)->getType();
-    if (const auto *vecTy = ty->getAs<clang::VectorType>())
-      ty = vecTy->getElementType();
-    assert(ty->isIntegerType() &&
-           "elementwise saturating builtins require integer operands");
-    bool isSigned = ty->isSignedIntegerType();
-    llvm::StringRef name;
-    if (builtinIDIfNoAsmLabel == Builtin::BI__builtin_elementwise_add_sat)
-      name = isSigned ? "sadd.sat" : "uadd.sat";
-    else
-      name = isSigned ? "ssub.sat" : "usub.sat";
-    return RValue::get(builder.emitIntrinsicCallOp(loc, name, op0.getType(),
-                                                   mlir::ValueRange{op0, 
op1}));
+    mlir::Value val =
+        builtinIDIfNoAsmLabel == Builtin::BI__builtin_elementwise_add_sat
+            ? builder.createAdd(loc, op0, op1, 
cir::OverflowBehavior::Saturated)
+            : builder.createSub(loc, op0, op1,
+                                cir::OverflowBehavior::Saturated);
+    return RValue::get(val);
   }
   case Builtin::BI__builtin_elementwise_max:
   case Builtin::BI__builtin_elementwise_min:
diff --git a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c 
b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c
new file mode 100644
index 0000000000000..933a1298ae5b9
--- /dev/null
+++ b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise-bool-nyi.c
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir %s -verify 
-emit-cir -o -
+
+typedef _Bool vbool4 __attribute__((ext_vector_type(4)));
+
+void test_bool_sat(_Bool a, _Bool b, vbool4 va, vbool4 vb) {
+  // expected-error@+1 {{ClangIR code gen Not Yet Implemented: saturating 
add/sub on a boolean operand}}
+  (void)__builtin_elementwise_add_sat(a, b);
+  // expected-error@+1 {{ClangIR code gen Not Yet Implemented: saturating 
add/sub on a boolean operand}}
+  (void)__builtin_elementwise_sub_sat(a, b);
+  // expected-error@+1 {{ClangIR code gen Not Yet Implemented: saturating 
add/sub on a boolean operand}}
+  (void)__builtin_elementwise_add_sat(va, vb);
+  // expected-error@+1 {{ClangIR code gen Not Yet Implemented: saturating 
add/sub on a boolean operand}}
+  (void)__builtin_elementwise_sub_sat(va, vb);
+}
diff --git a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c 
b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c
index eef16addd392c..42525e6744190 100644
--- a/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c
+++ b/clang/test/CIR/CodeGenBuiltins/builtins-elementwise.c
@@ -518,27 +518,27 @@ void test_builtin_elementwise_add_sat(int i1, int i2, 
unsigned u1, unsigned u2,
   // CIR-LABEL: test_builtin_elementwise_add_sat
   // LLVM-LABEL: test_builtin_elementwise_add_sat
 
-  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!s32i, !s32i) 
-> !s32i
+  // CIR: cir.add sat %{{.*}}, %{{.*}} : !s32i
   // LLVM: call i32 @llvm.sadd.sat.i32(i32 %{{.*}}, i32 %{{.*}})
   i1 = __builtin_elementwise_add_sat(i1, i2);
 
-  // CIR: cir.call_llvm_intrinsic "uadd.sat" %{{.*}}, %{{.*}} : (!u32i, !u32i) 
-> !u32i
+  // CIR: cir.add sat %{{.*}}, %{{.*}} : !u32i
   // LLVM: call i32 @llvm.uadd.sat.i32(i32 %{{.*}}, i32 %{{.*}})
   u1 = __builtin_elementwise_add_sat(u1, u2);
 
-  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!s16i, !s16i) 
-> !s16i
+  // CIR: cir.add sat %{{.*}}, %{{.*}} : !s16i
   // LLVM: call i16 @llvm.sadd.sat.i16(i16 %{{.*}}, i16 %{{.*}})
   s1 = __builtin_elementwise_add_sat(s1, s2);
 
-  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !s32i>, !cir.vector<4 x !s32i>) -> !cir.vector<4 x !s32i>
+  // CIR: cir.add sat %{{.*}}, %{{.*}} : !cir.vector<4 x !s32i>
   // LLVM: call <4 x i32> @llvm.sadd.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
   vi1 = __builtin_elementwise_add_sat(vi1, vi2);
 
-  // CIR: cir.call_llvm_intrinsic "uadd.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !u32i>, !cir.vector<4 x !u32i>) -> !cir.vector<4 x !u32i>
+  // CIR: cir.add sat %{{.*}}, %{{.*}} : !cir.vector<4 x !u32i>
   // LLVM: call <4 x i32> @llvm.uadd.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
   vu1 = __builtin_elementwise_add_sat(vu1, vu2);
 
-  // CIR: cir.call_llvm_intrinsic "sadd.sat" %{{.*}}, %{{.*}} : (!cir.vector<8 
x !s16i>, !cir.vector<8 x !s16i>) -> !cir.vector<8 x !s16i>
+  // CIR: cir.add sat %{{.*}}, %{{.*}} : !cir.vector<8 x !s16i>
   // LLVM: call <8 x i16> @llvm.sadd.sat.v8i16(<8 x i16> %{{.*}}, <8 x i16> 
%{{.*}})
   vs1 = __builtin_elementwise_add_sat(vs1, vs2);
 }
@@ -550,27 +550,27 @@ void test_builtin_elementwise_sub_sat(int i1, int i2, 
unsigned u1, unsigned u2,
   // CIR-LABEL: test_builtin_elementwise_sub_sat
   // LLVM-LABEL: test_builtin_elementwise_sub_sat
 
-  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!s32i, !s32i) 
-> !s32i
+  // CIR: cir.sub sat %{{.*}}, %{{.*}} : !s32i
   // LLVM: call i32 @llvm.ssub.sat.i32(i32 %{{.*}}, i32 %{{.*}})
   i1 = __builtin_elementwise_sub_sat(i1, i2);
 
-  // CIR: cir.call_llvm_intrinsic "usub.sat" %{{.*}}, %{{.*}} : (!u32i, !u32i) 
-> !u32i
+  // CIR: cir.sub sat %{{.*}}, %{{.*}} : !u32i
   // LLVM: call i32 @llvm.usub.sat.i32(i32 %{{.*}}, i32 %{{.*}})
   u1 = __builtin_elementwise_sub_sat(u1, u2);
 
-  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!s16i, !s16i) 
-> !s16i
+  // CIR: cir.sub sat %{{.*}}, %{{.*}} : !s16i
   // LLVM: call i16 @llvm.ssub.sat.i16(i16 %{{.*}}, i16 %{{.*}})
   s1 = __builtin_elementwise_sub_sat(s1, s2);
 
-  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !s32i>, !cir.vector<4 x !s32i>) -> !cir.vector<4 x !s32i>
+  // CIR: cir.sub sat %{{.*}}, %{{.*}} : !cir.vector<4 x !s32i>
   // LLVM: call <4 x i32> @llvm.ssub.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
   vi1 = __builtin_elementwise_sub_sat(vi1, vi2);
 
-  // CIR: cir.call_llvm_intrinsic "usub.sat" %{{.*}}, %{{.*}} : (!cir.vector<4 
x !u32i>, !cir.vector<4 x !u32i>) -> !cir.vector<4 x !u32i>
+  // CIR: cir.sub sat %{{.*}}, %{{.*}} : !cir.vector<4 x !u32i>
   // LLVM: call <4 x i32> @llvm.usub.sat.v4i32(<4 x i32> %{{.*}}, <4 x i32> 
%{{.*}})
   vu1 = __builtin_elementwise_sub_sat(vu1, vu2);
 
-  // CIR: cir.call_llvm_intrinsic "ssub.sat" %{{.*}}, %{{.*}} : (!cir.vector<8 
x !s16i>, !cir.vector<8 x !s16i>) -> !cir.vector<8 x !s16i>
+  // CIR: cir.sub sat %{{.*}}, %{{.*}} : !cir.vector<8 x !s16i>
   // LLVM: call <8 x i16> @llvm.ssub.sat.v8i16(<8 x i16> %{{.*}}, <8 x i16> 
%{{.*}})
   vs1 = __builtin_elementwise_sub_sat(vs1, vs2);
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to