[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