https://github.com/badumbatish updated https://github.com/llvm/llvm-project/pull/166037
>From 3ab831e2335304bf83c076d07bb233697708d4d6 Mon Sep 17 00:00:00 2001 From: Jasmine Tang <[email protected]> Date: Sat, 1 Nov 2025 20:32:55 -0700 Subject: [PATCH] Initial implementation for isfpclass and related builtins Add lowering test --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 16 +++ clang/include/clang/CIR/Dialect/IR/CIROps.td | 33 +++++ clang/include/clang/CIR/Dialect/IR/FPEnv.h | 50 +++++++ clang/include/clang/CIR/MissingFeatures.h | 3 + clang/lib/CIR/CodeGen/CIRGenBuilder.h | 59 ++++++++ clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 115 +++++++++++++++- clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 69 ++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 19 +++ clang/lib/CIR/CodeGen/TargetInfo.h | 8 ++ clang/lib/CIR/Dialect/IR/CMakeLists.txt | 1 + clang/lib/CIR/Dialect/IR/FPEnv.cpp | 64 +++++++++ .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 11 ++ clang/test/CIR/CodeGen/builtin-isfpclass.c | 129 ++++++++++++++++++ clang/test/CIR/Lowering/builtin_isfpclass.c | 125 +++++++++++++++++ 14 files changed, 701 insertions(+), 1 deletion(-) create mode 100644 clang/include/clang/CIR/Dialect/IR/FPEnv.h create mode 100644 clang/lib/CIR/Dialect/IR/FPEnv.cpp create mode 100644 clang/test/CIR/CodeGen/builtin-isfpclass.c create mode 100644 clang/test/CIR/Lowering/builtin_isfpclass.c diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 3288f5b12c77e..a680ac58cc904 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -442,6 +442,22 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return createCast(cir::CastKind::ptr_to_bool, v, getBoolTy()); } + // TODO(cir): the following function was introduced to keep in sync with LLVM + // codegen. CIR does not have "zext" operations. It should eventually be + // renamed or removed. For now, we just add whatever cast is required here. + mlir::Value createZExtOrBitCast(mlir::Location loc, mlir::Value src, + mlir::Type newTy) { + auto srcTy = src.getType(); + + if (srcTy == newTy) + return src; + + if (mlir::isa<cir::BoolType>(srcTy) && mlir::isa<cir::IntType>(newTy)) + return createBoolToInt(src, newTy); + + llvm_unreachable("unhandled extension cast"); + } + mlir::Value createBoolToInt(mlir::Value src, mlir::Type newTy) { return createCast(cir::CastKind::bool_to_int, src, newTy); } diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 86d09d72fe6ca..a6ee8c668efec 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -3921,6 +3921,39 @@ def CIR_RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> { let hasFolder = 1; } +def CIR_IsFPClassOp : CIR_Op<"is_fp_class"> { + let summary = "Corresponding to the `__builtin_fpclassify` builtin function in clang"; + + let description = [{ + The `cir.is_fp_class` operation takes a floating-point value as its first + argument and a bitfield of flags as its second argument. The operation + returns a boolean value indicating whether the floating-point value + satisfies the given flags. + + The flags must be a compile time constant and the values are: + + | Bit # | floating-point class | + | -------- | ------- | + | 0 | Signaling NaN | + | 1 | Quiet NaN | + | 2 | Negative infinity | + | 3 | Negative normal | + | 4 | Negative subnormal | + | 5 | Negative zero | + | 6 | Positive zero | + | 7 | Positive subnormal | + | 8 | Positive normal | + | 9 | Positive infinity | + }]; + + let arguments = (ins CIR_AnyFloatType:$src, + I32Attr:$flags); + let results = (outs CIR_BoolType:$result); + let assemblyFormat = [{ + $src `,` $flags `:` functional-type($src, $result) attr-dict + }]; +} + //===----------------------------------------------------------------------===// // Assume Operations //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/FPEnv.h b/clang/include/clang/CIR/Dialect/IR/FPEnv.h new file mode 100644 index 0000000000000..aceba9ee57d05 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/FPEnv.h @@ -0,0 +1,50 @@ +//===- FPEnv.h ---- FP Environment ------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// @file +/// This file contains the declarations of entities that describe floating +/// point environment and related functions. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_CIR_DIALECT_IR_FPENV_H +#define CLANG_CIR_DIALECT_IR_FPENV_H + +#include "llvm/ADT/FloatingPointMode.h" + +#include <optional> + +namespace cir { + +namespace fp { + +/// Exception behavior used for floating point operations. +/// +/// Each of these values corresponds to some LLVMIR metadata argument value of a +/// constrained floating point intrinsic. See the LLVM Language Reference Manual +/// for details. +enum ExceptionBehavior : uint8_t { + ebIgnore, ///< This corresponds to "fpexcept.ignore". + ebMayTrap, ///< This corresponds to "fpexcept.maytrap". + ebStrict, ///< This corresponds to "fpexcept.strict". +}; + +} // namespace fp + +/// For any RoundingMode enumerator, returns a string valid as input in +/// constrained intrinsic rounding mode metadata. +std::optional<llvm::StringRef> convertRoundingModeToStr(llvm::RoundingMode); + +/// For any ExceptionBehavior enumerator, returns a string valid as input in +/// constrained intrinsic exception behavior metadata. +std::optional<llvm::StringRef> + convertExceptionBehaviorToStr(fp::ExceptionBehavior); + +} // namespace cir + +#endif diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 598e826a473a6..d9bbd64d029a1 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -256,7 +256,10 @@ struct MissingFeatures { static bool emitNullabilityCheck() { return false; } static bool emitTypeCheck() { return false; } static bool emitTypeMetadataCodeForVCall() { return false; } + static bool fastMathGuard() { return false; } static bool fastMathFlags() { return false; } + static bool fastMathFuncAttributes() { return false; } + static bool fpConstraints() { return false; } static bool generateDebugInfo() { return false; } static bool globalViewIndices() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h index 50d585dca3b8c..f525f24918cb6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h +++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h @@ -16,6 +16,7 @@ #include "mlir/IR/BuiltinAttributes.h" #include "mlir/Support/LLVM.h" #include "clang/CIR/Dialect/IR/CIRDataLayout.h" +#include "clang/CIR/Dialect/IR/FPEnv.h" #include "clang/CIR/MissingFeatures.h" #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" @@ -29,6 +30,9 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy { const CIRGenTypeCache &typeCache; llvm::StringMap<unsigned> recordNames; llvm::StringMap<unsigned> globalsVersioning; + bool isFpConstrained = false; + cir::fp::ExceptionBehavior defaultConstrainedExcept = cir::fp::ebStrict; + llvm::RoundingMode defaultConstrainedRounding = llvm::RoundingMode::Dynamic; public: CIRGenBuilderTy(mlir::MLIRContext &mlirContext, const CIRGenTypeCache &tc) @@ -66,6 +70,56 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy { cir::ArrayType arrayTy) const { return cir::ConstArrayAttr::get(arrayTy, attrs); } + // + // Floating point specific helpers + // ------------------------------- + // + + /// Enable/Disable use of constrained floating point math. When enabled the + /// CreateF<op>() calls instead create constrained floating point intrinsic + /// calls. Fast math flags are unaffected by this setting. + void setIsFPConstrained(bool isCon) { + if (isCon) + llvm_unreachable("Constrained FP NYI"); + isFpConstrained = isCon; + } + + /// Query for the use of constrained floating point math + bool getIsFPConstrained() { + if (isFpConstrained) + llvm_unreachable("Constrained FP NYI"); + return isFpConstrained; + } + /// + /// Set the exception handling to be used with constrained floating point + void setDefaultConstrainedExcept(cir::fp::ExceptionBehavior newExcept) { +#ifndef NDEBUG + std::optional<llvm::StringRef> exceptStr = + cir::convertExceptionBehaviorToStr(newExcept); + assert(exceptStr && "Garbage strict exception behavior!"); +#endif + defaultConstrainedExcept = newExcept; + } + + /// Set the rounding mode handling to be used with constrained floating point + void setDefaultConstrainedRounding(llvm::RoundingMode newRounding) { +#ifndef NDEBUG + std::optional<llvm::StringRef> roundingStr = + cir::convertRoundingModeToStr(newRounding); + assert(roundingStr && "Garbage strict rounding mode!"); +#endif + defaultConstrainedRounding = newRounding; + } + + /// Get the exception handling used with constrained floating point + cir::fp::ExceptionBehavior getDefaultConstrainedExcept() { + return defaultConstrainedExcept; + } + + /// Get the rounding mode handling used with constrained floating point + llvm::RoundingMode getDefaultConstrainedRounding() { + return defaultConstrainedRounding; + } mlir::Attribute getConstRecordOrZeroAttr(mlir::ArrayAttr arrayAttr, bool packed = false, @@ -332,6 +386,11 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy { llvm_unreachable("negation for the given type is NYI"); } + cir::IsFPClassOp createIsFPClass(mlir::Location loc, mlir::Value src, + unsigned flags) { + return cir::IsFPClassOp::create(*this, loc, src, flags); + } + // TODO: split this to createFPExt/createFPTrunc when we have dedicated cast // operations. mlir::Value createFloatingCast(mlir::Value v, mlir::Type destType) { diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp index 798e9d9fbb99e..4088018738d57 100644 --- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp @@ -29,6 +29,16 @@ using namespace clang; using namespace clang::CIRGen; using namespace llvm; +static mlir::Value tryUseTestFPKind(CIRGenFunction &cgf, unsigned BuiltinID, + mlir::Value V) { + if (cgf.getBuilder().getIsFPConstrained() && + cgf.getBuilder().getDefaultConstrainedExcept() != cir::fp::ebIgnore) { + if (mlir::Value Result = cgf.getTargetHooks().testFPKind( + V, BuiltinID, cgf.getBuilder(), cgf.cgm)) + return Result; + } + return nullptr; +} static RValue emitLibraryCall(CIRGenFunction &cgf, const FunctionDecl *fd, const CallExpr *e, mlir::Operation *calleeValue) { CIRGenCallee callee = CIRGenCallee::forDirect(calleeValue, GlobalDecl(fd)); @@ -454,14 +464,117 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID, assert(!cir::MissingFeatures::coroSizeBuiltinCall()); return getUndefRValue(e->getType()); } + // From https://clang.llvm.org/docs/LanguageExtensions.html#builtin-isfpclass + // : + // + // The `__builtin_isfpclass()` builtin is a generalization of functions + // isnan, isinf, isfinite and some others defined by the C standard. It tests + // if the floating-point value, specified by the first argument, falls into + // any of data classes, specified by the second argument. + case Builtin::BI__builtin_isnan: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + if (mlir::Value result = tryUseTestFPKind(*this, builtinID, v)) + return RValue::get(result); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcNan), + convertType(e->getType()))); + } + + case Builtin::BI__builtin_issignaling: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcSNan), + convertType(e->getType()))); + } + + case Builtin::BI__builtin_isinf: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + if (mlir::Value result = tryUseTestFPKind(*this, builtinID, v)) + return RValue::get(result); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcInf), + convertType(e->getType()))); + } + + case Builtin::BIfinite: + case Builtin::BI__finite: + case Builtin::BIfinitef: + case Builtin::BI__finitef: + case Builtin::BIfinitel: + case Builtin::BI__finitel: + case Builtin::BI__builtin_isfinite: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + if (mlir::Value result = tryUseTestFPKind(*this, builtinID, v)) + return RValue::get(result); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcFinite), + convertType(e->getType()))); + } + + case Builtin::BI__builtin_isnormal: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcNormal), + convertType(e->getType()))); + } + + case Builtin::BI__builtin_issubnormal: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcSubnormal), + convertType(e->getType()))); + } + + case Builtin::BI__builtin_iszero: { + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + mlir::Location loc = getLoc(e->getBeginLoc()); + // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, FPClassTest::fcZero), + convertType(e->getType()))); + } + case Builtin::BI__builtin_isfpclass: { + Expr::EvalResult result; + if (!e->getArg(1)->EvaluateAsInt(result, cgm.getASTContext())) + break; + + CIRGenFunction::CIRGenFPOptionsRAII fpOptsRaii(*this, e); + mlir::Value v = emitScalarExpr(e->getArg(0)); + uint64_t test = result.Val.getInt().getLimitedValue(); + mlir::Location loc = getLoc(e->getBeginLoc()); + // + // // FIXME: We should use builder.createZExt once createZExt is available. + return RValue::get(builder.createZExtOrBitCast( + loc, builder.createIsFPClass(loc, v, test), convertType(e->getType()))); + } } // If this is an alias for a lib function (e.g. __builtin_sin), emit // the call using the normal call path, but using the unmangled // version of the function name. - if (getContext().BuiltinInfo.isLibFunction(builtinID)) + if (getContext().BuiltinInfo.isLibFunction(builtinID)) { return emitLibraryCall(*this, fd, e, cgm.getBuiltinLibFunction(fd, builtinID)); + } // Some target-specific builtins can have aggregate return values, e.g. // __builtin_arm_mve_vld2q_u32. So if the result is an aggregate, force diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index d3c0d9f109317..8b1856da22130 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -805,6 +805,22 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) { cgm.errorNYI(dtor->getSourceRange(), "function-try-block destructor"); } +// Map the LangOption for exception behavior into the corresponding enum in +// the IR. +static cir::fp::ExceptionBehavior +toConstrainedExceptMd(LangOptions::FPExceptionModeKind kind) { + switch (kind) { + case LangOptions::FPE_Ignore: + return cir::fp::ebIgnore; + case LangOptions::FPE_MayTrap: + return cir::fp::ebMayTrap; + case LangOptions::FPE_Strict: + return cir::fp::ebStrict; + default: + llvm_unreachable("Unsupported FP Exception Behavior"); + } +} + /// Given a value of type T* that may not be to a complete object, construct /// an l-vlaue withi the natural pointee alignment of T. LValue CIRGenFunction::makeNaturalAlignPointeeAddrLValue(mlir::Value val, @@ -975,6 +991,59 @@ void CIRGenFunction::emitNullInitialization(mlir::Location loc, Address destPtr, builder.createStore(loc, zeroValue, destPtr); } +CIRGenFunction::CIRGenFPOptionsRAII::CIRGenFPOptionsRAII(CIRGenFunction &cgf, + const clang::Expr *e) + : cgf(cgf) { + constructorHelper(e->getFPFeaturesInEffect(cgf.getLangOpts())); +} + +CIRGenFunction::CIRGenFPOptionsRAII::CIRGenFPOptionsRAII(CIRGenFunction &cgf, + FPOptions fpFeatures) + : cgf(cgf) { + constructorHelper(fpFeatures); +} + +void CIRGenFunction::CIRGenFPOptionsRAII::constructorHelper( + FPOptions fpFeatures) { + oldFpFeatures = cgf.curFpFeatures; + cgf.curFpFeatures = fpFeatures; + + oldExcept = cgf.builder.getDefaultConstrainedExcept(); + oldRounding = cgf.builder.getDefaultConstrainedRounding(); + + if (oldFpFeatures == fpFeatures) + return; + + // TODO(cir): create guard to restore fast math configurations. + assert(!cir::MissingFeatures::fastMathGuard()); + + llvm::RoundingMode newRoundingBehavior = fpFeatures.getRoundingMode(); + // TODO(cir): override rounding behaviour once FM configs are guarded. + auto newExceptionBehavior = + toConstrainedExceptMd(static_cast<LangOptions::FPExceptionModeKind>( + fpFeatures.getExceptionMode())); + // TODO(cir): override exception behaviour once FM configs are guarded. + + // TODO(cir): override FP flags once FM configs are guarded. + assert(!cir::MissingFeatures::fastMathFlags()); + + assert((cgf.curFuncDecl == nullptr || cgf.builder.getIsFPConstrained() || + isa<CXXConstructorDecl>(cgf.curFuncDecl) || + isa<CXXDestructorDecl>(cgf.curFuncDecl) || + (newExceptionBehavior == cir::fp::ebIgnore && + newRoundingBehavior == llvm::RoundingMode::NearestTiesToEven)) && + "FPConstrained should be enabled on entire function"); + + // TODO(cir): mark CIR function with fast math attributes. + assert(!cir::MissingFeatures::fastMathFuncAttributes()); +} + +CIRGenFunction::CIRGenFPOptionsRAII::~CIRGenFPOptionsRAII() { + cgf.curFpFeatures = oldFpFeatures; + cgf.builder.setDefaultConstrainedExcept(oldExcept); + cgf.builder.setDefaultConstrainedRounding(oldRounding); +} + // TODO(cir): should be shared with LLVM codegen. bool CIRGenFunction::shouldNullCheckClassCastValue(const CastExpr *ce) { const Expr *e = ce->getSubExpr(); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index e3b9b6a8180d9..7d9a93bde4894 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -31,6 +31,7 @@ #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/FPEnv.h" #include "clang/CIR/MissingFeatures.h" #include "clang/CIR/TypeEvaluationKind.h" #include "llvm/ADT/ScopedHashTable.h" @@ -174,6 +175,21 @@ class CIRGenFunction : public CIRGenTypeCache { /// Sanitizers enabled for this function. clang::SanitizerSet sanOpts; + class CIRGenFPOptionsRAII { + public: + CIRGenFPOptionsRAII(CIRGenFunction &cgf, FPOptions fpFeatures); + CIRGenFPOptionsRAII(CIRGenFunction &cgf, const clang::Expr *e); + ~CIRGenFPOptionsRAII(); + + private: + void constructorHelper(clang::FPOptions fpFeatures); + CIRGenFunction &cgf; + clang::FPOptions oldFpFeatures; + cir::fp::ExceptionBehavior oldExcept; + llvm::RoundingMode oldRounding; + }; + clang::FPOptions curFpFeatures; + /// The symbol table maps a variable name to a value in the current scope. /// Entering a function creates a new scope, and the function arguments are /// added to the mapping. When the processing of a function is terminated, @@ -217,6 +233,9 @@ class CIRGenFunction : public CIRGenTypeCache { const TargetInfo &getTarget() const { return cgm.getTarget(); } mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); } + const TargetCIRGenInfo &getTargetHooks() const { + return cgm.getTargetCIRGenInfo(); + } // --------------------- // Opaque value handling // --------------------- diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h index dbb0312c76040..ae55dae4da964 100644 --- a/clang/lib/CIR/CodeGen/TargetInfo.h +++ b/clang/lib/CIR/CodeGen/TargetInfo.h @@ -48,6 +48,14 @@ class TargetCIRGenInfo { virtual cir::TargetAddressSpaceAttr getCIRAllocaAddressSpace() const { return {}; } + /// Performs a target specific test of a floating point value for things + /// like IsNaN, Infinity, ... Nullptr is returned if no implementation + /// exists. + virtual mlir::Value testFPKind(mlir::Value v, unsigned builtinId, + CIRGenBuilderTy &builder, + CIRGenModule &cgm) const { + return {}; + } /// Determine whether a call to an unprototyped functions under /// the given calling convention should use the variadic diff --git a/clang/lib/CIR/Dialect/IR/CMakeLists.txt b/clang/lib/CIR/Dialect/IR/CMakeLists.txt index 98575941035f2..de3c7d7a1c3b9 100644 --- a/clang/lib/CIR/Dialect/IR/CMakeLists.txt +++ b/clang/lib/CIR/Dialect/IR/CMakeLists.txt @@ -4,6 +4,7 @@ add_clang_library(MLIRCIR CIRMemorySlot.cpp CIRTypes.cpp CIRDataLayout.cpp + FPEnv.cpp DEPENDS MLIRCIROpsIncGen diff --git a/clang/lib/CIR/Dialect/IR/FPEnv.cpp b/clang/lib/CIR/Dialect/IR/FPEnv.cpp new file mode 100644 index 0000000000000..719ceb32480c9 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/FPEnv.cpp @@ -0,0 +1,64 @@ +//===-- FPEnv.cpp ---- FP Environment -------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +/// @file +/// This file contains the implementations of entities that describe floating +/// point environment. +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/IR/FPEnv.h" + +namespace cir { + +std::optional<llvm::StringRef> +convertRoundingModeToStr(llvm::RoundingMode useRounding) { + std::optional<llvm::StringRef> roundingStr; + switch (useRounding) { + case llvm::RoundingMode::Dynamic: + roundingStr = "round.dynamic"; + break; + case llvm::RoundingMode::NearestTiesToEven: + roundingStr = "round.tonearest"; + break; + case llvm::RoundingMode::NearestTiesToAway: + roundingStr = "round.tonearestaway"; + break; + case llvm::RoundingMode::TowardNegative: + roundingStr = "round.downward"; + break; + case llvm::RoundingMode::TowardPositive: + roundingStr = "round.upward"; + break; + case llvm::RoundingMode::TowardZero: + roundingStr = "round.towardZero"; + break; + default: + break; + } + return roundingStr; +} + +std::optional<llvm::StringRef> +convertExceptionBehaviorToStr(fp::ExceptionBehavior useExcept) { + std::optional<llvm::StringRef> exceptStr; + switch (useExcept) { + case fp::ebStrict: + exceptStr = "fpexcept.strict"; + break; + case fp::ebIgnore: + exceptStr = "fpexcept.ignore"; + break; + case fp::ebMayTrap: + exceptStr = "fpexcept.maytrap"; + break; + } + return exceptStr; +} + +} // namespace cir diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index bb75f2d94066f..f39d72f2fe593 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -632,6 +632,17 @@ mlir::LogicalResult CIRToLLVMASinOpLowering::matchAndRewrite( return mlir::success(); } +mlir::LogicalResult CIRToLLVMIsFPClassOpLowering::matchAndRewrite( + cir::IsFPClassOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + auto src = adaptor.getSrc(); + auto flags = adaptor.getFlags(); + auto retTy = rewriter.getI1Type(); + + rewriter.replaceOpWithNewOp<mlir::LLVM::IsFPClass>(op, retTy, src, flags); + return mlir::success(); +} + mlir::LogicalResult CIRToLLVMAssumeOpLowering::matchAndRewrite( cir::AssumeOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { diff --git a/clang/test/CIR/CodeGen/builtin-isfpclass.c b/clang/test/CIR/CodeGen/builtin-isfpclass.c new file mode 100644 index 0000000000000..b02069fcbb7f5 --- /dev/null +++ b/clang/test/CIR/CodeGen/builtin-isfpclass.c @@ -0,0 +1,129 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s + +int finite(double); + +// CHECK: cir.func {{.*}}@test_is_finite +void test_is_finite(__fp16 *H, float F, double D, long double LD) { + volatile int res; + res = __builtin_isinf(*H); + // CHECK: cir.is_fp_class %{{.*}}, 516 : (!cir.f16) -> !cir.bool + + res = __builtin_isinf(F); + // CHECK: cir.is_fp_class %{{.*}}, 516 : (!cir.float) -> !cir.bool + + res = __builtin_isinf(D); + // CHECK: cir.is_fp_class %{{.*}}, 516 : (!cir.double) -> !cir.bool + + res = __builtin_isinf(LD); + // CHECK: cir.is_fp_class %{{.*}}, 516 : (!cir.long_double<!cir.f80>) -> !cir.bool + + res = __builtin_isfinite(*H); + // CHECK: cir.is_fp_class %{{.*}}, 504 : (!cir.f16) -> !cir.bool + res = __builtin_isfinite(F); + // CHECK: cir.is_fp_class %{{.*}}, 504 : (!cir.float) -> !cir.bool + res = finite(D); + // CHECK: cir.call @finite(%{{.*}}) nothrow side_effect(const) : (!cir.double) -> !s32i + + res = __builtin_isnormal(*H); + // CHECK: cir.is_fp_class %{{.*}}, 264 : (!cir.f16) -> !cir.bool + res = __builtin_isnormal(F); + // CHECK: cir.is_fp_class %{{.*}}, 264 : (!cir.float) -> !cir.bool + + res = __builtin_issubnormal(F); + // CHECK: cir.is_fp_class %{{.*}}, 144 : (!cir.float) -> !cir.bool + res = __builtin_iszero(F); + // CHECK: cir.is_fp_class %{{.*}}, 96 : (!cir.float) -> !cir.bool + res = __builtin_issignaling(F); + // CHECK: cir.is_fp_class %{{.*}}, 1 : (!cir.float) -> !cir.bool +} + +_Bool check_isfpclass_finite(float x) { + return __builtin_isfpclass(x, 504 /*Finite*/); +} + +// CHECK: cir.func {{.*}}@check_isfpclass_finite +// CHECK: cir.is_fp_class %{{.*}}, 504 : (!cir.float) + +_Bool check_isfpclass_nan_f32(float x) { + return __builtin_isfpclass(x, 3 /*NaN*/); +} + +// CHECK: cir.func {{.*}}@check_isfpclass_nan_f32 +// CHECK: cir.is_fp_class %{{.*}}, 3 : (!cir.float) + + +_Bool check_isfpclass_snan_f64(double x) { + return __builtin_isfpclass(x, 1 /*SNaN*/); +} + +// CHECK: cir.func {{.*}}@check_isfpclass_snan_f64 +// CHECK: cir.is_fp_class %{{.*}}, 1 : (!cir.double) + + +_Bool check_isfpclass_zero_f16(_Float16 x) { + return __builtin_isfpclass(x, 96 /*Zero*/); +} + +// CHECK: cir.func {{.*}}@check_isfpclass_zero_f16 +// CHECK: cir.is_fp_class %{{.*}}, 96 : (!cir.f16) + +// Update when we support FP pragma in functions and can convert BoolType in prvalue to i1. + +// _Bool check_isfpclass_finite_strict(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 504 /*Finite*/); +// } +// +// _Bool check_isfpclass_nan_f32_strict(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } +// +// _Bool check_isfpclass_snan_f64_strict(double x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 1 /*NaN*/); +// } +// +// _Bool check_isfpclass_zero_f16_strict(_Float16 x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 96 /*Zero*/); +// } +// +// _Bool check_isnan(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isnan(x); +// } +// +// _Bool check_isinf(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isinf(x); +// } +// +// _Bool check_isfinite(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfinite(x); +// } +// +// _Bool check_isnormal(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isnormal(x); +// } +// +// typedef float __attribute__((ext_vector_type(4))) float4; +// typedef double __attribute__((ext_vector_type(4))) double4; +// typedef int __attribute__((ext_vector_type(4))) int4; +// typedef long __attribute__((ext_vector_type(4))) long4; +// +// int4 check_isfpclass_nan_v4f32(float4 x) { +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } +// +// int4 check_isfpclass_nan_strict_v4f32(float4 x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } +// +// long4 check_isfpclass_nan_v4f64(double4 x) { +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } diff --git a/clang/test/CIR/Lowering/builtin_isfpclass.c b/clang/test/CIR/Lowering/builtin_isfpclass.c new file mode 100644 index 0000000000000..f3b480b492a3f --- /dev/null +++ b/clang/test/CIR/Lowering/builtin_isfpclass.c @@ -0,0 +1,125 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s + +int finite(double); + +// CHECK: define {{.*}}@test_is_finite +void test_is_finite(__fp16 *H, float F, double D, long double LD) { + volatile int res; + res = __builtin_isinf(*H); + // CHECK: call i1 @llvm.is.fpclass.f16(half %{{.*}}, i32 516) + res = __builtin_isinf(F); + // CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 516) + res = __builtin_isinf(D); + // CHECK: call i1 @llvm.is.fpclass.f64(double %{{.*}}, i32 516) + res = __builtin_isinf(LD); + // CHECK: call i1 @llvm.is.fpclass.f80(x86_fp80 %{{.*}}, i32 516) + + res = __builtin_isfinite(*H); + // CHECK: call i1 @llvm.is.fpclass.f16(half %{{.*}}, i32 504) + res = __builtin_isfinite(F); + // CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 504) + res = finite(D); + // CHECK: call i32 @finite(double %{{.*}}) + + res = __builtin_isnormal(*H); + // CHECK: call i1 @llvm.is.fpclass.f16(half %{{.*}}, i32 264) + res = __builtin_isnormal(F); + // CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 264) + + res = __builtin_issubnormal(F); + // CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 144) + res = __builtin_iszero(F); + // CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 96) + res = __builtin_issignaling(F); + // CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 1) +} + +_Bool check_isfpclass_finite(float x) { + return __builtin_isfpclass(x, 504 /*Finite*/); +} + +// CHECK: define {{.*}}@check_isfpclass_finite +// CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 504) + +_Bool check_isfpclass_nan_f32(float x) { + return __builtin_isfpclass(x, 3 /*NaN*/); +} + +// CHECK: define {{.*}}@check_isfpclass_nan_f32 +// CHECK: call i1 @llvm.is.fpclass.f32(float %{{.*}}, i32 3) + +_Bool check_isfpclass_snan_f64(double x) { + return __builtin_isfpclass(x, 1 /*SNaN*/); +} + +// CHECK: define {{.*}}@check_isfpclass_snan_f64 +// CHECK: call i1 @llvm.is.fpclass.f64(double %{{.*}}, i32 1) + + +_Bool check_isfpclass_zero_f16(_Float16 x) { + return __builtin_isfpclass(x, 96 /*Zero*/); +} + +// CHECK: define {{.*}}@check_isfpclass_zero_f16 +// CHECK: call i1 @llvm.is.fpclass.f16(half %{{.*}}, i32 96) + +// Update when we support FP pragma in functions. + +// _Bool check_isfpclass_finite_strict(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 504 /*Finite*/); +// } +// +// _Bool check_isfpclass_nan_f32_strict(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } +// +// _Bool check_isfpclass_snan_f64_strict(double x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 1 /*NaN*/); +// } +// +// _Bool check_isfpclass_zero_f16_strict(_Float16 x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 96 /*Zero*/); +// } +// +// _Bool check_isnan(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isnan(x); +// } +// +// _Bool check_isinf(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isinf(x); +// } +// +// _Bool check_isfinite(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfinite(x); +// } +// +// _Bool check_isnormal(float x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isnormal(x); +// } +// +// typedef float __attribute__((ext_vector_type(4))) float4; +// typedef double __attribute__((ext_vector_type(4))) double4; +// typedef int __attribute__((ext_vector_type(4))) int4; +// typedef long __attribute__((ext_vector_type(4))) long4; +// +// int4 check_isfpclass_nan_v4f32(float4 x) { +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } +// +// int4 check_isfpclass_nan_strict_v4f32(float4 x) { +// #pragma STDC FENV_ACCESS ON +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } +// +// long4 check_isfpclass_nan_v4f64(double4 x) { +// return __builtin_isfpclass(x, 3 /*NaN*/); +// } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
