https://github.com/eisenwave updated https://github.com/llvm/llvm-project/pull/227603
>From 8527d174330684cfffd5debf5b4878f828342f3b Mon Sep 17 00:00:00 2001 From: Eisenwave <[email protected]> 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 <[email protected]> --- 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@-1 {{2nd argument must be a scalar or vector of integer types (was 'float')}} + + (void)__builtin_elementwise_saturating_cast(f, short); + // expected-error@-1 {{1st argument must be a scalar or vector of integer types (was 'float')}} + + (void)__builtin_elementwise_saturating_cast(x, short4); + // expected-error@-1 {{first two arguments to '__builtin_elementwise_saturating_cast' must be vectors}} + + (void)__builtin_elementwise_saturating_cast((int3){1, 2, 3}, short4); + // expected-error@-1 {{vector operands do not have the same number of elements}} + + (void)__builtin_elementwise_saturating_cast(x, short, x); + // expected-error@-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 <[email protected]> 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: _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
