[clang] [CIR] Upstream isfpclass op (PR #166037)
Jasmine Tang via cfe-commits
cfe-commits at lists.llvm.org
Mon Nov 10 10:40:06 PST 2025
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 <jjasmine at igalia.com>
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*/);
+// }
More information about the cfe-commits
mailing list