Skip to content

Commit

Permalink
[CIR] [CodeGen] Introduce IsFPClassOp to support builtin_isfpclass (#971
Browse files Browse the repository at this point in the history
)

The llvm's intrinsic `llvm.is.fpclass` is used to support multiple float
point builtins:
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.

I meant to support this by creating IntrinsicCallOp directly. But I
can't make it due to #480 since
the return type of the intrinsic will mismatch. So I have to create a
new Op for it. But I feel it might not be too bad. At least it is more
explicit and more expressive.
  • Loading branch information
ChuanqiXu9 authored Oct 21, 2024
1 parent e744086 commit f50ca24
Show file tree
Hide file tree
Showing 8 changed files with 447 additions and 35 deletions.
33 changes: 33 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -4027,6 +4027,39 @@ def FMinOp : BinaryFPToFPBuiltinOp<"fmin", "MinNumOp">;
def FModOp : BinaryFPToFPBuiltinOp<"fmod", "FRemOp">;
def PowOp : BinaryFPToFPBuiltinOp<"pow", "PowOp">;

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

//===----------------------------------------------------------------------===//
// Assume Operations
//===----------------------------------------------------------------------===//
Expand Down
3 changes: 2 additions & 1 deletion clang/include/clang/CIR/Dialect/IR/CIRTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ def CIR_LongDouble : CIR_FloatType<"LongDouble", "long_double"> {

// Constraints

def CIR_AnyFloat: AnyTypeOf<[CIR_Single, CIR_Double, CIR_FP80, CIR_FP128, CIR_LongDouble]>;
def CIR_AnyFloat: AnyTypeOf<[CIR_Single, CIR_Double, CIR_FP80, CIR_FP128, CIR_LongDouble,
CIR_FP16, CIR_BFloat16]>;
def CIR_AnyIntOrFloat: AnyTypeOf<[CIR_AnyFloat, CIR_IntType]>;

//===----------------------------------------------------------------------===//
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 @@ -584,6 +584,11 @@ class CIRGenBuilderTy : public CIRBaseBuilderTy {
getAttr<mlir::cir::FPAttr>(t, fpVal));
}

mlir::cir::IsFPClassOp createIsFPClass(mlir::Location loc, mlir::Value src,
unsigned flags) {
return create<mlir::cir::IsFPClassOp>(loc, src, flags);
}

/// Create constant nullptr for pointer-to-data-member type ty.
mlir::cir::ConstantOp getNullDataMemberPtr(mlir::cir::DataMemberType ty,
mlir::Location loc) {
Expand Down
148 changes: 115 additions & 33 deletions clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ static RValue buildLibraryCall(CIRGenFunction &CGF, const FunctionDecl *FD,
return CGF.buildCall(E->getCallee()->getType(), callee, E, ReturnValueSlot());
}

static mlir::Value tryUseTestFPKind(CIRGenFunction &CGF, unsigned BuiltinID,
mlir::Value V) {
if (CGF.getBuilder().getIsFPConstrained() &&
CGF.getBuilder().getDefaultConstrainedExcept() != fp::ebIgnore) {
if (mlir::Value Result = CGF.getTargetHooks().testFPKind(
V, BuiltinID, CGF.getBuilder(), CGF.CGM))
return Result;
}
return nullptr;
}

template <class Operation>
static RValue buildUnaryFPBuiltin(CIRGenFunction &CGF, const CallExpr &E) {
auto Arg = CGF.buildScalarExpr(E.getArg(0));
Expand Down Expand Up @@ -1191,36 +1202,6 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
case Builtin::BI__builtin_isunordered:
llvm_unreachable("BI__builtin_isgreater and BI__builtin_isless like NYI");

case Builtin::BI__builtin_isnan:
llvm_unreachable("BI__builtin_isnan NYI");

case Builtin::BI__builtin_issignaling:
llvm_unreachable("BI__builtin_issignaling NYI");

case Builtin::BI__builtin_isinf:
llvm_unreachable("BI__builtin_isinf NYI");

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:
llvm_unreachable("Builtin::BIfinite like NYI");

case Builtin::BI__builtin_isnormal:
llvm_unreachable("BI__builtin_isnormal NYI");

case Builtin::BI__builtin_issubnormal:
llvm_unreachable("BI__builtin_issubnormal NYI");

case Builtin::BI__builtin_iszero:
llvm_unreachable("BI__builtin_iszero NYI");

case Builtin::BI__builtin_isfpclass:
llvm_unreachable("BI__builtin_isfpclass NYI");

case Builtin::BI__builtin_nondeterministic_value:
llvm_unreachable("BI__builtin_nondeterministic_value NYI");

Expand Down Expand Up @@ -1328,9 +1309,6 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
case Builtin::BI__builtin_matrix_column_major_store:
llvm_unreachable("BI__builtin_matrix_column_major_store NYI");

case Builtin::BI__builtin_isinf_sign:
llvm_unreachable("BI__builtin_isinf_sign NYI");

case Builtin::BI__builtin_flt_rounds:
llvm_unreachable("BI__builtin_flt_rounds NYI");

Expand Down Expand Up @@ -2080,6 +2058,110 @@ RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
llvm_unreachable("BI__builtin_ms_va_copy NYI");
case Builtin::BI__builtin_get_device_side_mangled_name:
llvm_unreachable("BI__builtin_get_device_side_mangled_name NYI");

// 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 = buildScalarExpr(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 = buildScalarExpr(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 = buildScalarExpr(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 = buildScalarExpr(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 = buildScalarExpr(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 = buildScalarExpr(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 = buildScalarExpr(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 = buildScalarExpr(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
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/CodeGen/TargetInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace cir {

class CIRGenFunction;
class CIRGenModule;
class CIRGenBuilderTy;

/// This class organizes various target-specific codegeneration issues, like
/// target-specific attributes, builtins and so on.
Expand All @@ -43,6 +44,15 @@ class TargetCIRGenInfo {
return false;
}

/// 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 {};
}

/// Corrects the MLIR type for a given constraint and "usual"
/// type.
///
Expand Down
29 changes: 28 additions & 1 deletion clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4119,6 +4119,33 @@ class CIRThrowOpLowering
}
};

class CIRIsFPClassOpLowering
: public mlir::OpConversionPattern<mlir::cir::IsFPClassOp> {
public:
using OpConversionPattern<mlir::cir::IsFPClassOp>::OpConversionPattern;

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

auto loc = op->getLoc();

auto intrinsic =
rewriter.create<mlir::LLVM::IsFPClass>(loc, retTy, src, flags);
// FIMXE: CIR now will convert cir::BoolType to i8 type unconditionally.
// Remove this conversion after we fix
// https://github.com/llvm/clangir/issues/480
auto converted = rewriter.create<mlir::LLVM::ZExtOp>(
loc, rewriter.getI8Type(), intrinsic->getResult(0));

rewriter.replaceOp(op, converted);
return mlir::success();
}
};

void populateCIRToLLVMConversionPatterns(mlir::RewritePatternSet &patterns,
mlir::TypeConverter &converter,
mlir::DataLayout &dataLayout) {
Expand Down Expand Up @@ -4151,7 +4178,7 @@ void populateCIRToLLVMConversionPatterns(mlir::RewritePatternSet &patterns,
CIREhTypeIdOpLowering, CIRCatchParamOpLowering, CIRResumeOpLowering,
CIRAllocExceptionOpLowering, CIRFreeExceptionOpLowering,
CIRThrowOpLowering, CIRIntrinsicCallLowering, CIRBaseClassAddrOpLowering,
CIRVTTAddrPointOpLowering
CIRVTTAddrPointOpLowering, CIRIsFPClassOpLowering
#define GET_BUILTIN_LOWERING_LIST
#include "clang/CIR/Dialect/IR/CIRBuiltinsLowering.inc"
#undef GET_BUILTIN_LOWERING_LIST
Expand Down
Loading

0 comments on commit f50ca24

Please sign in to comment.