[clang] [CIR] Upstream initial support for unary op (PR #131369)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Fri Mar 14 10:52:22 PDT 2025
https://github.com/andykaylor created https://github.com/llvm/llvm-project/pull/131369
This adds support for the cir.unary operation.
>From d51bb762224f70b2b879198e2466ca0d258f9eae Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Mon, 10 Mar 2025 15:07:50 -0700
Subject: [PATCH] [CIR] Upstream initial support for unary op
This adds support for the cir.unary operation.
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 49 +++
clang/include/clang/CIR/MissingFeatures.h | 9 +
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 48 +++
clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 223 ++++++++++
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 2 +
clang/lib/CIR/CodeGen/CIRGenFunction.h | 4 +
clang/lib/CIR/CodeGen/CIRGenValue.h | 1 +
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 41 ++
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 125 +++++-
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.h | 10 +
clang/test/CIR/CodeGen/unary.cpp | 392 ++++++++++++++++++
11 files changed, 903 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CIR/CodeGen/unary.cpp
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());
+ // TODO: For now, assume signed overflow is undefined. We'll need to add
+ // an attribute to the unary op to control this.
+ maybeNSW = mlir::LLVM::IntegerOverflowFlags::nsw;
+ }
+
+ switch (op.getKind()) {
+ case cir::UnaryOpKind::Inc:
+ assert(!isVector && "++ not allowed on vector types");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::AddOp>(
+ op, llvmType, adaptor.getInput(), createIntConstant(1), maybeNSW);
+ return mlir::success();
+ case cir::UnaryOpKind::Dec:
+ assert(!isVector && "-- not allowed on vector types");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::SubOp>(
+ op, llvmType, adaptor.getInput(), createIntConstant(1), maybeNSW);
+ return mlir::success();
+ case cir::UnaryOpKind::Plus:
+ rewriter.replaceOp(op, adaptor.getInput());
+ return mlir::success();
+ case cir::UnaryOpKind::Minus:
+ assert(!isVector &&
+ "Add vector handling when vector types are supported");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::SubOp>(
+ op, llvmType, createIntConstant(0), adaptor.getInput(), maybeNSW);
+ return mlir::success();
+
+ case cir::UnaryOpKind::Not:
+ // bit-wise compliment operator, implemented as an XOR with -1.
+ assert(!isVector &&
+ "Add vector handling when vector types are supported");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::XOrOp>(
+ op, llvmType, adaptor.getInput(), createIntConstant(-1));
+ return mlir::success();
+ }
+ llvm_unreachable("Unexpected unary op for int");
+ }
+
+ // Floating point unary operations: + - ++ --
+ if (mlir::isa<cir::CIRFPTypeInterface>(elementType)) {
+ switch (op.getKind()) {
+ case cir::UnaryOpKind::Inc:
+ assert(!isVector && "++ not allowed on vector types");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::FAddOp>(
+ op, llvmType, createFloatConstant(1.0), adaptor.getInput());
+ return mlir::success();
+ case cir::UnaryOpKind::Dec:
+ assert(!isVector && "-- not allowed on vector types");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::FAddOp>(
+ op, llvmType, createFloatConstant(-1.0), adaptor.getInput());
+ return mlir::success();
+ case cir::UnaryOpKind::Plus:
+ rewriter.replaceOp(op, adaptor.getInput());
+ return mlir::success();
+ case cir::UnaryOpKind::Minus:
+ rewriter.replaceOpWithNewOp<mlir::LLVM::FNegOp>(op, llvmType,
+ adaptor.getInput());
+ return mlir::success();
+ case cir::UnaryOpKind::Not:
+ return op.emitError() << "Unary not is invalid for floating-point types";
+ }
+ llvm_unreachable("Unexpected unary op for float");
+ }
+
+ // Boolean unary operations: ! only. (For all others, the operand has
+ // already been promoted to int.)
+ if (mlir::isa<cir::BoolType>(elementType)) {
+ switch (op.getKind()) {
+ case cir::UnaryOpKind::Inc:
+ case cir::UnaryOpKind::Dec:
+ case cir::UnaryOpKind::Plus:
+ case cir::UnaryOpKind::Minus:
+ // Some of these are allowed in source code, but we shouldn't get here
+ // with a boolean type.
+ return op.emitError() << "Unsupported unary operation on boolean type";
+ case cir::UnaryOpKind::Not:
+ assert(!isVector && "NYI: op! on vector mask");
+ rewriter.replaceOpWithNewOp<mlir::LLVM::XOrOp>(
+ op, llvmType, adaptor.getInput(), createIntConstant(1));
+ return mlir::success();
+ }
+ llvm_unreachable("Unexpected unary op for bool");
+ }
+
+ // Pointer unary operations: + only. (++ and -- of pointers are implemented
+ // with cir.ptr_stride, not cir.unary.)
+ if (mlir::isa<cir::PointerType>(elementType)) {
+ return op.emitError()
+ << "Unary operation on pointer types is not yet implemented";
+ }
+
+ return op.emitError() << "Unary operation has unsupported type: "
+ << elementType;
+}
+
static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
mlir::DataLayout &dataLayout) {
converter.addConversion([&](cir::PointerType type) -> mlir::Type {
@@ -707,7 +829,8 @@ void ConvertCIRToLLVMPass::runOnOperation() {
// clang-format off
CIRToLLVMBrOpLowering,
CIRToLLVMFuncOpLowering,
- CIRToLLVMTrapOpLowering
+ CIRToLLVMTrapOpLowering,
+ CIRToLLVMUnaryOpLowering
// clang-format on
>(converter, patterns.getContext());
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index d090bbe4f2e10..60518e55348e1 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -136,6 +136,16 @@ class CIRToLLVMGlobalOpLowering
cir::GlobalOp op, mlir::ConversionPatternRewriter &rewriter) const;
};
+class CIRToLLVMUnaryOpLowering
+ : public mlir::OpConversionPattern<cir::UnaryOp> {
+public:
+ using mlir::OpConversionPattern<cir::UnaryOp>::OpConversionPattern;
+
+ mlir::LogicalResult
+ matchAndRewrite(cir::UnaryOp op, OpAdaptor,
+ mlir::ConversionPatternRewriter &) const override;
+};
+
class CIRToLLVMBrOpLowering : public mlir::OpConversionPattern<cir::BrOp> {
public:
using mlir::OpConversionPattern<cir::BrOp>::OpConversionPattern;
diff --git a/clang/test/CIR/CodeGen/unary.cpp b/clang/test/CIR/CodeGen/unary.cpp
new file mode 100644
index 0000000000000..5d93587463562
--- /dev/null
+++ b/clang/test/CIR/CodeGen/unary.cpp
@@ -0,0 +1,392 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -Wno-unused-value -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -Wno-unused-value -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
+
+unsigned up0() {
+ unsigned a = 1u;
+ return +a;
+}
+
+// CHECK: cir.func @up0() -> !cir.int<u, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<u, 32>, !cir.ptr<!cir.int<u, 32>>, ["a", init]
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[OUTPUT:.*]] = cir.unary(plus, %[[INPUT]])
+
+// LLVM: define i32 @up0()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+
+// OGCG: define{{.*}} i32 @_Z3up0v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+
+unsigned um0() {
+ unsigned a = 1u;
+ return -a;
+}
+
+// CHECK: cir.func @um0() -> !cir.int<u, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<u, 32>, !cir.ptr<!cir.int<u, 32>>, ["a", init]
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[OUTPUT:.*]] = cir.unary(minus, %[[INPUT]])
+
+// LLVM: define i32 @um0()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = sub i32 0, %[[A_LOAD]]
+
+// OGCG: define{{.*}} i32 @_Z3um0v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = sub i32 0, %[[A_LOAD]]
+
+unsigned un0() {
+ unsigned a = 1u;
+ return ~a; // a ^ -1 , not
+}
+
+// CHECK: cir.func @un0() -> !cir.int<u, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<u, 32>, !cir.ptr<!cir.int<u, 32>>, ["a", init]
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[OUTPUT:.*]] = cir.unary(not, %[[INPUT]])
+
+// LLVM: define i32 @un0()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = xor i32 %[[A_LOAD]], -1
+
+// OGCG: define{{.*}} i32 @_Z3un0v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = xor i32 %[[A_LOAD]], -1
+
+int inc0() {
+ int a = 1;
+ ++a;
+ return a;
+}
+
+// CHECK: cir.func @inc0() -> !cir.int<s, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.int<1> : !cir.int<s, 32>
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.int<s, 32>
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(inc, %[[INPUT]])
+// CHECK: cir.store %[[INCREMENTED]], %[[A]]
+// CHECK: %[[A_TO_OUTPUT:.*]] = cir.load %[[A]]
+
+// LLVM: define i32 @inc0()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = add nsw i32 %[[A_LOAD]], 1
+
+// OGCG: define{{.*}} i32 @_Z4inc0v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = add nsw i32 %[[A_LOAD]], 1
+
+int dec0() {
+ int a = 1;
+ --a;
+ return a;
+}
+
+// CHECK: cir.func @dec0() -> !cir.int<s, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.int<1> : !cir.int<s, 32>
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.int<s, 32>
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(dec, %[[INPUT]])
+// CHECK: cir.store %[[INCREMENTED]], %[[A]]
+// CHECK: %[[A_TO_OUTPUT:.*]] = cir.load %[[A]]
+
+// LLVM: define i32 @dec0()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = sub nsw i32 %[[A_LOAD]], 1
+
+// OGCG: define{{.*}} i32 @_Z4dec0v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = add nsw i32 %[[A_LOAD]], -1
+
+int inc1() {
+ int a = 1;
+ a++;
+ return a;
+}
+
+// CHECK: cir.func @inc1() -> !cir.int<s, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.int<1> : !cir.int<s, 32>
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.int<s, 32>
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(inc, %[[INPUT]])
+// CHECK: cir.store %[[INCREMENTED]], %[[A]]
+// CHECK: %[[A_TO_OUTPUT:.*]] = cir.load %[[A]]
+
+// LLVM: define i32 @inc1()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = add nsw i32 %[[A_LOAD]], 1
+
+// OGCG: define{{.*}} i32 @_Z4inc1v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = add nsw i32 %[[A_LOAD]], 1
+
+int dec1() {
+ int a = 1;
+ a--;
+ return a;
+}
+
+// CHECK: cir.func @dec1() -> !cir.int<s, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.int<1> : !cir.int<s, 32>
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.int<s, 32>
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(dec, %[[INPUT]])
+// CHECK: cir.store %[[INCREMENTED]], %[[A]]
+// CHECK: %[[A_TO_OUTPUT:.*]] = cir.load %[[A]]
+
+// LLVM: define i32 @dec1()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = sub nsw i32 %[[A_LOAD]], 1
+
+// OGCG: define{{.*}} i32 @_Z4dec1v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = add nsw i32 %[[A_LOAD]], -1
+
+// Ensure the increment is performed after the assignment to b.
+int inc2() {
+ int a = 1;
+ int b = a++;
+ return b;
+}
+
+// CHECK: cir.func @inc2() -> !cir.int<s, 32>
+// CHECK: %[[A:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["a", init]
+// CHECK: %[[B:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["b", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.int<1> : !cir.int<s, 32>
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.int<s, 32>
+// CHECK: %[[ATOB:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(inc, %[[ATOB]])
+// CHECK: cir.store %[[INCREMENTED]], %[[A]]
+// CHECK: cir.store %[[ATOB]], %[[B]]
+// CHECK: %[[B_TO_OUTPUT:.*]] = cir.load %[[B]]
+
+// LLVM: define i32 @inc2()
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: %[[B:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 1, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// LLVM: %[[A_INC:.*]] = add nsw i32 %[[A_LOAD]], 1
+// LLVM: store i32 %[[A_INC]], ptr %[[A]], align 4
+// LLVM: store i32 %[[A_LOAD]], ptr %[[B]], align 4
+// LLVM: %[[B_TO_OUTPUT:.*]] = load i32, ptr %[[B]], align 4
+
+// OGCG: define{{.*}} i32 @_Z4inc2v()
+// OGCG: %[[A:.*]] = alloca i32, align 4
+// OGCG: %[[B:.*]] = alloca i32, align 4
+// OGCG: store i32 1, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load i32, ptr %[[A]], align 4
+// OGCG: %[[A_INC:.*]] = add nsw i32 %[[A_LOAD]], 1
+// OGCG: store i32 %[[A_INC]], ptr %[[A]], align 4
+// OGCG: store i32 %[[A_LOAD]], ptr %[[B]], align 4
+// OGCG: %[[B_TO_OUTPUT:.*]] = load i32, ptr %[[B]], align 4
+
+float fpPlus() {
+ float a = 1.0f;
+ return +a;
+}
+
+// CHECK: cir.func @fpPlus() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[OUTPUT:.*]] = cir.unary(plus, %[[INPUT]])
+
+// LLVM: define float @fpPlus()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+
+// OGCG: define{{.*}} float @_Z6fpPlusv()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+
+float fpMinus() {
+ float a = 1.0f;
+ return -a;
+}
+
+// CHECK: cir.func @fpMinus() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[OUTPUT:.*]] = cir.unary(minus, %[[INPUT]])
+
+// LLVM: define float @fpMinus()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = fneg float %[[A_LOAD]]
+
+// OGCG: define{{.*}} float @_Z7fpMinusv()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = fneg float %[[A_LOAD]]
+
+float fpPreInc() {
+ float a = 1.0f;
+ return ++a;
+}
+
+// CHECK: cir.func @fpPreInc() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.fp<1.000000e+00> : !cir.float
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.float
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(inc, %[[INPUT]])
+
+// LLVM: define float @fpPreInc()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = fadd float 1.000000e+00, %[[A_LOAD]]
+
+// OGCG: define{{.*}} float @_Z8fpPreIncv()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = fadd float %[[A_LOAD]], 1.000000e+00
+
+float fpPreDec() {
+ float a = 1.0f;
+ return --a;
+}
+
+// CHECK: cir.func @fpPreDec() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.fp<1.000000e+00> : !cir.float
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.float
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[DECREMENTED:.*]] = cir.unary(dec, %[[INPUT]])
+
+// LLVM: define float @fpPreDec()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = fadd float -1.000000e+00, %[[A_LOAD]]
+
+// OGCG: define{{.*}} float @_Z8fpPreDecv()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = fadd float %[[A_LOAD]], -1.000000e+00
+
+float fpPostInc() {
+ float a = 1.0f;
+ return a++;
+}
+
+// CHECK: cir.func @fpPostInc() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.fp<1.000000e+00> : !cir.float
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.float
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(inc, %[[INPUT]])
+
+// LLVM: define float @fpPostInc()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = fadd float 1.000000e+00, %[[A_LOAD]]
+
+// OGCG: define{{.*}} float @_Z9fpPostIncv()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = fadd float %[[A_LOAD]], 1.000000e+00
+
+float fpPostDec() {
+ float a = 1.0f;
+ return a--;
+}
+
+// CHECK: cir.func @fpPostDec() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.fp<1.000000e+00> : !cir.float
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.float
+// CHECK: %[[INPUT:.*]] = cir.load %[[A]]
+// CHECK: %[[DECREMENTED:.*]] = cir.unary(dec, %[[INPUT]])
+
+// LLVM: define float @fpPostDec()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// LLVM: %[[RESULT:.*]] = fadd float -1.000000e+00, %[[A_LOAD]]
+
+// OGCG: define{{.*}} float @_Z9fpPostDecv()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// OGCG: %[[RESULT:.*]] = fadd float %[[A_LOAD]], -1.000000e+00
+
+// Ensure the increment is performed after the assignment to b.
+float fpPostInc2() {
+ float a = 1.0f;
+ float b = a++;
+ return b;
+}
+
+// CHECK: cir.func @fpPostInc2() -> !cir.float
+// CHECK: %[[A:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["a", init]
+// CHECK: %[[B:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["b", init]
+// CHECK: %[[ATMP:.*]] = cir.const #cir.fp<1.000000e+00> : !cir.float
+// CHECK: cir.store %[[ATMP]], %[[A]] : !cir.float
+// CHECK: %[[ATOB:.*]] = cir.load %[[A]]
+// CHECK: %[[INCREMENTED:.*]] = cir.unary(inc, %[[ATOB]])
+// CHECK: cir.store %[[INCREMENTED]], %[[A]]
+// CHECK: cir.store %[[ATOB]], %[[B]]
+// CHECK: %[[B_TO_OUTPUT:.*]] = cir.load %[[B]]
+
+// LLVM: define float @fpPostInc2()
+// LLVM: %[[A:.*]] = alloca float, i64 1, align 4
+// LLVM: %[[B:.*]] = alloca float, i64 1, align 4
+// LLVM: store float 1.000000e+00, ptr %[[A]], align 4
+// LLVM: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// LLVM: %[[A_INC:.*]] = fadd float 1.000000e+00, %[[A_LOAD]]
+// LLVM: store float %[[A_INC]], ptr %[[A]], align 4
+// LLVM: store float %[[A_LOAD]], ptr %[[B]], align 4
+// LLVM: %[[B_TO_OUTPUT:.*]] = load float, ptr %[[B]], align 4
+
+// OGCG: define{{.*}} float @_Z10fpPostInc2v()
+// OGCG: %[[A:.*]] = alloca float, align 4
+// OGCG: %[[B:.*]] = alloca float, align 4
+// OGCG: store float 1.000000e+00, ptr %[[A]], align 4
+// OGCG: %[[A_LOAD:.*]] = load float, ptr %[[A]], align 4
+// OGCG: %[[A_INC:.*]] = fadd float %[[A_LOAD]], 1.000000e+00
+// OGCG: store float %[[A_INC]], ptr %[[A]], align 4
+// OGCG: store float %[[A_LOAD]], ptr %[[B]], align 4
+// OGCG: %[[B_TO_OUTPUT:.*]] = load float, ptr %[[B]], align 4
More information about the cfe-commits
mailing list