llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir Author: Andy Kaylor (andykaylor) <details> <summary>Changes</summary> This adds support for the cir.unary operation. --- Patch is 40.74 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131369.diff 11 Files Affected: - (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+49) - (modified) clang/include/clang/CIR/MissingFeatures.h (+9) - (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+48) - (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+223) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+2) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+4) - (modified) clang/lib/CIR/CodeGen/CIRGenValue.h (+1) - (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+41) - (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+124-1) - (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h (+10) - (added) clang/test/CIR/CodeGen/unary.cpp (+392) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 77c43e5ace64a..52c78ffe42647 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -468,6 +468,55 @@ def BrOp : CIR_Op<"br", }]; } +//===----------------------------------------------------------------------===// +// UnaryOp +//===----------------------------------------------------------------------===// + +def UnaryOpKind_Inc : I32EnumAttrCase<"Inc", 1, "inc">; +def UnaryOpKind_Dec : I32EnumAttrCase<"Dec", 2, "dec">; +def UnaryOpKind_Plus : I32EnumAttrCase<"Plus", 3, "plus">; +def UnaryOpKind_Minus : I32EnumAttrCase<"Minus", 4, "minus">; +def UnaryOpKind_Not : I32EnumAttrCase<"Not", 5, "not">; + +def UnaryOpKind : I32EnumAttr< + "UnaryOpKind", + "unary operation kind", + [UnaryOpKind_Inc, + UnaryOpKind_Dec, + UnaryOpKind_Plus, + UnaryOpKind_Minus, + UnaryOpKind_Not, + ]> { + let cppNamespace = "::cir"; +} + +// FIXME: Pure won't work when we add overloading. +def UnaryOp : CIR_Op<"unary", [Pure, SameOperandsAndResultType]> { + let summary = "Unary operations"; + let description = [{ + `cir.unary` performs the unary operation according to + the specified opcode kind: [inc, dec, plus, minus, not]. + + It requires one input operand and has one result, both types + should be the same. + + ```mlir + %7 = cir.unary(inc, %1) : i32 -> i32 + %8 = cir.unary(dec, %2) : i32 -> i32 + ``` + }]; + + let results = (outs CIR_AnyType:$result); + let arguments = (ins Arg<UnaryOpKind, "unary op kind">:$kind, Arg<CIR_AnyType>:$input); + + let assemblyFormat = [{ + `(` $kind `,` $input `)` `:` type($input) `,` type($result) attr-dict + }]; + + let hasVerifier = 1; + let hasFolder = 1; +} + //===----------------------------------------------------------------------===// // GlobalOp //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 6c3d74cf96c64..fcbb2ae3db6aa 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -72,6 +72,10 @@ struct MissingFeatures { static bool opFuncLinkage() { return false; } static bool opFuncVisibility() { return false; } + // Unary operator handling + static bool opUnarySignedOverflow() { return false; } + static bool opUnaryPromotionType() { return false; } + // Misc static bool scalarConversionOpts() { return false; } static bool tryEmitAsConstant() { return false; } @@ -86,6 +90,11 @@ struct MissingFeatures { static bool aggValueSlot() { return false; } static bool unsizedTypes() { return false; } + static bool sanitizers() { return false; } + static bool CGFPOptionsRAII() { return false; } + + // Missing types + static bool vectorType() { return false; } }; } // namespace cir diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 5b81fe172e645..24c0c8a18efd8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -165,6 +165,54 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) { return LValue(); } +LValue CIRGenFunction::emitUnaryOpLValue(const UnaryOperator *e) { + UnaryOperatorKind op = e->getOpcode(); + + // __extension__ doesn't affect lvalue-ness. + if (op == UO_Extension) + return emitLValue(e->getSubExpr()); + + switch (op) { + case UO_Deref: { + cgm.errorNYI(e->getSourceRange(), "UnaryOp dereference"); + return LValue(); + } + case UO_Real: + case UO_Imag: { + cgm.errorNYI(e->getSourceRange(), "UnaryOp real/imag"); + return LValue(); + } + case UO_PreInc: + case UO_PreDec: { + bool isInc = e->isIncrementOp(); + LValue lv = emitLValue(e->getSubExpr()); + + assert(e->isPrefix() && "Prefix operator in unexpected state!"); + + if (e->getType()->isAnyComplexType()) { + cgm.errorNYI(e->getSourceRange(), "UnaryOp complex inc/dec"); + return LValue(); + } else { + emitScalarPrePostIncDec(e, lv, isInc, /*isPre=*/true); + } + + return lv; + } + case UO_Extension: + llvm_unreachable("UnaryOperator extension should be handled above!"); + case UO_Plus: + case UO_Minus: + case UO_Not: + case UO_LNot: + case UO_AddrOf: + case UO_PostInc: + case UO_PostDec: + case UO_Coawait: + llvm_unreachable("UnaryOperator of non-lvalue kind!"); + } + llvm_unreachable("Unknown unary operator kind!"); +} + /// Emit code to compute the specified expression which /// can have any type. The result is returned as an RValue struct. RValue CIRGenFunction::emitAnyExpr(const Expr *e) { diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index b9e56dc4123d6..b0d644faade17 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -92,6 +92,222 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value VisitCastExpr(CastExpr *E); + // Unary Operators. + mlir::Value VisitUnaryPostDec(const UnaryOperator *e) { + LValue lv = cgf.emitLValue(e->getSubExpr()); + return emitScalarPrePostIncDec(e, lv, false, false); + } + mlir::Value VisitUnaryPostInc(const UnaryOperator *e) { + LValue lv = cgf.emitLValue(e->getSubExpr()); + return emitScalarPrePostIncDec(e, lv, true, false); + } + mlir::Value VisitUnaryPreDec(const UnaryOperator *e) { + LValue lv = cgf.emitLValue(e->getSubExpr()); + return emitScalarPrePostIncDec(e, lv, false, true); + } + mlir::Value VisitUnaryPreInc(const UnaryOperator *e) { + LValue lv = cgf.emitLValue(e->getSubExpr()); + return emitScalarPrePostIncDec(e, lv, true, true); + } + mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv, + bool isInc, bool isPre) { + if (cgf.getLangOpts().OpenMP) + cgf.cgm.errorNYI(e->getSourceRange(), "inc/dec OpenMP"); + + QualType type = e->getSubExpr()->getType(); + + mlir::Value value; + mlir::Value input; + + if (type->getAs<AtomicType>()) { + cgf.cgm.errorNYI(e->getSourceRange(), "Atomic inc/dec"); + // TODO(cir): This is not correct, but it will produce reasonable code + // until atomic operations are implemented. + value = cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal(); + input = value; + } else { + value = cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal(); + input = value; + } + + // NOTE: When possible, more frequent cases are handled first. + + // Special case of integer increment that we have to check first: bool++. + // Due to promotion rules, we get: + // bool++ -> bool = bool + 1 + // -> bool = (int)bool + 1 + // -> bool = ((int)bool + 1 != 0) + // An interesting aspect of this is that increment is always true. + // Decrement does not have this property. + if (isInc && type->isBooleanType()) { + value = builder.create<cir::ConstantOp>(cgf.getLoc(e->getExprLoc()), + cgf.convertType(type), + builder.getCIRBoolAttr(true)); + } else if (type->isIntegerType()) { + QualType promotedType; + bool canPerformLossyDemotionCheck = false; + if (cgf.getContext().isPromotableIntegerType(type)) { + promotedType = cgf.getContext().getPromotedIntegerType(type); + assert(promotedType != type && "Shouldn't promote to the same type."); + canPerformLossyDemotionCheck = true; + canPerformLossyDemotionCheck &= + cgf.getContext().getCanonicalType(type) != + cgf.getContext().getCanonicalType(promotedType); + canPerformLossyDemotionCheck &= + type->isIntegerType() && promotedType->isIntegerType(); + + // TODO(cir): Currently, we store bitwidths in CIR types only for + // integers. This might also be required for other types. + auto srcCirTy = mlir::dyn_cast<cir::IntType>(cgf.convertType(type)); + auto promotedCirTy = + mlir::dyn_cast<cir::IntType>(cgf.convertType(type)); + assert(srcCirTy && promotedCirTy && "Expected integer type"); + + assert( + (!canPerformLossyDemotionCheck || + type->isSignedIntegerOrEnumerationType() || + promotedType->isSignedIntegerOrEnumerationType() || + srcCirTy.getWidth() == promotedCirTy.getWidth()) && + "The following check expects that if we do promotion to different " + "underlying canonical type, at least one of the types (either " + "base or promoted) will be signed, or the bitwidths will match."); + } + + assert(!cir::MissingFeatures::sanitizers()); + if (e->canOverflow() && type->isSignedIntegerOrEnumerationType()) { + value = emitIncDecConsiderOverflowBehavior(e, value, isInc); + } else { + cir::UnaryOpKind kind = + e->isIncrementOp() ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec; + // NOTE(CIR): clang calls CreateAdd but folds this to a unary op + value = emitUnaryOp(e, kind, input); + } + } else if (const PointerType *ptr = type->getAs<PointerType>()) { + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec pointer"); + return {}; + } else if (type->isVectorType()) { + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec vector"); + return {}; + } else if (type->isRealFloatingType()) { + assert(!cir::MissingFeatures::CGFPOptionsRAII()); + + if (type->isHalfType() && + !cgf.getContext().getLangOpts().NativeHalfType) { + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec half"); + return {}; + } + + if (mlir::isa<cir::SingleType, cir::DoubleType>(value.getType())) { + // Create the inc/dec operation. + // NOTE(CIR): clang calls CreateAdd but folds this to a unary op + cir::UnaryOpKind kind = + (isInc ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec); + value = emitUnaryOp(e, kind, value); + } else { + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fp type"); + return {}; + } + } else if (type->isFixedPointType()) { + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fixed point"); + return {}; + } else { + assert(type->castAs<ObjCObjectPointerType>()); + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec ObjectiveC pointer"); + return {}; + } + + CIRGenFunction::SourceLocRAIIObject sourceloc{ + cgf, cgf.getLoc(e->getSourceRange())}; + + // Store the updated result through the lvalue + if (lv.isBitField()) { + cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec bitfield"); + return {}; + } else { + cgf.emitStoreThroughLValue(RValue::get(value), lv); + } + + // If this is a postinc, return the value read from memory, otherwise use + // the updated value. + return isPre ? value : input; + } + + mlir::Value emitIncDecConsiderOverflowBehavior(const UnaryOperator *e, + mlir::Value inVal, + bool isInc) { + assert(!cir::MissingFeatures::opUnarySignedOverflow()); + cir::UnaryOpKind kind = + e->isIncrementOp() ? cir::UnaryOpKind::Inc : cir::UnaryOpKind::Dec; + switch (cgf.getLangOpts().getSignedOverflowBehavior()) { + case LangOptions::SOB_Defined: + return emitUnaryOp(e, kind, inVal); + case LangOptions::SOB_Undefined: + assert(!cir::MissingFeatures::sanitizers()); + return emitUnaryOp(e, kind, inVal); + break; + case LangOptions::SOB_Trapping: + if (!e->canOverflow()) + return emitUnaryOp(e, kind, inVal); + cgf.cgm.errorNYI(e->getSourceRange(), "inc/def overflow SOB_Trapping"); + return {}; + } + llvm_unreachable("Unexpected signed overflow behavior kind"); + } + + mlir::Value VisitUnaryPlus(const UnaryOperator *e, + QualType promotionType = QualType()) { + if (!promotionType.isNull()) + cgf.cgm.errorNYI(e->getSourceRange(), "VisitUnaryPlus: promotionType"); + assert(!cir::MissingFeatures::opUnaryPromotionType()); + mlir::Value result = VisitPlus(e); + return result; + } + + mlir::Value VisitPlus(const UnaryOperator *e) { + // This differs from gcc, though, most likely due to a bug in gcc. + ignoreResultAssign = false; + + assert(!cir::MissingFeatures::opUnaryPromotionType()); + mlir::Value operand = Visit(e->getSubExpr()); + + return emitUnaryOp(e, cir::UnaryOpKind::Plus, operand); + } + + mlir::Value VisitUnaryMinus(const UnaryOperator *e, + QualType promotionType = QualType()) { + if (!promotionType.isNull()) + cgf.cgm.errorNYI(e->getSourceRange(), "VisitUnaryMinus: promotionType"); + assert(!cir::MissingFeatures::opUnaryPromotionType()); + mlir::Value result = VisitMinus(e); + return result; + } + + mlir::Value VisitMinus(const UnaryOperator *e) { + ignoreResultAssign = false; + + assert(!cir::MissingFeatures::opUnaryPromotionType()); + mlir::Value operand = Visit(e->getSubExpr()); + + assert(!cir::MissingFeatures::opUnarySignedOverflow()); + + // NOTE: LLVM codegen will lower this directly to either a FNeg + // or a Sub instruction. In CIR this will be handled later in LowerToLLVM. + return emitUnaryOp(e, cir::UnaryOpKind::Minus, operand); + } + + mlir::Value emitUnaryOp(const UnaryOperator *e, cir::UnaryOpKind kind, + mlir::Value input) { + return builder.create<cir::UnaryOp>( + cgf.getLoc(e->getSourceRange().getBegin()), input.getType(), kind, + input); + } + + mlir::Value VisitUnaryNot(const UnaryOperator *e) { + ignoreResultAssign = false; + mlir::Value op = Visit(e->getSubExpr()); + return emitUnaryOp(e, cir::UnaryOpKind::Not, op); + } + /// Emit a conversion from the specified type to the specified destination /// type, both of which are CIR scalar types. /// TODO: do we need ScalarConversionOpts here? Should be done in another @@ -148,3 +364,10 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *ce) { } return {}; } + +mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *E, + LValue LV, bool isInc, + bool isPre) { + return ScalarExprEmitter(*this, builder) + .emitScalarPrePostIncDec(E, LV, isInc, isPre); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 47d296b70d789..2338ec9cd952a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -305,6 +305,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { std::string("l-value not implemented for '") + e->getStmtClassName() + "'"); return LValue(); + case Expr::UnaryOperatorClass: + return emitUnaryOpLValue(cast<UnaryOperator>(e)); case Expr::DeclRefExprClass: return emitDeclRefLValue(cast<DeclRefExpr>(e)); } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 5ab882666f3e0..3542b6cafbc9c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -206,6 +206,7 @@ class CIRGenFunction : public CIRGenTypeCache { LValue lvalue, bool capturedByInit = false); LValue emitDeclRefLValue(const clang::DeclRefExpr *e); + LValue emitUnaryOpLValue(const clang::UnaryOperator *e); /// Determine whether the given initializer is trivial in the sense /// that it requires no code to be generated. @@ -305,6 +306,9 @@ class CIRGenFunction : public CIRGenTypeCache { // TODO: Add symbol table support } + mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv, + bool isInc, bool isPre); + /// Emit the computation of the specified expression of scalar type. mlir::Value emitScalarExpr(const clang::Expr *e); cir::FuncOp generateCode(clang::GlobalDecl gd, cir::FuncOp fn, diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h index d29646983fd30..c559e853aad39 100644 --- a/clang/lib/CIR/CodeGen/CIRGenValue.h +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -93,6 +93,7 @@ class LValue { public: bool isSimple() const { return lvType == Simple; } + bool isBitField() const { return lvType == BitField; } // TODO: Add support for volatile bool isVolatile() const { return false; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index d041791770d82..faf8996434f74 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -457,6 +457,47 @@ void cir::FuncOp::print(OpAsmPrinter &p) { // been implemented yet. mlir::LogicalResult cir::FuncOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// UnaryOp +//===----------------------------------------------------------------------===// + +LogicalResult cir::UnaryOp::verify() { + switch (getKind()) { + case cir::UnaryOpKind::Inc: + case cir::UnaryOpKind::Dec: + case cir::UnaryOpKind::Plus: + case cir::UnaryOpKind::Minus: + case cir::UnaryOpKind::Not: + // Nothing to verify. + return success(); + } + + llvm_unreachable("Unknown UnaryOp kind?"); +} + +static bool isBoolNot(cir::UnaryOp op) { + return isa<cir::BoolType>(op.getInput().getType()) && + op.getKind() == cir::UnaryOpKind::Not; +} + +// This folder simplifies the sequential boolean not operations. +// For instance, the next two unary operations will be eliminated: +// +// ```mlir +// %1 = cir.unary(not, %0) : !cir.bool, !cir.bool +// %2 = cir.unary(not, %1) : !cir.bool, !cir.bool +// ``` +// +// and the argument of the first one (%0) will be used instead. +OpFoldResult cir::UnaryOp::fold(FoldAdaptor adaptor) { + if (isBoolNot(*this)) + if (auto previous = dyn_cast_or_null<UnaryOp>(getInput().getDefiningOp())) + if (isBoolNot(previous)) + return previous.getInput(); + + return {}; +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 0cd27ecf1a3bd..a126e1a29de13 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -568,6 +568,128 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite( return mlir::success(); } +mlir::LogicalResult CIRToLLVMUnaryOpLowering::matchAndRewrite( + cir::UnaryOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + assert(op.getType() == op.getInput().getType() && + "Unary operation's operand type and result type are different"); + mlir::Type type = op.getType(); + mlir::Type elementType = type; + bool isVector = false; + assert(!cir::MissingFeatures::vectorType()); + mlir::Type llvmType = getTypeConverter()->convertType(type); + mlir::Location loc = op.getLoc(); + + auto createIntConstant = [&](int64_t value) -> mlir::Value { + return rewriter.create<mlir::LLVM::ConstantOp>( + loc, llvmType, mlir::IntegerAttr::get(llvmType, value)); + }; + + auto createFloatConstant = [&](double value) -> mlir::Value { + mlir::FloatAttr attr = rewriter.getFloatAttr(llvmType, value); + return rewriter.create<mlir::LLVM::ConstantOp>(loc, llvmType, attr); + }; + + // Integer unary operations: + - ~ ++ -- + if (mlir::isa<cir::IntType>(elementType)) { + mlir::LLVM::IntegerOverflowFlags maybeNSW = + mlir::LLVM::IntegerOverflowFlags::none; + if (mlir::dyn_cast<cir::IntType>(elementType).isSigned()) { + assert(!cir::MissingFeatures::opUnarySignedOverflow... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/131369 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits