Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -4018,6 +4018,72 @@ def CIR_RotateOp : CIR_Op<"rotate", [Pure, SameOperandsAndResultType]> {
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// FPClass Test Flags
//===----------------------------------------------------------------------===//

def FPClassTestEnum : CIR_I32EnumAttr<"FPClassTest", "floating-point class test flags", [
// Basic flags
I32EnumAttrCase<"SignalingNaN", 1, "fcSNan">,
I32EnumAttrCase<"QuietNaN", 2, "fcQNan">,
I32EnumAttrCase<"NegativeInfinity", 4, "fcNegInf">,
I32EnumAttrCase<"NegativeNormal", 8, "fcNegNormal">,
I32EnumAttrCase<"NegativeSubnormal", 16, "fcNegSubnormal">,
I32EnumAttrCase<"NegativeZero", 32, "fcNegZero">,
I32EnumAttrCase<"PositiveZero", 64, "fcPosZero">,
I32EnumAttrCase<"PositiveSubnormal", 128, "fcPosSubnormal">,
I32EnumAttrCase<"PositiveNormal", 256, "fcPosNormal">,
I32EnumAttrCase<"PositiveInfinity", 512, "fcPosInf">,

// Composite flags
I32EnumAttrCase<"Nan", 3, "fcNan">, // fcSNan | fcQNan
I32EnumAttrCase<"Infinity", 516, "fcInf">, // fcPosInf | fcNegInf
I32EnumAttrCase<"Normal", 264, "fcNormal">, // fcPosNormal | fcNegNormal
I32EnumAttrCase<"Subnormal", 144, "fcSubnormal">, // fcPosSubnormal | fcNegSubnormal
I32EnumAttrCase<"Zero", 96, "fcZero">, // fcPosZero | fcNegZero
I32EnumAttrCase<"PositiveFinite", 448, "fcPosFinite">,// fcPosNormal | fcPosSubnormal | fcPosZero
I32EnumAttrCase<"NegativeFinite", 56, "fcNegFinite">, // fcNegNormal | fcNegSubnormal | fcNegZero
I32EnumAttrCase<"Finite", 504, "fcFinite">, // fcPosFinite | fcNegFinite
I32EnumAttrCase<"Positive", 960, "fcPositive">, // fcPosFinite | fcPosInf
I32EnumAttrCase<"Negative", 60, "fcNegative">, // fcNegFinite | fcNegInf
I32EnumAttrCase<"All", 1023, "fcAllFlags">, // fcNan | fcInf | fcFinite
]> {
let cppNamespace = "::cir";
}

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,
FPClassTestEnum:$flags);
let results = (outs CIR_BoolType:$result);
let assemblyFormat = [{
$src `,` $flags `:` functional-type($src, $result) attr-dict
}];
}

//===----------------------------------------------------------------------===//
// Assume Operations
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ struct MissingFeatures {
static bool emitTypeCheck() { return false; }
static bool emitTypeMetadataCodeForVCall() { return false; }
static bool fastMathFlags() { return false; }

static bool fpConstraints() { return false; }
static bool generateDebugInfo() { return false; }
static bool globalViewIndices() { return false; }
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,11 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
llvm_unreachable("negation for the given type is NYI");
}

cir::IsFPClassOp createIsFPClass(mlir::Location loc, mlir::Value src,
cir::FPClassTest 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) {
Expand Down
92 changes: 92 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,98 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
cir::PrefetchOp::create(builder, loc, address, locality, isWrite);
return RValue::get(nullptr);
}
// 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: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
assert(!cir::MissingFeatures::fpConstraints());
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::Nan),
convertType(e->getType())));
}

case Builtin::BI__builtin_issignaling: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::SignalingNaN),
convertType(e->getType())));
}

case Builtin::BI__builtin_isinf: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
assert(!cir::MissingFeatures::fpConstraints());
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::Infinity),
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: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
assert(!cir::MissingFeatures::fpConstraints());
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::Finite),
convertType(e->getType())));
}

case Builtin::BI__builtin_isnormal: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::Normal),
convertType(e->getType())));
}

case Builtin::BI__builtin_issubnormal: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::Subnormal),
convertType(e->getType())));
}

case Builtin::BI__builtin_iszero: {
assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
mlir::Location loc = getLoc(e->getBeginLoc());
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest::Zero),
convertType(e->getType())));
}
case Builtin::BI__builtin_isfpclass: {
Expr::EvalResult result;
if (!e->getArg(1)->EvaluateAsInt(result, cgm.getASTContext()))
break;

assert(!cir::MissingFeatures::cgFPOptionsRAII());
mlir::Value v = emitScalarExpr(e->getArg(0));
uint64_t test = result.Val.getInt().getLimitedValue();
mlir::Location loc = getLoc(e->getBeginLoc());
//
return RValue::get(builder.createBoolToInt(
builder.createIsFPClass(loc, v, cir::FPClassTest(test)),
convertType(e->getType())));
}
}

// If this is an alias for a lib function (e.g. __builtin_sin), emit
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,18 @@ mlir::LogicalResult CIRToLLVMASinOpLowering::matchAndRewrite(
return mlir::success();
}

mlir::LogicalResult CIRToLLVMIsFPClassOpLowering::matchAndRewrite(
cir::IsFPClassOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
mlir::Value src = adaptor.getSrc();
cir::FPClassTest flags = adaptor.getFlags();
mlir::IntegerType retTy = rewriter.getI1Type();

rewriter.replaceOpWithNewOp<mlir::LLVM::IsFPClass>(
op, retTy, src, static_cast<uint32_t>(flags));
return mlir::success();
}

mlir::LogicalResult CIRToLLVMAssumeOpLowering::matchAndRewrite(
cir::AssumeOp op, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const {
Expand Down
174 changes: 174 additions & 0 deletions clang/test/CIR/CodeGen/builtin-isfpclass.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=LLVM
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=OGCG
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);
// CIR: cir.is_fp_class %{{.*}}, fcInf : (!cir.f16) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 516)
// OGCG: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 516)

res = __builtin_isinf(F);
// CIR: cir.is_fp_class %{{.*}}, fcInf : (!cir.float) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 516)
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 516)

res = __builtin_isinf(D);
// CIR: cir.is_fp_class %{{.*}}, fcInf : (!cir.double) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f64(double {{.*}}, i32 516)
// OGCG: call i1 @llvm.is.fpclass.f64(double {{.*}}, i32 516)

res = __builtin_isinf(LD);
// CIR: cir.is_fp_class %{{.*}}, fcInf : (!cir.long_double<!cir.f80>) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f80(x86_fp80 {{.*}}, i32 516)
// OGCG: call i1 @llvm.is.fpclass.f80(x86_fp80 {{.*}}, i32 516)

res = __builtin_isfinite(*H);
// CIR: cir.is_fp_class %{{.*}}, fcFinite : (!cir.f16) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 504)
// OGCG: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 504)

res = __builtin_isfinite(F);
// CIR: cir.is_fp_class %{{.*}}, fcFinite : (!cir.float) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 504)
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 504)

res = finite(D);
// CIR: cir.call @finite(%{{.*}}) nothrow side_effect(const) : (!cir.double) -> !s32i
// LLVM: call i32 @finite(double {{.*}})
// OGCG: call i1 @llvm.is.fpclass.f64(double %20, i32 504)
res = __builtin_isnormal(*H);
// CIR: cir.is_fp_class %{{.*}}, fcNormal : (!cir.f16) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 264)
// OGCG: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 264)

res = __builtin_isnormal(F);
// CIR: cir.is_fp_class %{{.*}}, fcNormal : (!cir.float) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 264)
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 264)

res = __builtin_issubnormal(F);
// CIR: cir.is_fp_class %{{.*}}, fcSubnormal : (!cir.float) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 144)
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 144)
res = __builtin_iszero(F);
// CIR: cir.is_fp_class %{{.*}}, fcZero : (!cir.float) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 96)
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 96)
res = __builtin_issignaling(F);
// CIR: cir.is_fp_class %{{.*}}, fcSNan : (!cir.float) -> !cir.bool
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 1)
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 1)
}

_Bool check_isfpclass_finite(float x) {
return __builtin_isfpclass(x, 504 /*Finite*/);
}

// CIR: cir.func {{.*}}@check_isfpclass_finite
// CIR: cir.is_fp_class %{{.*}}, fcFinite : (!cir.float)
// LLVM: @check_isfpclass_finite
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 504)
// OGCG: @check_isfpclass_finite
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 504)

_Bool check_isfpclass_nan_f32(float x) {
return __builtin_isfpclass(x, 3 /*NaN*/);
}

// CIR: cir.func {{.*}}@check_isfpclass_nan_f32
// CIR: cir.is_fp_class %{{.*}}, fcNan : (!cir.float)
// LLVM: @check_isfpclass_nan_f32
// LLVM: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 3)
// OGCG: @check_isfpclass_nan_f32
// OGCG: call i1 @llvm.is.fpclass.f32(float {{.*}}, i32 3)


_Bool check_isfpclass_snan_f64(double x) {
return __builtin_isfpclass(x, 1 /*SNaN*/);
}

// CIR: cir.func {{.*}}@check_isfpclass_snan_f64
// CIR: cir.is_fp_class %{{.*}}, fcSNan : (!cir.double)
// LLVM: @check_isfpclass_snan_f64
// LLVM: call i1 @llvm.is.fpclass.f64(double {{.*}}, i32 1)
// OGCG: @check_isfpclass_snan_f64
// OGCG: call i1 @llvm.is.fpclass.f64(double {{.*}}, i32 1)


_Bool check_isfpclass_zero_f16(_Float16 x) {
return __builtin_isfpclass(x, 96 /*Zero*/);
}

// CIR: cir.func {{.*}}@check_isfpclass_zero_f16
// CIR: cir.is_fp_class %{{.*}}, fcZero : (!cir.f16)
// LLVM: @check_isfpclass_zero_f16
// LLVM: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 96)
// OGCG: @check_isfpclass_zero_f16
// OGCG: call i1 @llvm.is.fpclass.f16(half {{.*}}, i32 96)

// 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*/);
// }