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
