Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CIR] Upstream initial support for unary op #131369

Merged
merged 4 commits into from
Mar 17, 2025
Merged

Conversation

andykaylor
Copy link
Contributor

This adds support for the cir.unary operation.

@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Mar 14, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 14, 2025

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

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 --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]

@llvmbot
Copy link
Member

llvmbot commented Mar 14, 2025

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

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 --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]

let cppNamespace = "::cir";
}

// FIXME: Pure won't work when we add overloading.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean operator overloading? If so, why would we model that as unary operator? In the CFE those get modeled as function calls, so do we intended to flatten that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure what the comment meant, but I wasn't comfortable deleting it. I did some checking, and CIR also implements operator overloading as function calls right now (in the incubator), but maybe there is a plan to eventually model them using the base operations. I could see a case for the latter, because a mangled function name isn't particular helpful in reasoning about what something is doing. On the other hand, you obviously can't rely on the overloaded operator even doing something related to what it is overloading, so maybe the opaque call is better.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would love to see the answer for this from @lanza and @bcardosolopes

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still have the open question on that comment I'd like an answer to, but 1 suggestion here. Else this LGTM.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'm down to just the FIXME comment in the CIROps.td, otherwise am happy. Once we have a reasonable answer to that/know we want to remove that/etc, this is fine.

@andykaylor
Copy link
Contributor Author

The git history says the FIXME about "Pure" was added when @cmarcelo introduced UnaryOp in the incubator, but there is no PR associated with the commit so I don't know what discussions may have taken place.

@bcardosolopes
Copy link
Member

bcardosolopes commented Mar 17, 2025

The git history says the FIXME about "Pure" was added when @cmarcelo introduced UnaryOp in the incubator, but there is no PR associated with the commit so I don't know what discussions may have taken place.

We floated the idea of implementing operators as part of UnaryOp. This was a while ago - it probably doesn't make sense (given current direction) and is outdated!

@andykaylor andykaylor merged commit 5f86666 into llvm:main Mar 17, 2025
6 of 10 checks passed
andykaylor added a commit to andykaylor/clangir that referenced this pull request Mar 18, 2025
In the review for upstreaming unary operation support
(llvm/llvm-project#131369), it was suggested that
the VisitPlus and VisitMinus functions should be combined. This is a backport
of that refactoring.
andykaylor added a commit to andykaylor/clangir that referenced this pull request Mar 18, 2025
In the review for upstreaming unary operation support
(llvm/llvm-project#131369), it was suggested that
the VisitPlus and VisitMinus functions should be combined. This is a backport
of that refactoring.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants