[clang] [llvm] [clang] Add __builtin_elementwise_saturating_cast (PR #227603)
Jan Schultke via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 30 02:12:22 PDT 2026
https://github.com/eisenwave updated https://github.com/llvm/llvm-project/pull/227603
>From 8527d174330684cfffd5debf5b4878f828342f3b Mon Sep 17 00:00:00 2001
From: Eisenwave <me at eisenwave.net>
Date: Wed, 30 Sep 2026 10:22:21 +0200
Subject: [PATCH 1/2] [clang] Add __builtin_elementwise_saturating_cast
Co-authored-by: GPT-5.6 Luna <copilot at github.com>
---
clang/include/clang/AST/ComputeDependence.h | 2 +
clang/include/clang/AST/Expr.h | 59 ++++++++++
clang/include/clang/AST/RecursiveASTVisitor.h | 1 +
clang/include/clang/AST/TextNodeDumper.h | 2 +
clang/include/clang/Basic/Builtins.td | 6 +
clang/include/clang/Basic/StmtNodes.td | 1 +
clang/include/clang/Basic/TokenKinds.def | 1 +
clang/include/clang/Sema/Sema.h | 11 ++
.../include/clang/Serialization/ASTBitCodes.h | 3 +
clang/lib/AST/ASTImporter.cpp | 18 +++
clang/lib/AST/ByteCode/Compiler.cpp | 25 ++++-
clang/lib/AST/ByteCode/Compiler.h | 2 +
clang/lib/AST/ByteCode/Interp.cpp | 5 +
clang/lib/AST/ByteCode/Interp.h | 1 +
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 40 +++++++
clang/lib/AST/ByteCode/InterpHelpers.h | 4 +
clang/lib/AST/ByteCode/Opcodes.td | 2 +
clang/lib/AST/ComputeDependence.cpp | 9 ++
clang/lib/AST/Expr.cpp | 14 +++
clang/lib/AST/ExprClassification.cpp | 1 +
clang/lib/AST/ExprConstant.cpp | 36 ++++++
clang/lib/AST/ItaniumMangle.cpp | 1 +
clang/lib/AST/StmtPrinter.cpp | 9 ++
clang/lib/AST/StmtProfile.cpp | 5 +
clang/lib/AST/TextNodeDumper.cpp | 5 +
clang/lib/CodeGen/CGExprScalar.cpp | 93 +++++++++++++++
clang/lib/Parse/ParseExpr.cpp | 15 ++-
clang/lib/Sema/SemaChecking.cpp | 53 +++++++++
clang/lib/Sema/SemaExceptionSpec.cpp | 1 +
clang/lib/Sema/SemaExpr.cpp | 9 ++
clang/lib/Sema/TreeTransform.h | 28 +++++
clang/lib/Serialization/ASTReaderStmt.cpp | 13 +++
clang/lib/Serialization/ASTWriter.cpp | 1 +
clang/lib/Serialization/ASTWriterStmt.cpp | 10 ++
clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 +
.../builtins-elementwise-saturating-cast.c | 106 ++++++++++++++++++
.../builtins-elementwise-saturating-cast.c | 39 +++++++
.../constant-builtins-saturating-cast.cpp | 55 +++++++++
llvm/include/llvm/ADT/APSInt.h | 21 ++++
llvm/lib/Support/APSInt.cpp | 22 ++++
llvm/unittests/ADT/APSIntTest.cpp | 47 ++++++++
41 files changed, 772 insertions(+), 5 deletions(-)
create mode 100644 clang/test/CodeGen/builtins-elementwise-saturating-cast.c
create mode 100644 clang/test/Sema/builtins-elementwise-saturating-cast.c
create mode 100644 clang/test/Sema/constant-builtins-saturating-cast.cpp
diff --git a/clang/include/clang/AST/ComputeDependence.h b/clang/include/clang/AST/ComputeDependence.h
index 6430081a40350..216facc5a9eb0 100644
--- a/clang/include/clang/AST/ComputeDependence.h
+++ b/clang/include/clang/AST/ComputeDependence.h
@@ -38,6 +38,7 @@ class ConditionalOperator;
class BinaryConditionalOperator;
class StmtExpr;
class ConvertVectorExpr;
+class ElementwiseSaturatingCastExpr;
class VAArgExpr;
class ChooseExpr;
class NoInitExpr;
@@ -130,6 +131,7 @@ ExprDependence computeDependence(ConditionalOperator *E);
ExprDependence computeDependence(BinaryConditionalOperator *E);
ExprDependence computeDependence(StmtExpr *E, unsigned TemplateDepth);
ExprDependence computeDependence(ConvertVectorExpr *E);
+ExprDependence computeDependence(ElementwiseSaturatingCastExpr *E);
ExprDependence computeDependence(VAArgExpr *E);
ExprDependence computeDependence(ChooseExpr *E);
ExprDependence computeDependence(NoInitExpr *E);
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index c03c88232e13d..718bcae540ce4 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -4880,6 +4880,65 @@ class ConvertVectorExpr final
}
};
+/// ElementwiseSaturatingCastExpr - Clang builtin function
+/// __builtin_elementwise_saturating_cast.
+/// This AST node provides support for saturating an integer scalar or vector
+/// conversion to the destination type.
+class ElementwiseSaturatingCastExpr final : public Expr {
+private:
+ Stmt *SrcExpr;
+ TypeSourceInfo *TInfo;
+ SourceLocation BuiltinLoc, RParenLoc;
+
+ friend class ASTReader;
+ friend class ASTStmtReader;
+ explicit ElementwiseSaturatingCastExpr(EmptyShell Empty)
+ : Expr(ElementwiseSaturatingCastExprClass, Empty) {}
+
+ ElementwiseSaturatingCastExpr(Expr *SrcExpr, TypeSourceInfo *TI,
+ QualType DstType, ExprValueKind VK,
+ ExprObjectKind OK, SourceLocation BuiltinLoc,
+ SourceLocation RParenLoc)
+ : Expr(ElementwiseSaturatingCastExprClass, DstType, VK, OK),
+ SrcExpr(SrcExpr), TInfo(TI), BuiltinLoc(BuiltinLoc),
+ RParenLoc(RParenLoc) {
+ setDependence(computeDependence(this));
+ }
+
+public:
+ static ElementwiseSaturatingCastExpr *CreateEmpty(const ASTContext &C);
+
+ static ElementwiseSaturatingCastExpr *
+ Create(const ASTContext &C, Expr *SrcExpr, TypeSourceInfo *TI,
+ QualType DstType, ExprValueKind VK, ExprObjectKind OK,
+ SourceLocation BuiltinLoc, SourceLocation RParenLoc);
+
+ /// getSrcExpr - Return the expression to be converted.
+ Expr *getSrcExpr() const { return cast<Expr>(SrcExpr); }
+
+ /// getTypeSourceInfo - Return the destination type.
+ TypeSourceInfo *getTypeSourceInfo() const { return TInfo; }
+ void setTypeSourceInfo(TypeSourceInfo *TI) { TInfo = TI; }
+
+ /// getBuiltinLoc - Return the location of the builtin token.
+ SourceLocation getBuiltinLoc() const { return BuiltinLoc; }
+
+ /// getRParenLoc - Return the location of the final right parenthesis.
+ SourceLocation getRParenLoc() const { return RParenLoc; }
+
+ SourceLocation getBeginLoc() const LLVM_READONLY { return BuiltinLoc; }
+ SourceLocation getEndLoc() const LLVM_READONLY { return RParenLoc; }
+
+ static bool classof(const Stmt *T) {
+ return T->getStmtClass() == ElementwiseSaturatingCastExprClass;
+ }
+
+ child_range children() { return child_range(&SrcExpr, &SrcExpr + 1); }
+ const_child_range children() const {
+ return const_child_range(&SrcExpr, &SrcExpr + 1);
+ }
+};
+
/// ChooseExpr - GNU builtin-in function __builtin_choose_expr.
/// This AST node is similar to the conditional operator (?:) in C, with
/// the following exceptions:
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index f144c890f6ee6..e8ef231307dad 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3053,6 +3053,7 @@ DEF_TRAVERSE_STMT(OpenACCAsteriskSizeExpr, {})
DEF_TRAVERSE_STMT(PredefinedExpr, {})
DEF_TRAVERSE_STMT(ShuffleVectorExpr, {})
DEF_TRAVERSE_STMT(ConvertVectorExpr, {})
+DEF_TRAVERSE_STMT(ElementwiseSaturatingCastExpr, {})
DEF_TRAVERSE_STMT(StmtExpr, {})
DEF_TRAVERSE_STMT(SourceLocExpr, {})
DEF_TRAVERSE_STMT(EmbedExpr, {
diff --git a/clang/include/clang/AST/TextNodeDumper.h b/clang/include/clang/AST/TextNodeDumper.h
index 592aa2f1bc942..b4b349f57d3c1 100644
--- a/clang/include/clang/AST/TextNodeDumper.h
+++ b/clang/include/clang/AST/TextNodeDumper.h
@@ -443,6 +443,8 @@ class TextNodeDumper
void VisitEmbedExpr(const EmbedExpr *S);
void VisitAtomicExpr(const AtomicExpr *AE);
void VisitConvertVectorExpr(const ConvertVectorExpr *S);
+ void
+ VisitElementwiseSaturatingCastExpr(const ElementwiseSaturatingCastExpr *S);
};
} // namespace clang
diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td
index f1628c175490f..ff21140004c5b 100644
--- a/clang/include/clang/Basic/Builtins.td
+++ b/clang/include/clang/Basic/Builtins.td
@@ -1805,6 +1805,12 @@ def ElementwiseSubSat : Builtin {
let Prototype = "void(...)";
}
+def ElementwiseSaturatingCast : Builtin {
+ let Spellings = ["__builtin_elementwise_saturating_cast"];
+ let Attributes = [NoThrow, Const, CustomTypeChecking, Constexpr];
+ let Prototype = "void(...)";
+}
+
def ElementwiseFshl : Builtin {
let Spellings = ["__builtin_elementwise_fshl"];
let Attributes = [NoThrow, Const, CustomTypeChecking, Constexpr];
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index 96f7a0080f20d..59db9a375b22f 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -220,6 +220,7 @@ def CUDAKernelCallExpr : StmtNode<CallExpr>;
// Clang Extensions.
def ShuffleVectorExpr : StmtNode<Expr>;
def ConvertVectorExpr : StmtNode<Expr>;
+def ElementwiseSaturatingCastExpr : StmtNode<Expr>;
def BlockExpr : StmtNode<Expr>;
def OpaqueValueExpr : StmtNode<Expr>;
def RecoveryExpr : StmtNode<Expr>;
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index dc9c7d8109467..8308c3cf3012f 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -669,6 +669,7 @@ ALIAS("_pascal" , __pascal , KEYBORLAND)
// Clang Extensions.
KEYWORD(__builtin_convertvector , KEYALL)
+KEYWORD(__builtin_elementwise_saturating_cast, KEYALL)
ALIAS("__char16_t" , char16_t , KEYCXX)
ALIAS("__char32_t" , char32_t , KEYCXX)
KEYWORD(__builtin_bit_cast , KEYALL)
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index e33073c652dba..8862813c2171c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2679,6 +2679,12 @@ class Sema final : public SemaBase {
SourceLocation BuiltinLoc,
SourceLocation RParenLoc);
+ /// ElementwiseSaturatingCastExpr - Handle
+ /// __builtin_elementwise_saturating_cast.
+ ExprResult ElementwiseSaturatingCastExpr(Expr *E, TypeSourceInfo *TInfo,
+ SourceLocation BuiltinLoc,
+ SourceLocation RParenLoc);
+
static StringRef GetFormatStringTypeName(FormatStringType FST);
static FormatStringType GetFormatStringType(StringRef FormatFlavor);
static FormatStringType GetFormatStringType(const FormatAttr *Format);
@@ -7671,6 +7677,11 @@ class Sema final : public SemaBase {
SourceLocation BuiltinLoc,
SourceLocation RParenLoc);
+ ExprResult ActOnElementwiseSaturatingCastExpr(Expr *E,
+ ParsedType ParsedDestTy,
+ SourceLocation BuiltinLoc,
+ SourceLocation RParenLoc);
+
//===---------------------------- OpenCL Features -----------------------===//
/// Parse a __builtin_astype expression.
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 26bcf48ec55cc..0fc0c32c7968f 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -1762,6 +1762,9 @@ enum StmtCode {
/// A ConvertVectorExpr record.
EXPR_CONVERT_VECTOR,
+ /// An ElementwiseSaturatingCastExpr record.
+ EXPR_ELEMENTWISE_SATURATING_CAST,
+
/// BlockExpr
EXPR_BLOCK,
diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp
index eec9e8c2013b1..b4927fafe236d 100644
--- a/clang/lib/AST/ASTImporter.cpp
+++ b/clang/lib/AST/ASTImporter.cpp
@@ -632,6 +632,8 @@ namespace clang {
ExpectedStmt VisitVAArgExpr(VAArgExpr *E);
ExpectedStmt VisitChooseExpr(ChooseExpr *E);
ExpectedStmt VisitConvertVectorExpr(ConvertVectorExpr *E);
+ ExpectedStmt
+ VisitElementwiseSaturatingCastExpr(ElementwiseSaturatingCastExpr *E);
ExpectedStmt VisitShuffleVectorExpr(ShuffleVectorExpr *E);
ExpectedStmt VisitGNUNullExpr(GNUNullExpr *E);
ExpectedStmt VisitGenericSelectionExpr(GenericSelectionExpr *E);
@@ -7677,6 +7679,22 @@ ExpectedStmt ASTNodeImporter::VisitConvertVectorExpr(ConvertVectorExpr *E) {
E->getStoredFPFeaturesOrDefault());
}
+ExpectedStmt ASTNodeImporter::VisitElementwiseSaturatingCastExpr(
+ ElementwiseSaturatingCastExpr *E) {
+ Error Err = Error::success();
+ auto *ToSrcExpr = importChecked(Err, E->getSrcExpr());
+ auto ToRParenLoc = importChecked(Err, E->getRParenLoc());
+ auto ToBuiltinLoc = importChecked(Err, E->getBuiltinLoc());
+ auto ToType = importChecked(Err, E->getType());
+ auto *ToTSI = importChecked(Err, E->getTypeSourceInfo());
+ if (Err)
+ return std::move(Err);
+
+ return ElementwiseSaturatingCastExpr::Create(
+ Importer.getToContext(), ToSrcExpr, ToTSI, ToType, E->getValueKind(),
+ E->getObjectKind(), ToBuiltinLoc, ToRParenLoc);
+}
+
ExpectedStmt ASTNodeImporter::VisitShuffleVectorExpr(ShuffleVectorExpr *E) {
Error Err = Error::success();
auto ToRParenLoc = importChecked(Err, E->getRParenLoc());
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index f7d2d202b2c9f..56781107651f1 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -4771,6 +4771,30 @@ bool Compiler<Emitter>::VisitConvertVectorExpr(const ConvertVectorExpr *E) {
return emitVectorConversion(E->getSrcExpr(), E);
}
+template <class Emitter>
+bool Compiler<Emitter>::VisitElementwiseSaturatingCastExpr(
+ const ElementwiseSaturatingCastExpr *E) {
+ QualType ReturnType = E->getType();
+ OptPrimType ReturnT = classify(E);
+
+ if (!Initializing && !ReturnT && !ReturnType->isVoidType()) {
+ UnsignedOrNone LocalIndex = allocateLocal(E);
+ if (!LocalIndex)
+ return false;
+ if (!this->emitGetPtrLocal(*LocalIndex, E))
+ return false;
+ }
+
+ if (!this->visit(E->getSrcExpr()))
+ return false;
+ if (!this->emitElementwiseSaturatingCast(E, E))
+ return false;
+
+ if (DiscardResult && !ReturnType->isVoidType())
+ return this->emitPop(ReturnT.value_or(PT_Ptr), E);
+ return true;
+}
+
template <class Emitter>
bool Compiler<Emitter>::VisitShuffleVectorExpr(const ShuffleVectorExpr *E) {
// FIXME: Unary shuffle with mask not currently supported.
@@ -6219,7 +6243,6 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
if (!this->visitAsLValue(E->getArg(1)))
return false;
} break;
-
default:
if (!Context::isUnevaluatedBuiltin(BuiltinID)) {
// Put arguments on the stack.
diff --git a/clang/lib/AST/ByteCode/Compiler.h b/clang/lib/AST/ByteCode/Compiler.h
index 56a4d098640c3..a37e047cd8a6f 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -228,6 +228,8 @@ class Compiler final : public ConstStmtVisitor<Compiler<Emitter>, bool>,
bool VisitRecoveryExpr(const RecoveryExpr *E);
bool VisitAddrLabelExpr(const AddrLabelExpr *E);
bool VisitConvertVectorExpr(const ConvertVectorExpr *E);
+ bool
+ VisitElementwiseSaturatingCastExpr(const ElementwiseSaturatingCastExpr *E);
bool VisitShuffleVectorExpr(const ShuffleVectorExpr *E);
bool VisitExtVectorElementExpr(const ExtVectorElementExpr *E);
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 132c4ae92be41..217b296739978 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -2601,6 +2601,11 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE,
return InterpretBuiltin(S, OpPC, CE, BuiltinID);
}
+bool ElementwiseSaturatingCast(InterpState &S, CodePtr OpPC, const Expr *E) {
+ return InterpretElementwiseSaturatingCast(
+ S, OpPC, cast<ElementwiseSaturatingCastExpr>(E));
+}
+
bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
const CallExpr *CE) {
const Pointer &Ptr = S.Stk.pop<Pointer>();
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index aed1e17a97479..23ebc64d1e114 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -122,6 +122,7 @@ bool CallBI(InterpState &S, CodePtr OpPC, const CallExpr *CE,
uint32_t BuiltinID);
bool CallPtr(InterpState &S, CodePtr OpPC, uint32_t ArgSize,
const CallExpr *CE);
+bool ElementwiseSaturatingCast(InterpState &S, CodePtr OpPC, const Expr *E);
bool CheckLiteralType(InterpState &S, CodePtr OpPC, const Type *T);
bool InvalidShuffleVectorIndex(InterpState &S, CodePtr OpPC, uint32_t Index);
bool CheckBitCast(InterpState &S, CodePtr OpPC, bool HasIndeterminateBits,
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 843850af334a2..71e9265a1efb9 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2587,6 +2587,46 @@ static bool interp__builtin_scalar_fp_round_mask_binop(
return true;
}
+bool InterpretElementwiseSaturatingCast(
+ InterpState &S, CodePtr OpPC, const ElementwiseSaturatingCastExpr *Cast) {
+ QualType SourceType = Cast->getSrcExpr()->getType();
+
+ if (!SourceType->isVectorType()) {
+ APSInt Source;
+ if (!popToAPSInt(S, Cast->getSrcExpr(), Source))
+ return false;
+ int DestWidth = S.getASTContext().getIntWidth(Cast->getType());
+ bool DestUnsigned = Cast->getType()->isUnsignedIntegerOrEnumerationType();
+ pushInteger(S, Source.extOrTruncSat(DestWidth, DestUnsigned),
+ Cast->getType());
+ return true;
+ }
+
+ const auto *SourceVecTy = SourceType->castAs<VectorType>();
+ const auto *DestVecTy = Cast->getType()->castAs<VectorType>();
+ assert(SourceVecTy->getNumElements() == DestVecTy->getNumElements());
+ const Pointer &Source = S.Stk.pop<Pointer>();
+ const Pointer &Dest = S.Stk.peek<Pointer>();
+ PrimType SourceT = *S.getContext().classify(SourceVecTy->getElementType());
+ PrimType DestT = *S.getContext().classify(DestVecTy->getElementType());
+
+ for (unsigned I = 0; I != SourceVecTy->getNumElements(); ++I) {
+ APSInt Value;
+ if (SourceT == PT_Bool)
+ Value = Source.elem<Boolean>(I).toAPSInt();
+ else
+ INT_TYPE_SWITCH_NO_BOOL(SourceT,
+ { Value = Source.elem<T>(I).toAPSInt(); });
+ QualType DestElemType = DestVecTy->getElementType();
+ int DestWidth = S.getASTContext().getIntWidth(DestElemType);
+ bool DestUnsigned = DestElemType->isUnsignedIntegerOrEnumerationType();
+ APSInt Result = Value.extOrTruncSat(DestWidth, DestUnsigned);
+ assignIntegral(S, Dest.atIndex(I), DestT, Result);
+ }
+ Dest.initializeAllElements();
+ return true;
+}
+
static bool interp__builtin_elementwise_int_binop(
InterpState &S, CodePtr OpPC, const CallExpr *Call,
llvm::function_ref<APInt(const APSInt &, const APSInt &)> Fn) {
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 50f0462a93f16..998a94dc0db02 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -15,6 +15,7 @@
namespace clang {
class CallExpr;
+class ElementwiseSaturatingCastExpr;
class OffsetOfExpr;
namespace interp {
@@ -28,6 +29,9 @@ bool Interpret(InterpState &S);
bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
uint32_t BuiltinID);
+bool InterpretElementwiseSaturatingCast(InterpState &S, CodePtr OpPC,
+ const ElementwiseSaturatingCastExpr *E);
+
/// Interpret an offsetof operation.
bool InterpretOffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E,
ArrayRef<int64_t> ArrayIndices, int64_t &Result);
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 8b92654adfd6f..c6049854ec4af 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -241,6 +241,8 @@ def CallVirt : Opcode {
def CallBI : Opcode { let Args = [ArgCallExpr, ArgUint32]; }
+def ElementwiseSaturatingCast : Opcode { let Args = [ArgExpr]; }
+
def CallPtr : Opcode {
let Args = [ArgUint32, ArgCallExpr];
}
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index 7ea1c67ff7738..f56334f482a86 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -201,6 +201,15 @@ ExprDependence clang::computeDependence(ConvertVectorExpr *E) {
return D;
}
+ExprDependence clang::computeDependence(ElementwiseSaturatingCastExpr *E) {
+ auto D = toExprDependenceAsWritten(
+ E->getTypeSourceInfo()->getType()->getDependence()) |
+ E->getSrcExpr()->getDependence();
+ if (!E->getType()->isDependentType())
+ D &= ~ExprDependence::Type;
+ return D;
+}
+
ExprDependence clang::computeDependence(ChooseExpr *E) {
if (E->isConditionDependent())
return ExprDependence::TypeValueInstantiation |
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 728be799c50a3..765d6d8256ffc 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3870,6 +3870,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
case MaterializeTemporaryExprClass:
case ShuffleVectorExprClass:
case ConvertVectorExprClass:
+ case ElementwiseSaturatingCastExprClass:
case AsTypeExprClass:
case CXXParenListInitExprClass:
// These have a side-effect if any subexpression does.
@@ -5733,6 +5734,19 @@ ConvertVectorExpr *ConvertVectorExpr::Create(
RParenLoc, FPFeatures);
}
+ElementwiseSaturatingCastExpr *
+ElementwiseSaturatingCastExpr::CreateEmpty(const ASTContext &C) {
+ return new (C) ElementwiseSaturatingCastExpr(EmptyShell());
+}
+
+ElementwiseSaturatingCastExpr *ElementwiseSaturatingCastExpr::Create(
+ const ASTContext &C, Expr *SrcExpr, TypeSourceInfo *TI, QualType DstType,
+ ExprValueKind VK, ExprObjectKind OK, SourceLocation BuiltinLoc,
+ SourceLocation RParenLoc) {
+ return new (C) ElementwiseSaturatingCastExpr(SrcExpr, TI, DstType, VK, OK,
+ BuiltinLoc, RParenLoc);
+}
+
APValue &CompoundLiteralExpr::getOrCreateStaticValue(ASTContext &Ctx) const {
assert(hasStaticStorage());
if (!StaticValue) {
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index 5fdd8d516ffa6..62a2ec078136f 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -183,6 +183,7 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
case Expr::CXXThrowExprClass:
case Expr::ShuffleVectorExprClass:
case Expr::ConvertVectorExprClass:
+ case Expr::ElementwiseSaturatingCastExprClass:
case Expr::IntegerLiteralClass:
case Expr::FixedPointLiteralClass:
case Expr::CharacterLiteralClass:
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 12a84f7c818d2..73abb732cae1a 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -11967,6 +11967,8 @@ namespace {
bool VisitUnaryOperator(const UnaryOperator *E);
bool VisitCallExpr(const CallExpr *E);
bool VisitConvertVectorExpr(const ConvertVectorExpr *E);
+ bool
+ VisitElementwiseSaturatingCastExpr(const ElementwiseSaturatingCastExpr *E);
bool VisitShuffleVectorExpr(const ShuffleVectorExpr *E);
// FIXME: Missing: conditional operator (for GNU
@@ -15328,6 +15330,26 @@ bool VectorExprEvaluator::VisitConvertVectorExpr(const ConvertVectorExpr *E) {
return Success(APValue(ResultElements.data(), ResultElements.size()), E);
}
+bool VectorExprEvaluator::VisitElementwiseSaturatingCastExpr(
+ const ElementwiseSaturatingCastExpr *E) {
+ APValue Source;
+ if (!EvaluateAsRValue(Info, E->getSrcExpr(), Source))
+ return false;
+
+ QualType DestTy = E->getType()->castAs<VectorType>()->getElementType();
+
+ unsigned SourceLen = Source.getVectorLength();
+ SmallVector<APValue, 4> ResultElements;
+ ResultElements.reserve(SourceLen);
+ for (unsigned EltNum = 0; EltNum < SourceLen; ++EltNum)
+ ResultElements.push_back(
+ APValue(Source.getVectorElt(EltNum).getInt().extOrTruncSat(
+ Info.Ctx.getIntWidth(DestTy),
+ DestTy->isUnsignedIntegerOrEnumerationType())));
+
+ return Success(APValue(ResultElements.data(), ResultElements.size()), E);
+}
+
static bool handleVectorShuffle(EvalInfo &Info, const ShuffleVectorExpr *E,
QualType ElemType, APValue const &VecVal1,
APValue const &VecVal2, unsigned EltNum,
@@ -16070,6 +16092,8 @@ class IntExprEvaluator
bool VisitUnaryOperator(const UnaryOperator *E);
bool VisitCastExpr(const CastExpr* E);
+ bool
+ VisitElementwiseSaturatingCastExpr(const ElementwiseSaturatingCastExpr *E);
bool VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *E);
bool VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E) {
@@ -20209,6 +20233,17 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
llvm_unreachable("unknown cast resulting in integral value");
}
+bool IntExprEvaluator::VisitElementwiseSaturatingCastExpr(
+ const ElementwiseSaturatingCastExpr *E) {
+ APSInt Source;
+ if (!EvaluateInteger(E->getSrcExpr(), Source, Info))
+ return false;
+ return Success(
+ Source.extOrTruncSat(Info.Ctx.getIntWidth(E->getType()),
+ E->getType()->isUnsignedIntegerOrEnumerationType()),
+ E);
+}
+
bool IntExprEvaluator::VisitUnaryReal(const UnaryOperator *E) {
if (E->getSubExpr()->getType()->isAnyComplexType()) {
ComplexValue LV;
@@ -22522,6 +22557,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
case Expr::ObjCAvailabilityCheckExprClass:
case Expr::ShuffleVectorExprClass:
case Expr::ConvertVectorExprClass:
+ case Expr::ElementwiseSaturatingCastExprClass:
case Expr::BlockExprClass:
case Expr::NoStmtClass:
case Expr::OpaqueValueExprClass:
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 9b3589a527d03..43c1c856055fd 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5098,6 +5098,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
case Expr::PredefinedExprClass:
case Expr::ShuffleVectorExprClass:
case Expr::ConvertVectorExprClass:
+ case Expr::ElementwiseSaturatingCastExprClass:
case Expr::StmtExprClass:
case Expr::ArrayTypeTraitExprClass:
case Expr::ExpressionTraitExprClass:
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index f2a26ae83c605..733ab3254633f 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2011,6 +2011,15 @@ void StmtPrinter::VisitConvertVectorExpr(ConvertVectorExpr *Node) {
OS << ")";
}
+void StmtPrinter::VisitElementwiseSaturatingCastExpr(
+ ElementwiseSaturatingCastExpr *Node) {
+ OS << "__builtin_elementwise_saturating_cast(";
+ PrintExpr(Node->getSrcExpr());
+ OS << ", ";
+ Node->getTypeSourceInfo()->getType().print(OS, Policy);
+ OS << ")";
+}
+
void StmtPrinter::VisitInitListExpr(InitListExpr* Node) {
if (Node->getSyntacticForm()) {
Visit(Node->getSyntacticForm());
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 49d013be43d26..13ab5269ec4c2 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -1680,6 +1680,11 @@ void StmtProfiler::VisitConvertVectorExpr(const ConvertVectorExpr *S) {
VisitExpr(S);
}
+void StmtProfiler::VisitElementwiseSaturatingCastExpr(
+ const ElementwiseSaturatingCastExpr *S) {
+ VisitExpr(S);
+}
+
void StmtProfiler::VisitChooseExpr(const ChooseExpr *S) {
VisitExpr(S);
}
diff --git a/clang/lib/AST/TextNodeDumper.cpp b/clang/lib/AST/TextNodeDumper.cpp
index 45981683ad171..53cb14ee2c56c 100644
--- a/clang/lib/AST/TextNodeDumper.cpp
+++ b/clang/lib/AST/TextNodeDumper.cpp
@@ -3430,3 +3430,8 @@ void TextNodeDumper::VisitConvertVectorExpr(const ConvertVectorExpr *S) {
if (S->hasStoredFPFeatures())
printFPOptions(S->getStoredFPFeatures());
}
+
+void TextNodeDumper::VisitElementwiseSaturatingCastExpr(
+ const ElementwiseSaturatingCastExpr *S) {
+ VisitStmt(S);
+}
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 308c45bcbee4f..c441694224972 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -650,6 +650,7 @@ class ScalarExprEmitter
Value *VisitMatrixSubscriptExpr(MatrixSubscriptExpr *E);
Value *VisitShuffleVectorExpr(ShuffleVectorExpr *E);
Value *VisitConvertVectorExpr(ConvertVectorExpr *E);
+ Value *VisitElementwiseSaturatingCastExpr(ElementwiseSaturatingCastExpr *E);
Value *VisitMemberExpr(MemberExpr *E);
Value *VisitExtVectorElementExpr(Expr *E) { return EmitLoadOfLValue(E); }
Value *VisitMatrixElementExpr(Expr *E) { return EmitLoadOfLValue(E); }
@@ -2142,6 +2143,98 @@ Value *ScalarExprEmitter::VisitConvertVectorExpr(ConvertVectorExpr *E) {
return Res;
}
+Value *ScalarExprEmitter::VisitElementwiseSaturatingCastExpr(
+ ElementwiseSaturatingCastExpr *E) {
+ Value *Src = CGF.EmitScalarExpr(E->getSrcExpr());
+ QualType SrcType = E->getSrcExpr()->getType();
+ QualType DstType = E->getType();
+
+ assert((SrcType->isIntegerType() || SrcType->isVectorType()) &&
+ "Elementwise saturating cast source must be an integer or vector");
+ assert((DstType->isIntegerType() || DstType->isVectorType()) &&
+ "Elementwise saturating cast destination must be an integer or "
+ "vector");
+
+ const auto *SrcVecTy = SrcType->getAs<VectorType>();
+ const auto *DstVecTy = DstType->getAs<VectorType>();
+ assert((SrcVecTy == nullptr) == (DstVecTy == nullptr) &&
+ "Elementwise saturating cast source and destination must both be "
+ "scalars or vectors");
+ if (SrcVecTy)
+ assert(SrcVecTy->getNumElements() == DstVecTy->getNumElements() &&
+ "Elementwise saturating cast vectors must have the same length");
+
+ QualType SrcEltType = SrcVecTy ? SrcVecTy->getElementType() : SrcType;
+ QualType DstEltType = DstVecTy ? DstVecTy->getElementType() : DstType;
+ assert(SrcEltType->isIntegerType() &&
+ "Elementwise saturating cast source element must be an integer");
+ assert(DstEltType->isIntegerType() &&
+ "Elementwise saturating cast destination element must be an integer");
+
+ llvm::Type *SrcIRTy = Src->getType();
+ llvm::Type *DstIRTy = CGF.ConvertType(DstType);
+ unsigned SrcBits = CGF.getContext().getIntWidth(SrcEltType);
+ unsigned DstBits = CGF.getContext().getIntWidth(DstEltType);
+ bool SrcSigned = SrcEltType->isSignedIntegerOrEnumerationType();
+ bool DstSigned = DstEltType->isSignedIntegerOrEnumerationType();
+ auto MakeConstant = [&](const llvm::APInt &Bound) -> Value * {
+ llvm::Constant *ConstantValue =
+ llvm::ConstantInt::get(CGF.getLLVMContext(), Bound);
+ if (SrcIRTy->isVectorTy())
+ return llvm::ConstantVector::getSplat(
+ cast<llvm::VectorType>(SrcIRTy)->getElementCount(), ConstantValue);
+ return ConstantValue;
+ };
+
+ if (!SrcSigned && !DstSigned) {
+ if (SrcBits <= DstBits)
+ return CGF.Builder.CreateIntCast(Src, DstIRTy, /*isSigned=*/false,
+ "sat.cast");
+ Value *Result = CGF.Builder.CreateBinaryIntrinsic(
+ llvm::Intrinsic::umin, Src,
+ MakeConstant(llvm::APInt::getMaxValue(DstBits).zext(SrcBits)), nullptr,
+ "sat.cast");
+ return CGF.Builder.CreateIntCast(Result, DstIRTy, /*isSigned=*/false,
+ "sat.cast");
+ } else if (!SrcSigned && DstSigned) {
+ if (SrcBits < DstBits)
+ return CGF.Builder.CreateIntCast(Src, DstIRTy, /*isSigned=*/false,
+ "sat.cast");
+ llvm::APInt DstMax = llvm::APInt::getSignedMaxValue(DstBits);
+ if (SrcBits > DstBits)
+ DstMax = DstMax.zext(SrcBits);
+ Value *Result = CGF.Builder.CreateBinaryIntrinsic(
+ llvm::Intrinsic::umin, Src, MakeConstant(DstMax), nullptr, "sat.cast");
+ return CGF.Builder.CreateIntCast(Result, DstIRTy, /*isSigned=*/false,
+ "sat.cast");
+ } else if (SrcSigned && !DstSigned) {
+ Value *Result = CGF.Builder.CreateBinaryIntrinsic(
+ llvm::Intrinsic::smax, Src, MakeConstant(llvm::APInt(SrcBits, 0)),
+ nullptr, "sat.cast");
+ if (SrcBits > DstBits)
+ Result = CGF.Builder.CreateBinaryIntrinsic(
+ llvm::Intrinsic::smin, Result,
+ MakeConstant(llvm::APInt::getMaxValue(DstBits).zext(SrcBits)),
+ nullptr, "sat.cast");
+ return CGF.Builder.CreateIntCast(Result, DstIRTy, /*isSigned=*/false,
+ "sat.cast");
+ } else {
+ if (SrcBits <= DstBits)
+ return CGF.Builder.CreateIntCast(Src, DstIRTy, /*isSigned=*/true,
+ "sat.cast");
+ Value *Result = CGF.Builder.CreateBinaryIntrinsic(
+ llvm::Intrinsic::smax, Src,
+ MakeConstant(llvm::APInt::getSignedMinValue(DstBits).sext(SrcBits)),
+ nullptr, "sat.cast");
+ Result = CGF.Builder.CreateBinaryIntrinsic(
+ llvm::Intrinsic::smin, Result,
+ MakeConstant(llvm::APInt::getSignedMaxValue(DstBits).sext(SrcBits)),
+ nullptr, "sat.cast");
+ return CGF.Builder.CreateIntCast(Result, DstIRTy, /*isSigned=*/true,
+ "sat.cast");
+ }
+}
+
Value *ScalarExprEmitter::VisitMemberExpr(MemberExpr *E) {
if (CodeGenFunction::ConstantEmission Constant = CGF.tryEmitAsConstant(E)) {
CGF.EmitIgnoredExpr(E->getBase());
diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp
index ef89b77d0a997..7c0c09b081774 100644
--- a/clang/lib/Parse/ParseExpr.cpp
+++ b/clang/lib/Parse/ParseExpr.cpp
@@ -1092,6 +1092,7 @@ Parser::ParseCastExpression(CastParseKind ParseKind, bool isAddressOfOperand,
case tok::kw___builtin_choose_expr:
case tok::kw___builtin_astype: // primary-expression: [OCL] as_type()
case tok::kw___builtin_convertvector:
+ case tok::kw___builtin_elementwise_saturating_cast:
case tok::kw___builtin_COLUMN:
case tok::kw___builtin_FILE:
case tok::kw___builtin_FILE_NAME:
@@ -2556,7 +2557,8 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() {
ConsumeParen());
break;
}
- case tok::kw___builtin_convertvector: {
+ case tok::kw___builtin_convertvector:
+ case tok::kw___builtin_elementwise_saturating_cast: {
// The first argument is an expression to be converted, followed by a comma.
ExprResult Expr(ParseAssignmentExpression());
if (Expr.isInvalid()) {
@@ -2569,7 +2571,7 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() {
return ExprError();
}
- // Second argument is the type to bitcast to.
+ // Second argument is the destination type.
TypeResult DestTy = ParseTypeName();
if (DestTy.isInvalid())
return ExprError();
@@ -2581,8 +2583,13 @@ ExprResult Parser::ParseBuiltinPrimaryExpression() {
return ExprError();
}
- Res = Actions.ActOnConvertVectorExpr(Expr.get(), DestTy.get(), StartLoc,
- ConsumeParen());
+ SourceLocation RParenLoc = ConsumeParen();
+ if (T == tok::kw___builtin_convertvector)
+ Res = Actions.ActOnConvertVectorExpr(Expr.get(), DestTy.get(), StartLoc,
+ RParenLoc);
+ else
+ Res = Actions.ActOnElementwiseSaturatingCastExpr(Expr.get(), DestTy.get(),
+ StartLoc, RParenLoc);
break;
}
case tok::kw___builtin_COLUMN:
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 687af75c1496b..32fcef360d8b6 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -6698,6 +6698,59 @@ ExprResult Sema::ConvertVectorExpr(Expr *E, TypeSourceInfo *TInfo,
RParenLoc, CurFPFeatureOverrides());
}
+ExprResult Sema::ElementwiseSaturatingCastExpr(Expr *E, TypeSourceInfo *TInfo,
+ SourceLocation BuiltinLoc,
+ SourceLocation RParenLoc) {
+ ExprValueKind VK = VK_PRValue;
+ ExprObjectKind OK = OK_Ordinary;
+ QualType DstTy = TInfo->getType();
+
+ ExprResult Source = BuiltinVectorMathConversions(*this, E);
+ if (Source.isInvalid())
+ return ExprError();
+
+ QualType SourceTy = Source.get()->getType();
+ if (SourceTy->isDependentType() || DstTy->isDependentType())
+ return ElementwiseSaturatingCastExpr::Create(Context, Source.get(), TInfo,
+ Context.DependentTy, VK, OK,
+ BuiltinLoc, RParenLoc);
+
+ if (checkMathBuiltinElementType(*this, Source.get()->getBeginLoc(), SourceTy,
+ EltwiseBuiltinArgTyRestriction::IntegerTy, 1))
+ return ExprError();
+ if (checkMathBuiltinElementType(*this, TInfo->getTypeLoc().getBeginLoc(),
+ DstTy,
+ EltwiseBuiltinArgTyRestriction::IntegerTy, 2))
+ return ExprError();
+
+ const auto *SourceVecTy = SourceTy->getAs<VectorType>();
+ const auto *DestVecTy = DstTy->getAs<VectorType>();
+ if (DestVecTy && !SourceVecTy)
+ return ExprError(
+ Diag(BuiltinLoc, diag::err_vec_builtin_non_vector)
+ << &Context.Idents.get("__builtin_elementwise_saturating_cast") << false
+ << SourceRange(E->getBeginLoc(), RParenLoc));
+
+ if (SourceVecTy && DestVecTy &&
+ SourceVecTy->getNumElements() != DestVecTy->getNumElements())
+ return ExprError(
+ Diag(BuiltinLoc, diag::err_typecheck_vector_lengths_not_equal)
+ << SourceTy << DstTy << false
+ << SourceRange(E->getBeginLoc(), RParenLoc));
+
+ QualType ResultTy = DstTy;
+ if (SourceVecTy && !DestVecTy) {
+ ResultTy =
+ SourceVecTy->isExtVectorType()
+ ? Context.getExtVectorType(DstTy, SourceVecTy->getNumElements())
+ : Context.getVectorType(DstTy, SourceVecTy->getNumElements(),
+ VectorKind::Generic);
+ }
+
+ return ElementwiseSaturatingCastExpr::Create(
+ Context, Source.get(), TInfo, ResultTy, VK, OK, BuiltinLoc, RParenLoc);
+}
+
bool Sema::BuiltinPrefetch(CallExpr *TheCall) {
unsigned NumArgs = TheCall->getNumArgs();
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 56fe626f8192f..7ce7240f96feb 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1301,6 +1301,7 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
case Expr::ShuffleVectorExprClass:
case Expr::StmtExprClass:
case Expr::ConvertVectorExprClass:
+ case Expr::ElementwiseSaturatingCastExprClass:
case Expr::VAArgExprClass:
case Expr::CXXParenListInitExprClass:
case Expr::CXXExpansionSelectExprClass:
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 729186875128d..373056910cf7c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7219,6 +7219,15 @@ ExprResult Sema::ActOnConvertVectorExpr(Expr *E, ParsedType ParsedDestTy,
return ConvertVectorExpr(E, TInfo, BuiltinLoc, RParenLoc);
}
+ExprResult Sema::ActOnElementwiseSaturatingCastExpr(Expr *E,
+ ParsedType ParsedDestTy,
+ SourceLocation BuiltinLoc,
+ SourceLocation RParenLoc) {
+ TypeSourceInfo *TInfo;
+ GetTypeFromParser(ParsedDestTy, &TInfo);
+ return ElementwiseSaturatingCastExpr(E, TInfo, BuiltinLoc, RParenLoc);
+}
+
ExprResult Sema::BuildResolvedCallExpr(Expr *Fn, NamedDecl *NDecl,
SourceLocation LParenLoc,
ArrayRef<Expr *> Args,
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index d362dd7bf999c..981b6ca3dc2de 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -4094,6 +4094,15 @@ class TreeTransform {
return SemaRef.ConvertVectorExpr(SrcExpr, DstTInfo, BuiltinLoc, RParenLoc);
}
+ /// Build a new __builtin_elementwise_saturating_cast expression.
+ ExprResult RebuildElementwiseSaturatingCastExpr(SourceLocation BuiltinLoc,
+ Expr *SrcExpr,
+ TypeSourceInfo *DstTInfo,
+ SourceLocation RParenLoc) {
+ return SemaRef.ElementwiseSaturatingCastExpr(SrcExpr, DstTInfo, BuiltinLoc,
+ RParenLoc);
+ }
+
/// Build a new template argument pack expansion.
///
/// By default, performs semantic analysis to build a new pack expansion
@@ -17926,6 +17935,25 @@ TreeTransform<Derived>::TransformConvertVectorExpr(ConvertVectorExpr *E) {
E->getRParenLoc());
}
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformElementwiseSaturatingCastExpr(
+ ElementwiseSaturatingCastExpr *E) {
+ ExprResult SrcExpr = getDerived().TransformExpr(E->getSrcExpr());
+ if (SrcExpr.isInvalid())
+ return ExprError();
+
+ TypeSourceInfo *Type = getDerived().TransformType(E->getTypeSourceInfo());
+ if (!Type)
+ return ExprError();
+
+ if (!getDerived().AlwaysRebuild() && Type == E->getTypeSourceInfo() &&
+ SrcExpr.get() == E->getSrcExpr())
+ return E;
+
+ return getDerived().RebuildElementwiseSaturatingCastExpr(
+ E->getBuiltinLoc(), SrcExpr.get(), Type, E->getRParenLoc());
+}
+
template<typename Derived>
ExprResult
TreeTransform<Derived>::TransformBlockExpr(BlockExpr *E) {
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 15f6af71e8a88..7c88b9bcfb4c8 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1452,6 +1452,15 @@ void ASTStmtReader::VisitConvertVectorExpr(ConvertVectorExpr *E) {
FPOptionsOverride::getFromOpaqueInt(Record.readInt()));
}
+void ASTStmtReader::VisitElementwiseSaturatingCastExpr(
+ ElementwiseSaturatingCastExpr *E) {
+ VisitExpr(E);
+ E->BuiltinLoc = readSourceLocation();
+ E->RParenLoc = readSourceLocation();
+ E->TInfo = readTypeSourceInfo();
+ E->SrcExpr = Record.readSubExpr();
+}
+
void ASTStmtReader::VisitBlockExpr(BlockExpr *E) {
VisitExpr(E);
E->setBlockDecl(readDeclAs<BlockDecl>());
@@ -3548,6 +3557,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
break;
}
+ case EXPR_ELEMENTWISE_SATURATING_CAST:
+ S = new (Context) ElementwiseSaturatingCastExpr(Empty);
+ break;
+
case EXPR_BLOCK:
S = new (Context) BlockExpr(Empty);
break;
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 5d90aa1df9d13..7282a21e6a74c 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -808,6 +808,7 @@ static void AddStmtsExprs(llvm::BitstreamWriter &Stream,
RECORD(EXPR_CHOOSE);
RECORD(EXPR_GNU_NULL);
RECORD(EXPR_SHUFFLE_VECTOR);
+ RECORD(EXPR_ELEMENTWISE_SATURATING_CAST);
RECORD(EXPR_BLOCK);
RECORD(EXPR_GENERIC_SELECTION);
RECORD(EXPR_OBJC_STRING_LITERAL);
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 249f1477a394c..c96bd02f3fab0 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1441,6 +1441,16 @@ void ASTStmtWriter::VisitConvertVectorExpr(ConvertVectorExpr *E) {
Record.push_back(E->getStoredFPFeatures().getAsOpaqueInt());
}
+void ASTStmtWriter::VisitElementwiseSaturatingCastExpr(
+ ElementwiseSaturatingCastExpr *E) {
+ VisitExpr(E);
+ Record.AddSourceLocation(E->getBuiltinLoc());
+ Record.AddSourceLocation(E->getRParenLoc());
+ Record.AddTypeSourceInfo(E->getTypeSourceInfo());
+ Record.AddStmt(E->getSrcExpr());
+ Code = serialization::EXPR_ELEMENTWISE_SATURATING_CAST;
+}
+
void ASTStmtWriter::VisitBlockExpr(BlockExpr *E) {
VisitExpr(E);
Record.AddDeclRef(E->getBlockDecl());
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 059bc4770da91..2b7247b1a158d 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -2190,6 +2190,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::ParenListExprClass:
case Stmt::ShuffleVectorExprClass:
case Stmt::ConvertVectorExprClass:
+ case Stmt::ElementwiseSaturatingCastExprClass:
case Stmt::VAArgExprClass:
case Stmt::CUDAKernelCallExprClass:
case Stmt::OpaqueValueExprClass:
diff --git a/clang/test/CodeGen/builtins-elementwise-saturating-cast.c b/clang/test/CodeGen/builtins-elementwise-saturating-cast.c
new file mode 100644
index 0000000000000..1c3889a335f0b
--- /dev/null
+++ b/clang/test/CodeGen/builtins-elementwise-saturating-cast.c
@@ -0,0 +1,106 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux -emit-llvm -disable-llvm-passes -o - %s | FileCheck %s
+
+typedef int int4 __attribute__((ext_vector_type(4)));
+typedef short short4 __attribute__((ext_vector_type(4)));
+
+short signed_to_narrower_signed(int x) {
+ // CHECK-LABEL: define{{.*}}signext i16 @signed_to_narrower_signed
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: [[LOW_RESULT:%.+]] = call i32 @llvm.smax.i32(i32 [[X]], i32 -32768)
+ // CHECK-NEXT: [[HIGH_RESULT:%.+]] = call i32 @llvm.smin.i32(i32 [[LOW_RESULT]], i32 32767)
+ // CHECK-NEXT: trunc i32 [[HIGH_RESULT]] to i16
+ return __builtin_elementwise_saturating_cast(x, short);
+}
+
+int signed_to_wider_signed(short x) {
+ // CHECK-LABEL: define{{.*}}i32 @signed_to_wider_signed
+ // CHECK: [[X:%.+]] = load i16, ptr %x.addr
+ // CHECK-NEXT: sext i16 [[X]] to i32
+ return __builtin_elementwise_saturating_cast(x, int);
+}
+
+int signed_to_equal_signed(int x) {
+ // CHECK-LABEL: define{{.*}}i32 @signed_to_equal_signed
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: ret i32 [[X]]
+ return __builtin_elementwise_saturating_cast(x, int);
+}
+
+unsigned short unsigned_to_narrower_unsigned(unsigned x) {
+ // CHECK-LABEL: define{{.*}}zeroext i16 @unsigned_to_narrower_unsigned
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: [[HIGH_RESULT:%.+]] = call i32 @llvm.umin.i32(i32 [[X]], i32 65535)
+ // CHECK-NEXT: trunc i32 [[HIGH_RESULT]] to i16
+ return __builtin_elementwise_saturating_cast(x, unsigned short);
+}
+
+unsigned int unsigned_to_wider_unsigned(unsigned short x) {
+ // CHECK-LABEL: define{{.*}}i32 @unsigned_to_wider_unsigned
+ // CHECK: [[X:%.+]] = load i16, ptr %x.addr
+ // CHECK-NEXT: zext i16 [[X]] to i32
+ return __builtin_elementwise_saturating_cast(x, unsigned int);
+}
+
+unsigned int unsigned_to_equal_unsigned(unsigned x) {
+ // CHECK-LABEL: define{{.*}}i32 @unsigned_to_equal_unsigned
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: ret i32 [[X]]
+ return __builtin_elementwise_saturating_cast(x, unsigned int);
+}
+
+unsigned short signed_to_narrower_unsigned(int x) {
+ // CHECK-LABEL: define{{.*}}zeroext i16 @signed_to_narrower_unsigned
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: [[LOW_RESULT:%.+]] = call i32 @llvm.smax.i32(i32 [[X]], i32 0)
+ // CHECK-NEXT: [[HIGH_RESULT:%.+]] = call i32 @llvm.smin.i32(i32 [[LOW_RESULT]], i32 65535)
+ // CHECK-NEXT: trunc i32 [[HIGH_RESULT]] to i16
+ return __builtin_elementwise_saturating_cast(x, unsigned short);
+}
+
+unsigned int signed_to_wider_unsigned(short x) {
+ // CHECK-LABEL: define{{.*}}i32 @signed_to_wider_unsigned
+ // CHECK: [[X:%.+]] = load i16, ptr %x.addr
+ // CHECK-NEXT: [[LOW_RESULT:%.+]] = call i16 @llvm.smax.i16(i16 [[X]], i16 0)
+ // CHECK-NEXT: zext i16 [[LOW_RESULT]] to i32
+ return __builtin_elementwise_saturating_cast(x, unsigned int);
+}
+
+unsigned int signed_to_equal_unsigned(int x) {
+ // CHECK-LABEL: define{{.*}}i32 @signed_to_equal_unsigned
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: [[LOW_RESULT:%.+]] = call i32 @llvm.smax.i32(i32 [[X]], i32 0)
+ // CHECK-NEXT: ret i32 [[LOW_RESULT]]
+ return __builtin_elementwise_saturating_cast(x, unsigned int);
+}
+
+short unsigned_to_narrower_signed(unsigned x) {
+ // CHECK-LABEL: define{{.*}}signext i16 @unsigned_to_narrower_signed
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: [[HIGH_RESULT:%.+]] = call i32 @llvm.umin.i32(i32 [[X]], i32 32767)
+ // CHECK-NEXT: trunc i32 [[HIGH_RESULT]] to i16
+ return __builtin_elementwise_saturating_cast(x, short);
+}
+
+int unsigned_to_wider_signed(unsigned short x) {
+ // CHECK-LABEL: define{{.*}}i32 @unsigned_to_wider_signed
+ // CHECK: [[X:%.+]] = load i16, ptr %x.addr
+ // CHECK-NEXT: zext i16 [[X]] to i32
+ return __builtin_elementwise_saturating_cast(x, int);
+}
+
+int unsigned_to_equal_signed(unsigned x) {
+ // CHECK-LABEL: define{{.*}}i32 @unsigned_to_equal_signed
+ // CHECK: [[X:%.+]] = load i32, ptr %x.addr
+ // CHECK-NEXT: [[HIGH_RESULT:%.+]] = call i32 @llvm.umin.i32(i32 [[X]], i32 2147483647)
+ // CHECK-NEXT: ret i32 [[HIGH_RESULT]]
+ return __builtin_elementwise_saturating_cast(x, int);
+}
+
+short4 vector_narrow(int4 x) {
+ // CHECK-LABEL: define{{.*}}@vector_narrow
+ // CHECK: [[X:%.+]] = load <4 x i32>, ptr %x.addr
+ // CHECK-NEXT: [[LOW_RESULT:%.+]] = call <4 x i32> @llvm.smax.v4i32(<4 x i32> [[X]], <4 x i32> splat (i32 -32768))
+ // CHECK-NEXT: [[HIGH_RESULT:%.+]] = call <4 x i32> @llvm.smin.v4i32(<4 x i32> [[LOW_RESULT]], <4 x i32> splat (i32 32767))
+ // CHECK-NEXT: trunc <4 x i32> [[HIGH_RESULT]] to <4 x i16>
+ return __builtin_elementwise_saturating_cast(x, short);
+}
diff --git a/clang/test/Sema/builtins-elementwise-saturating-cast.c b/clang/test/Sema/builtins-elementwise-saturating-cast.c
new file mode 100644
index 0000000000000..bb91bdc075050
--- /dev/null
+++ b/clang/test/Sema/builtins-elementwise-saturating-cast.c
@@ -0,0 +1,39 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -triple x86_64-unknown-linux %s
+
+typedef int int4 __attribute__((ext_vector_type(4)));
+typedef short short4 __attribute__((ext_vector_type(4)));
+typedef int int3 __attribute__((ext_vector_type(3)));
+typedef _Bool bool4 __attribute__((ext_vector_type(4)));
+
+void test_valid(int x, int4 v, bool4 bv, _Bool b, char c) {
+ short s = __builtin_elementwise_saturating_cast(x, short);
+ short4 sv = __builtin_elementwise_saturating_cast(v, short);
+ short4 sv2 = __builtin_elementwise_saturating_cast(v, short4);
+ bool4 bsv = __builtin_elementwise_saturating_cast(v, bool4);
+ short4 sbv = __builtin_elementwise_saturating_cast(bv, short);
+
+ (void)__builtin_elementwise_saturating_cast(b, short);
+ (void)__builtin_elementwise_saturating_cast(c, short);
+ enum E { e };
+ (void)__builtin_elementwise_saturating_cast(e, short);
+ (void)__builtin_elementwise_saturating_cast(x, _Bool);
+ (void)__builtin_elementwise_saturating_cast(x, const int);
+}
+
+void test_invalid(int x, int4 v, float f) {
+ (void)__builtin_elementwise_saturating_cast(x, float);
+ // expected-error at -1 {{2nd argument must be a scalar or vector of integer types (was 'float')}}
+
+ (void)__builtin_elementwise_saturating_cast(f, short);
+ // expected-error at -1 {{1st argument must be a scalar or vector of integer types (was 'float')}}
+
+ (void)__builtin_elementwise_saturating_cast(x, short4);
+ // expected-error at -1 {{first two arguments to '__builtin_elementwise_saturating_cast' must be vectors}}
+
+ (void)__builtin_elementwise_saturating_cast((int3){1, 2, 3}, short4);
+ // expected-error at -1 {{vector operands do not have the same number of elements}}
+
+ (void)__builtin_elementwise_saturating_cast(x, short, x);
+ // expected-error at -1 {{expected ')'}}
+
+}
diff --git a/clang/test/Sema/constant-builtins-saturating-cast.cpp b/clang/test/Sema/constant-builtins-saturating-cast.cpp
new file mode 100644
index 0000000000000..17e53d3ab07a9
--- /dev/null
+++ b/clang/test/Sema/constant-builtins-saturating-cast.cpp
@@ -0,0 +1,55 @@
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -fexperimental-new-constant-interpreter %s
+
+typedef int int4 __attribute__((ext_vector_type(4)));
+typedef signed char char4 __attribute__((ext_vector_type(4)));
+typedef bool bool4 __attribute__((ext_vector_type(4)));
+
+// signed_to_narrower_signed
+static_assert(__builtin_elementwise_saturating_cast(-200, signed char) == -128);
+// signed_to_wider_signed
+static_assert(__builtin_elementwise_saturating_cast(static_cast<short>(-200),
+ int) == -200);
+// signed_to_equal_signed
+static_assert(__builtin_elementwise_saturating_cast(-200, int) == -200);
+// unsigned_to_narrower_unsigned
+static_assert(__builtin_elementwise_saturating_cast(300u, unsigned short) == 300);
+// unsigned_to_wider_unsigned
+static_assert(__builtin_elementwise_saturating_cast(static_cast<unsigned short>(300),
+ unsigned int) == 300u);
+// unsigned_to_equal_unsigned
+static_assert(__builtin_elementwise_saturating_cast(300u, unsigned int) == 300u);
+// signed_to_narrower_unsigned
+static_assert(__builtin_elementwise_saturating_cast(-1, unsigned short) == 0);
+// signed_to_wider_unsigned
+static_assert(__builtin_elementwise_saturating_cast(static_cast<short>(-1),
+ unsigned int) == 0);
+// signed_to_equal_unsigned
+static_assert(__builtin_elementwise_saturating_cast(-1, unsigned) == 0);
+// unsigned_to_narrower_signed
+static_assert(__builtin_elementwise_saturating_cast(300u, signed char) == 127);
+// unsigned_to_wider_signed
+static_assert(__builtin_elementwise_saturating_cast(static_cast<unsigned short>(300),
+ int) == 300);
+// unsigned_to_equal_signed
+static_assert(__builtin_elementwise_saturating_cast(300u, int) == 300);
+static_assert(__builtin_elementwise_saturating_cast(true, int) == 1);
+static_assert(__builtin_elementwise_saturating_cast(2, bool) == true);
+
+static_assert(__builtin_elementwise_saturating_cast(-1, unsigned char) == 0);
+static_assert(__builtin_elementwise_saturating_cast(300, unsigned char) == 255);
+
+constexpr char4 from_scalar_type =
+ __builtin_elementwise_saturating_cast((int4){-200, -1, 0, 300}, signed char);
+constexpr char4 from_vector_type =
+ __builtin_elementwise_saturating_cast((int4){-200, -1, 0, 300}, char4);
+
+static_assert(from_scalar_type[0] == -128 && from_scalar_type[1] == -1 &&
+ from_scalar_type[2] == 0 && from_scalar_type[3] == 127);
+static_assert(from_vector_type[0] == -128 && from_vector_type[1] == -1 &&
+ from_vector_type[2] == 0 && from_vector_type[3] == 127);
+
+constexpr bool4 from_bool_vector = __builtin_elementwise_saturating_cast(
+ (int4){0, -1, 2, 0}, bool4);
+static_assert(!from_bool_vector[0] && !from_bool_vector[1] &&
+ from_bool_vector[2] && !from_bool_vector[3]);
diff --git a/llvm/include/llvm/ADT/APSInt.h b/llvm/include/llvm/ADT/APSInt.h
index 17d055f7193a5..7eec2ec771cd2 100644
--- a/llvm/include/llvm/ADT/APSInt.h
+++ b/llvm/include/llvm/ADT/APSInt.h
@@ -122,6 +122,27 @@ class [[nodiscard]] APSInt : public APInt {
return APSInt(sextOrTrunc(width), IsUnsigned);
}
+ /// Truncate to \p width with saturation according to this integer's
+ /// signedness.
+ ///
+ /// \param width The destination bit width.
+ APSInt truncSat(uint32_t width) const {
+ if (IsUnsigned)
+ return APSInt(truncUSat(width), IsUnsigned);
+ return APSInt(truncSSat(width), IsUnsigned);
+ }
+
+ /// Extend to \p width when possible, or saturate to the destination's
+ /// representable range when the value does not fit.
+ ///
+ /// \param width The destination bit width.
+ /// \param toUnsigned Whether the destination is unsigned.
+ LLVM_ABI APSInt extOrTruncSat(uint32_t width, bool toUnsigned) const;
+
+ APSInt extOrTruncSat(uint32_t width) const {
+ return extOrTruncSat(width, IsUnsigned);
+ }
+
const APSInt &operator%=(const APSInt &RHS) {
assert(IsUnsigned == RHS.IsUnsigned && "Signedness mismatch!");
if (IsUnsigned)
diff --git a/llvm/lib/Support/APSInt.cpp b/llvm/lib/Support/APSInt.cpp
index 5a9f44f304a27..5449231ff8318 100644
--- a/llvm/lib/Support/APSInt.cpp
+++ b/llvm/lib/Support/APSInt.cpp
@@ -37,6 +37,28 @@ APSInt::APSInt(StringRef Str) {
*this = APSInt(Tmp, /*isUnsigned=*/true);
}
+APSInt APSInt::extOrTruncSat(uint32_t width, bool toUnsigned) const {
+ if (IsUnsigned && toUnsigned) {
+ if (width >= getBitWidth())
+ return extend(width);
+ return APSInt(truncUSat(width), /*isUnsigned=*/true);
+ } else if (IsUnsigned) {
+ if (width > getBitWidth())
+ return APSInt(zext(width), /*isUnsigned=*/false);
+ return APSInt(truncUSat(width - 1).zext(width),
+ /*isUnsigned=*/false);
+ } else if (toUnsigned) {
+ if (width >= getBitWidth())
+ return APSInt(sext(width + 1).truncSSatU(width),
+ /*isUnsigned=*/true);
+ return APSInt(truncSSatU(width), /*isUnsigned=*/true);
+ } else {
+ if (width >= getBitWidth())
+ return APSInt(sext(width), /*isUnsigned=*/false);
+ return truncSat(width);
+ }
+}
+
void APSInt::Profile(FoldingSetNodeID& ID) const {
ID.AddInteger((unsigned) (IsUnsigned ? 1 : 0));
APInt::Profile(ID);
diff --git a/llvm/unittests/ADT/APSIntTest.cpp b/llvm/unittests/ADT/APSIntTest.cpp
index 2d2a64433da94..233972f4e51c8 100644
--- a/llvm/unittests/ADT/APSIntTest.cpp
+++ b/llvm/unittests/ADT/APSIntTest.cpp
@@ -94,6 +94,53 @@ TEST(APSIntTest, tryExtValue) {
APSInt(APInt::getAllOnes(128), true).tryExtValue().value_or(42));
}
+TEST(APSIntTest, TruncSat) {
+ // Signed to signed.
+ EXPECT_EQ(APSInt::get(127).trunc(8), APSInt::get(127).truncSat(8));
+ EXPECT_EQ(APSInt::get(127).trunc(8), APSInt::get(300).truncSat(8));
+ EXPECT_EQ(APSInt::get(-128).trunc(8), APSInt::get(-200).truncSat(8));
+
+ // Unsigned to unsigned.
+ EXPECT_EQ(APSInt::getUnsigned(255).trunc(8),
+ APSInt::getUnsigned(255).truncSat(8));
+ EXPECT_EQ(APSInt::getUnsigned(255).trunc(8),
+ APSInt::getUnsigned(300).truncSat(8));
+}
+
+TEST(APSIntTest, ExtOrTruncSat) {
+ // Signed to signed.
+ EXPECT_EQ(APSInt::get(127).trunc(8),
+ APSInt::get(127).extOrTruncSat(8, false));
+ EXPECT_EQ(APSInt::get(127).trunc(8),
+ APSInt::get(300).extOrTruncSat(8, false));
+ EXPECT_EQ(APSInt::get(-128).trunc(8),
+ APSInt::get(-200).extOrTruncSat(8, false));
+ EXPECT_EQ(APSInt::get(300).trunc(16),
+ APSInt::get(300).extOrTruncSat(16, false));
+ EXPECT_EQ(APSInt::get(127).trunc(8),
+ APSInt::get(127).extOrTruncSat(8, false));
+
+ // Signed to unsigned.
+ EXPECT_EQ(APSInt::getUnsigned(0).trunc(8),
+ APSInt::get(-1).extOrTruncSat(8, true));
+ EXPECT_EQ(APSInt::getUnsigned(255).trunc(8),
+ APSInt::get(300).extOrTruncSat(8, true));
+ EXPECT_EQ(APSInt::getUnsigned(0).trunc(9),
+ APSInt::get(-1).trunc(8).extOrTruncSat(9, true));
+
+ // Unsigned to signed.
+ EXPECT_EQ(APSInt::get(127).trunc(8),
+ APSInt::getUnsigned(300).extOrTruncSat(8, false));
+ EXPECT_EQ(APSInt::get(200).trunc(9),
+ APSInt::getUnsigned(200).trunc(8).extOrTruncSat(9, false));
+
+ // Unsigned to unsigned.
+ EXPECT_EQ(APSInt::getUnsigned(255).trunc(8),
+ APSInt::getUnsigned(255).extOrTruncSat(8, true));
+ EXPECT_EQ(APSInt::getUnsigned(255).trunc(8),
+ APSInt::getUnsigned(300).extOrTruncSat(8, true));
+}
+
TEST(APSIntTest, compareValues) {
auto U = [](uint64_t V) { return APSInt::getUnsigned(V); };
auto S = [](int64_t V) { return APSInt::get(V); };
>From 017acb204f87d849e263676b962d53b93939dcf8 Mon Sep 17 00:00:00 2001
From: Eisenwave <me at eisenwave.net>
Date: Wed, 30 Sep 2026 11:12:04 +0200
Subject: [PATCH 2/2] Fix missing switch case
---
clang/tools/libclang/CXCursor.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index e042e12ea4c06..e3c30619f1bc9 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -337,6 +337,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
case Stmt::ShuffleVectorExprClass:
case Stmt::SourceLocExprClass:
case Stmt::ConvertVectorExprClass:
+ case Stmt::ElementwiseSaturatingCastExprClass:
case Stmt::VAArgExprClass:
case Stmt::ObjCArrayLiteralClass:
case Stmt::ObjCDictionaryLiteralClass:
More information about the llvm-commits
mailing list