[clang] [CIR] Add if statement support (PR #134333)

via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 3 18:44:41 PDT 2025


https://github.com/Andres-Salamanca created https://github.com/llvm/llvm-project/pull/134333

This patch adds support for if statements in the CIR dialect
Additionally, multiple RUN lines were introduced to improve codegen test coverage

>From 89f0f528f981223273b2c1548c9a71f2ceeca329 Mon Sep 17 00:00:00 2001
From: Andres Salamanca <andrealebarbaritos at gmail.com>
Date: Thu, 3 Apr 2025 12:07:25 -0500
Subject: [PATCH] [CIR] Add if statement support Upstream if statement support

Formatted code and added test cases.

added multiple RUN lines for the codegen test
---
 .../include/clang/CIR/Dialect/IR/CIRDialect.h |   4 +
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  60 ++++-
 clang/include/clang/CIR/MissingFeatures.h     |   4 +
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp          | 100 +++++++
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp    |  14 +
 clang/lib/CIR/CodeGen/CIRGenFunction.cpp      |  49 ++++
 clang/lib/CIR/CodeGen/CIRGenFunction.h        |  34 +++
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp          |  71 ++++-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 128 +++++++++
 .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp |  70 ++++-
 clang/test/CIR/CodeGen/if.cpp                 | 254 ++++++++++++++++++
 clang/test/CIR/Lowering/if.cir                |  99 +++++++
 clang/test/CIR/Transforms/if.cir              |  48 ++++
 13 files changed, 926 insertions(+), 9 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/if.cpp
 create mode 100644 clang/test/CIR/Lowering/if.cir
 create mode 100644 clang/test/CIR/Transforms/if.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
index 4d7f537418a90..4d7f0bfd1c253 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h
@@ -35,6 +35,10 @@
 using BuilderCallbackRef =
     llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>;
 
+namespace cir {
+void buildTerminatedBody(mlir::OpBuilder &builder, mlir::Location loc);
+} // namespace cir
+
 // TableGen'erated files for MLIR dialects require that a macro be defined when
 // they are included.  GET_OP_CLASSES tells the file to define the classes for
 // the operations of that dialect.
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 3965372755685..e181a5db3e1b9 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -424,8 +424,8 @@ def StoreOp : CIR_Op<"store", [
 // ReturnOp
 //===----------------------------------------------------------------------===//
 
-def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "DoWhileOp",
-                                              "WhileOp", "ForOp"]>,
+def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "IfOp",
+                                             "DoWhileOp", "WhileOp", "ForOp"]>,
                                  Terminator]> {
   let summary = "Return from function";
   let description = [{
@@ -462,6 +462,58 @@ def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "DoWhileOp",
   let hasVerifier = 1;
 }
 
+//===----------------------------------------------------------------------===//
+// IfOp
+//===----------------------------------------------------------------------===//
+
+def IfOp : CIR_Op<"if",
+     [DeclareOpInterfaceMethods<RegionBranchOpInterface>,
+     RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]>{
+
+  let summary = "the if-then-else operation";
+  let description = [{
+    The `cir.if` operation represents an if-then-else construct for
+    conditionally executing two regions of code. The operand is a `cir.bool`
+    type.
+
+    Examples:
+
+    ```mlir
+    cir.if %b  {
+      ...
+    } else {
+      ...
+    }
+
+    cir.if %c  {
+      ...
+    }
+
+    cir.if %c  {
+      ...
+      cir.br ^a
+    ^a:
+      cir.yield
+    }
+    ```
+
+    `cir.if` defines no values and the 'else' can be omitted. The if/else
+    regions must be terminated. If the region has only one block, the terminator
+    can be left out, and `cir.yield` terminator will be inserted implictly.
+    Otherwise, the region must be explicitly terminated.
+  }];
+  let arguments = (ins CIR_BoolType:$condition);
+  let regions = (region AnyRegion:$thenRegion, AnyRegion:$elseRegion);
+  let hasCustomAssemblyFormat=1;
+  let hasVerifier=1;
+  let skipDefaultBuilders=1;
+  let builders = [
+    OpBuilder<(ins "mlir::Value":$cond, "bool":$withElseRegion,
+      CArg<"BuilderCallbackRef", "buildTerminatedBody">:$thenBuilder,
+      CArg<"BuilderCallbackRef", "nullptr">:$elseBuilder)>
+  ];
+}
+
 //===----------------------------------------------------------------------===//
 // ConditionOp
 //===----------------------------------------------------------------------===//
@@ -512,8 +564,8 @@ def ConditionOp : CIR_Op<"condition", [
 //===----------------------------------------------------------------------===//
 
 def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
-                               ParentOneOf<["ScopeOp", "WhileOp", "ForOp",
-                                            "DoWhileOp"]>]> {
+                               ParentOneOf<["IfOp", "ScopeOp", "WhileOp",
+                                           "ForOp", "DoWhileOp"]>]> {
   let summary = "Represents the default branching behaviour of a region";
   let description = [{
     The `cir.yield` operation terminates regions on different CIR operations,
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 3a102d90aba8f..1d53d094fa4e7 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -81,6 +81,7 @@ struct MissingFeatures {
 
   // Clang early optimizations or things defered to LLVM lowering.
   static bool mayHaveIntegerOverflow() { return false; }
+  static bool shouldReverseUnaryCondOnBoolExpr() { return false; }
 
   // Misc
   static bool cxxABI() { return false; }
@@ -109,6 +110,9 @@ struct MissingFeatures {
   static bool cgFPOptionsRAII() { return false; }
   static bool metaDataNode() { return false; }
   static bool fastMathFlags() { return false; }
+  static bool constantFoldsToSimpleInteger() { return false; }
+  static bool incrementProfileCounter() { return false; }
+  static bool insertBuiltinUnpredictable() { return false; }
 
   // Missing types
   static bool dataMemberType() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index f01e03a89981d..a12ec878e3656 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -316,6 +316,106 @@ void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
   emitLValue(e);
 }
 
+/// Emit an `if` on a boolean condition, filling `then` and `else` into
+/// appropriated regions.
+mlir::LogicalResult CIRGenFunction::emitIfOnBoolExpr(const Expr *cond,
+                                                     const Stmt *thenS,
+                                                     const Stmt *elseS) {
+  // Attempt to be more accurate as possible with IfOp location, generate
+  // one fused location that has either 2 or 4 total locations, depending
+  // on else's availability.
+  auto getStmtLoc = [this](const Stmt &s) {
+    return mlir::FusedLoc::get(&getMLIRContext(),
+                               {getLoc(s.getSourceRange().getBegin()),
+                                getLoc(s.getSourceRange().getEnd())});
+  };
+  mlir::Location thenLoc = getStmtLoc(*thenS);
+  std::optional<mlir::Location> elseLoc;
+  if (elseS)
+    elseLoc = getStmtLoc(*elseS);
+
+  mlir::LogicalResult resThen = mlir::success(), resElse = mlir::success();
+  emitIfOnBoolExpr(
+      cond, /*thenBuilder=*/
+      [&](mlir::OpBuilder &, mlir::Location) {
+        LexicalScope lexScope{*this, thenLoc, builder.getInsertionBlock()};
+        resThen = emitStmt(thenS, /*useCurrentScope=*/true);
+      },
+      thenLoc,
+      /*elseBuilder=*/
+      [&](mlir::OpBuilder &, mlir::Location) {
+        assert(elseLoc && "Invalid location for elseS.");
+        LexicalScope lexScope{*this, *elseLoc, builder.getInsertionBlock()};
+        resElse = emitStmt(elseS, /*useCurrentScope=*/true);
+      },
+      elseLoc);
+
+  return mlir::LogicalResult::success(resThen.succeeded() &&
+                                      resElse.succeeded());
+}
+
+/// Emit an `if` on a boolean condition, filling `then` and `else` into
+/// appropriated regions.
+cir::IfOp CIRGenFunction::emitIfOnBoolExpr(
+    const clang::Expr *cond, BuilderCallbackRef thenBuilder,
+    mlir::Location thenLoc, BuilderCallbackRef elseBuilder,
+    std::optional<mlir::Location> elseLoc) {
+
+  SmallVector<mlir::Location, 2> ifLocs{thenLoc};
+  if (elseLoc)
+    ifLocs.push_back(*elseLoc);
+  mlir::Location loc = mlir::FusedLoc::get(&getMLIRContext(), ifLocs);
+
+  // Emit the code with the fully general case.
+  mlir::Value condV = emitOpOnBoolExpr(loc, cond);
+  return builder.create<cir::IfOp>(loc, condV, elseLoc.has_value(),
+                                   /*thenBuilder=*/thenBuilder,
+                                   /*elseBuilder=*/elseBuilder);
+}
+
+/// TODO(cir): PGO data
+/// TODO(cir): see EmitBranchOnBoolExpr for extra ideas).
+mlir::Value CIRGenFunction::emitOpOnBoolExpr(mlir::Location loc,
+                                             const Expr *cond) {
+  // TODO(CIR): scoped ApplyDebugLocation DL(*this, Cond);
+  // TODO(CIR): __builtin_unpredictable and profile counts?
+  cond = cond->IgnoreParens();
+
+  // if (const BinaryOperator *CondBOp = dyn_cast<BinaryOperator>(cond)) {
+  //   llvm_unreachable("binaryoperator ifstmt NYI");
+  // }
+
+  if (const UnaryOperator *CondUOp = dyn_cast<UnaryOperator>(cond)) {
+    // In LLVM the condition is reversed here for efficient codegen.
+    // This should be done in CIR prior to LLVM lowering, if we do now
+    // we can make CIR based diagnostics misleading.
+    //  cir.ternary(!x, t, f) -> cir.ternary(x, f, t)
+    assert(!cir::MissingFeatures::shouldReverseUnaryCondOnBoolExpr());
+  }
+
+  if (const ConditionalOperator *CondOp = dyn_cast<ConditionalOperator>(cond)) {
+
+    cgm.errorNYI(cond->getExprLoc(), "Ternary NYI");
+    assert(!cir::MissingFeatures::ternaryOp());
+    return createDummyValue(loc, cond->getType());
+  }
+
+  // if (const CXXThrowExpr *Throw = dyn_cast<CXXThrowExpr>(cond)) {
+  //   llvm_unreachable("NYI");
+  // }
+
+  // If the branch has a condition wrapped by __builtin_unpredictable,
+  // create metadata that specifies that the branch is unpredictable.
+  // Don't bother if not optimizing because that metadata would not be used.
+  auto *Call = dyn_cast<CallExpr>(cond->IgnoreImpCasts());
+  if (Call && cgm.getCodeGenOpts().OptimizationLevel != 0) {
+    assert(!cir::MissingFeatures::insertBuiltinUnpredictable());
+  }
+
+  // Emit the code with the fully general case.
+  return evaluateExprAsBool(cond);
+}
+
 mlir::Value CIRGenFunction::emitAlloca(StringRef name, mlir::Type ty,
                                        mlir::Location loc, CharUnits alignment,
                                        bool insertIntoFnEntryBlock,
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 2cf92dfbf3a5b..5d85bc6267e8e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -1358,6 +1358,20 @@ mlir::Value CIRGenFunction::emitScalarConversion(mlir::Value src,
       .emitScalarConversion(src, srcTy, dstTy, loc);
 }
 
+/// If the specified expression does not fold
+/// to a constant, or if it does but contains a label, return false.  If it
+/// constant folds return true and set the boolean result in Result.
+bool CIRGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond,
+                                                  bool &ResultBool,
+                                                  bool AllowLabels) {
+  llvm::APSInt ResultInt;
+  if (!ConstantFoldsToSimpleInteger(Cond, ResultInt, AllowLabels))
+    return false;
+
+  ResultBool = ResultInt.getBoolValue();
+  return true;
+}
+
 /// Return the size or alignment of the type of argument of the sizeof
 /// expression as an integer.
 mlir::Value ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr(
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index 47fc90836fca6..6510ce7985ead 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -135,6 +135,55 @@ mlir::Location CIRGenFunction::getLoc(mlir::Location lhs, mlir::Location rhs) {
   return mlir::FusedLoc::get(locs, metadata, &getMLIRContext());
 }
 
+bool CIRGenFunction::ContainsLabel(const Stmt *s, bool ignoreCaseStmts) {
+  // Null statement, not a label!
+  if (!s)
+    return false;
+
+  // If this is a label, we have to emit the code, consider something like:
+  // if (0) {  ...  foo:  bar(); }  goto foo;
+  //
+  // TODO: If anyone cared, we could track __label__'s, since we know that you
+  // can't jump to one from outside their declared region.
+  if (isa<LabelStmt>(s))
+    return true;
+
+  // If this is a case/default statement, and we haven't seen a switch, we
+  // have to emit the code.
+  if (isa<SwitchCase>(s) && !ignoreCaseStmts)
+    return true;
+
+  // If this is a switch statement, we want to ignore cases below it.
+  if (isa<SwitchStmt>(s))
+    ignoreCaseStmts = true;
+
+  // Scan subexpressions for verboten labels.
+  return std::any_of(s->child_begin(), s->child_end(),
+                     [=](const Stmt *subStmt) {
+                       return ContainsLabel(subStmt, ignoreCaseStmts);
+                     });
+}
+
+/// If the specified expression does not fold
+/// to a constant, or if it does but contains a label, return false.  If it
+/// constant folds return true and set the folded value.
+bool CIRGenFunction::ConstantFoldsToSimpleInteger(const Expr *cond,
+                                                  llvm::APSInt &resultInt,
+                                                  bool allowLabels) {
+  // FIXME: Rename and handle conversion of other evaluatable things
+  // to bool.
+  Expr::EvalResult result;
+  if (!cond->EvaluateAsInt(result, getContext()))
+    return false; // Not foldable, not integer or not fully evaluatable.
+
+  llvm::APSInt intValue = result.Val.getInt();
+  if (!allowLabels && ContainsLabel(cond))
+    return false; // Contains a label.
+
+  resultInt = intValue;
+  return true;
+}
+
 void CIRGenFunction::emitAndUpdateRetAlloca(QualType type, mlir::Location loc,
                                             CharUnits alignment) {
   if (!type->isVoidType()) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 5cae4d5da9516..15b25d8a81522 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -24,6 +24,7 @@
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/CharUnits.h"
 #include "clang/AST/Decl.h"
+#include "clang/AST/Stmt.h"
 #include "clang/AST/Type.h"
 #include "clang/CIR/Dialect/IR/CIRDialect.h"
 #include "clang/CIR/MissingFeatures.h"
@@ -164,6 +165,20 @@ class CIRGenFunction : public CIRGenTypeCache {
   /// that it requires no code to be generated.
   bool isTrivialInitializer(const Expr *init);
 
+  /// If the specified expression does not fold to a constant, or if it does but
+  /// contains a label, return false.  If it constant folds return true and set
+  /// the boolean result in Result.
+  bool ConstantFoldsToSimpleInteger(const clang::Expr *Cond, bool &ResultBool,
+                                    bool AllowLabels = false);
+  bool ConstantFoldsToSimpleInteger(const clang::Expr *Cond,
+                                    llvm::APSInt &ResultInt,
+                                    bool AllowLabels = false);
+
+  /// Return true if the statement contains a label in it.  If
+  /// this statement is not executed normally, it not containing a label means
+  /// that we can just remove the code.
+  bool ContainsLabel(const clang::Stmt *s, bool IgnoreCaseStmts = false);
+
   struct AutoVarEmission {
     const clang::VarDecl *Variable;
     /// The address of the alloca for languages with explicit address space
@@ -442,6 +457,25 @@ class CIRGenFunction : public CIRGenTypeCache {
   mlir::LogicalResult emitDeclStmt(const clang::DeclStmt &s);
   LValue emitDeclRefLValue(const clang::DeclRefExpr *e);
 
+  /// Emit an if on a boolean condition to the specified blocks.
+  /// FIXME: Based on the condition, this might try to simplify the codegen of
+  /// the conditional based on the branch. TrueCount should be the number of
+  /// times we expect the condition to evaluate to true based on PGO data. We
+  /// might decide to leave this as a separate pass (see EmitBranchOnBoolExpr
+  /// for extra ideas).
+  mlir::LogicalResult emitIfOnBoolExpr(const clang::Expr *cond,
+                                       const clang::Stmt *thenS,
+                                       const clang::Stmt *elseS);
+  cir::IfOp emitIfOnBoolExpr(const clang::Expr *cond,
+                             BuilderCallbackRef thenBuilder,
+                             mlir::Location thenLoc,
+                             BuilderCallbackRef elseBuilder,
+                             std::optional<mlir::Location> elseLoc = {});
+
+  mlir::Value emitOpOnBoolExpr(mlir::Location loc, const clang::Expr *cond);
+
+  mlir::LogicalResult emitIfStmt(const clang::IfStmt &s);
+
   /// Emit code to compute the specified expression,
   /// ignoring the result.
   void emitIgnoredExpr(const clang::Expr *e);
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index b5c1f0ae2a7ef..00a745b7196a0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -16,6 +16,7 @@
 #include "mlir/IR/Builders.h"
 #include "clang/AST/ExprCXX.h"
 #include "clang/AST/Stmt.h"
+#include "clang/CIR/MissingFeatures.h"
 
 using namespace clang;
 using namespace clang::CIRGen;
@@ -72,7 +73,8 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
       assert(outgoing && "expression emission cleared block!");
       return mlir::success();
     }
-
+  case Stmt::IfStmtClass:
+    return emitIfStmt(cast<IfStmt>(*s));
   case Stmt::ForStmtClass:
     return emitForStmt(cast<ForStmt>(*s));
   case Stmt::WhileStmtClass:
@@ -99,7 +101,6 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
   case Stmt::CaseStmtClass:
   case Stmt::SEHLeaveStmtClass:
   case Stmt::SYCLKernelCallStmtClass:
-  case Stmt::IfStmtClass:
   case Stmt::SwitchStmtClass:
   case Stmt::CoroutineBodyStmtClass:
   case Stmt::CoreturnStmtClass:
@@ -263,6 +264,72 @@ static void terminateBody(CIRGenBuilderTy &builder, mlir::Region &r,
     b->erase();
 }
 
+mlir::LogicalResult CIRGenFunction::emitIfStmt(const IfStmt &s) {
+  mlir::LogicalResult res = mlir::success();
+  // The else branch of a consteval if statement is always the only branch
+  // that can be runtime evaluated.
+  const Stmt *ConstevalExecuted;
+  if (s.isConsteval()) {
+    ConstevalExecuted = s.isNegatedConsteval() ? s.getThen() : s.getElse();
+    if (!ConstevalExecuted) {
+      // No runtime code execution required
+      return res;
+    }
+  }
+
+  // C99 6.8.4.1: The first substatement is executed if the expression
+  // compares unequal to 0.  The condition must be a scalar type.
+  auto ifStmtBuilder = [&]() -> mlir::LogicalResult {
+    if (s.isConsteval())
+      return emitStmt(ConstevalExecuted, /*useCurrentScope=*/true);
+
+    if (s.getInit())
+      if (emitStmt(s.getInit(), /*useCurrentScope=*/true).failed())
+        return mlir::failure();
+
+    if (s.getConditionVariable())
+      emitDecl(*s.getConditionVariable());
+
+    // During LLVM codegen, if the condition constant folds and can be elided,
+    // it tries to avoid emitting the condition and the dead arm of the if/else.
+    // TODO(cir): we skip this in CIRGen, but should implement this as part of
+    // SSCP or a specific CIR pass.
+    bool CondConstant;
+    if (ConstantFoldsToSimpleInteger(s.getCond(), CondConstant,
+                                     s.isConstexpr())) {
+      if (s.isConstexpr()) {
+        // Handle "if constexpr" explicitly here to avoid generating some
+        // ill-formed code since in CIR the "if" is no longer simplified
+        // in this lambda like in Clang but postponed to other MLIR
+        // passes.
+        if (const Stmt *Executed = CondConstant ? s.getThen() : s.getElse())
+          return emitStmt(Executed, /*useCurrentScope=*/true);
+        // There is nothing to execute at runtime.
+        // TODO(cir): there is still an empty cir.scope generated by the caller.
+        return mlir::success();
+      }
+      assert(!cir::MissingFeatures::constantFoldsToSimpleInteger());
+    }
+
+    assert(!cir::MissingFeatures::emitCondLikelihoodViaExpectIntrinsic());
+    assert(!cir::MissingFeatures::incrementProfileCounter());
+    return emitIfOnBoolExpr(s.getCond(), s.getThen(), s.getElse());
+  };
+
+  // TODO: Add a new scoped symbol table.
+  // LexicalScope ConditionScope(*this, S.getCond()->getSourceRange());
+  // The if scope contains the full source range for IfStmt.
+  mlir::Location scopeLoc = getLoc(s.getSourceRange());
+  builder.create<cir::ScopeOp>(
+      scopeLoc, /*scopeBuilder=*/
+      [&](mlir::OpBuilder &b, mlir::Location loc) {
+        LexicalScope lexScope{*this, scopeLoc, builder.getInsertionBlock()};
+        res = ifStmtBuilder();
+      });
+
+  return res;
+}
+
 mlir::LogicalResult CIRGenFunction::emitDeclStmt(const DeclStmt &s) {
   assert(builder.getInsertionBlock() && "expected valid insertion point");
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 4ace083e3c081..7877f2601a245 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -14,6 +14,7 @@
 
 #include "clang/CIR/Dialect/IR/CIRTypes.h"
 
+#include "mlir/Interfaces/ControlFlowInterfaces.h"
 #include "mlir/Interfaces/FunctionImplementation.h"
 #include "mlir/Support/LogicalResult.h"
 
@@ -447,6 +448,133 @@ mlir::LogicalResult cir::ReturnOp::verify() {
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// IfOp
+//===----------------------------------------------------------------------===//
+
+ParseResult cir::IfOp::parse(OpAsmParser &parser, OperationState &result) {
+  // create the regions for 'then'.
+  result.regions.reserve(2);
+  Region *thenRegion = result.addRegion();
+  Region *elseRegion = result.addRegion();
+
+  mlir::Builder &builder = parser.getBuilder();
+  OpAsmParser::UnresolvedOperand cond;
+  Type boolType = cir::BoolType::get(builder.getContext());
+
+  if (parser.parseOperand(cond) ||
+      parser.resolveOperand(cond, boolType, result.operands))
+    return failure();
+
+  // Parse 'then' region.
+  mlir::SMLoc parseThenLoc = parser.getCurrentLocation();
+  if (parser.parseRegion(*thenRegion, /*arguments=*/{}, /*argTypes=*/{}))
+    return failure();
+
+  if (ensureRegionTerm(parser, *thenRegion, parseThenLoc).failed())
+    return failure();
+
+  // If we find an 'else' keyword, parse the 'else' region.
+  if (!parser.parseOptionalKeyword("else")) {
+    mlir::SMLoc parseElseLoc = parser.getCurrentLocation();
+    if (parser.parseRegion(*elseRegion, /*arguments=*/{}, /*argTypes=*/{}))
+      return failure();
+    if (ensureRegionTerm(parser, *elseRegion, parseElseLoc).failed())
+      return failure();
+  }
+
+  // Parse the optional attribute list.
+  if (parser.parseOptionalAttrDict(result.attributes))
+    return failure();
+  return success();
+}
+
+void cir::IfOp::print(OpAsmPrinter &p) {
+
+  p << " " << getCondition() << " ";
+  mlir::Region &thenRegion = this->getThenRegion();
+  p.printRegion(thenRegion,
+                /*printEntryBlockArgs=*/false,
+                /*printBlockTerminators=*/!omitRegionTerm(thenRegion));
+
+  // Print the 'else' regions if it exists and has a block.
+  mlir::Region &elseRegion = this->getElseRegion();
+  if (!elseRegion.empty()) {
+    p << " else ";
+    p.printRegion(elseRegion,
+                  /*printEntryBlockArgs=*/false,
+                  /*printBlockTerminators=*/!omitRegionTerm(elseRegion));
+  }
+
+  p.printOptionalAttrDict(getOperation()->getAttrs());
+}
+
+/// Default callback for IfOp builders.
+void cir::buildTerminatedBody(OpBuilder &builder, Location loc) {
+  // add cir.yield to end of the block
+  builder.create<cir::YieldOp>(loc);
+}
+
+/// Given the region at `index`, or the parent operation if `index` is None,
+/// return the successor regions. These are the regions that may be selected
+/// during the flow of control. `operands` is a set of optional attributes that
+/// correspond to a constant value for each operand, or null if that operand is
+/// not a constant.
+void cir::IfOp::getSuccessorRegions(mlir::RegionBranchPoint point,
+                                    SmallVectorImpl<RegionSuccessor> &regions) {
+  // The `then` and the `else` region branch back to the parent operation.
+  if (!point.isParent()) {
+    regions.push_back(RegionSuccessor());
+    return;
+  }
+
+  // Don't consider the else region if it is empty.
+  Region *elseRegion = &this->getElseRegion();
+  if (elseRegion->empty())
+    elseRegion = nullptr;
+
+  // Otherwise, the successor is dependent on the condition.
+  // bool condition;
+  // if (auto condAttr = operands.front().dyn_cast_or_null<IntegerAttr>()) {
+  //   assert(0 && "not implemented");
+  // condition = condAttr.getValue().isOneValue();
+  // Add the successor regions using the condition.
+  // regions.push_back(RegionSuccessor(condition ? &thenRegion() :
+  // elseRegion));
+  // return;
+  // }
+
+  // If the condition isn't constant, both regions may be executed.
+  regions.push_back(RegionSuccessor(&getThenRegion()));
+  // If the else region does not exist, it is not a viable successor.
+  if (elseRegion)
+    regions.push_back(RegionSuccessor(elseRegion));
+
+  return;
+}
+
+void cir::IfOp::build(OpBuilder &builder, OperationState &result, Value cond,
+                      bool withElseRegion, BuilderCallbackRef thenBuilder,
+                      BuilderCallbackRef elseBuilder) {
+  assert(thenBuilder && "the builder callback for 'then' must be present");
+  result.addOperands(cond);
+
+  OpBuilder::InsertionGuard guard(builder);
+  Region *thenRegion = result.addRegion();
+  builder.createBlock(thenRegion);
+  thenBuilder(builder, result.location);
+
+  Region *elseRegion = result.addRegion();
+  if (!withElseRegion) {
+    return;
+  }
+
+  builder.createBlock(elseRegion);
+  elseBuilder(builder, result.location);
+}
+
+LogicalResult cir::IfOp::verify() { return success(); }
+
 //===----------------------------------------------------------------------===//
 // ScopeOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
index 52f4b2241505d..ea2b46a6a67f9 100644
--- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp
@@ -13,6 +13,8 @@
 
 #include "PassDetail.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
+#include "mlir/IR/Block.h"
+#include "mlir/IR/Builders.h"
 #include "mlir/IR/PatternMatch.h"
 #include "mlir/Support/LogicalResult.h"
 #include "mlir/Transforms/DialectConversion.h"
@@ -54,6 +56,67 @@ struct CIRFlattenCFGPass : public CIRFlattenCFGBase<CIRFlattenCFGPass> {
   void runOnOperation() override;
 };
 
+struct CIRIfFlattening : public mlir::OpRewritePattern<cir::IfOp> {
+  using OpRewritePattern<IfOp>::OpRewritePattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::IfOp ifOp,
+                  mlir::PatternRewriter &rewriter) const override {
+    mlir::OpBuilder::InsertionGuard guard(rewriter);
+    mlir::Location loc = ifOp.getLoc();
+    bool emptyElse = ifOp.getElseRegion().empty();
+    mlir::Block *currentBlock = rewriter.getInsertionBlock();
+    mlir::Block *remainingOpsBlock =
+        rewriter.splitBlock(currentBlock, rewriter.getInsertionPoint());
+    mlir::Block *continueBlock;
+    if (ifOp->getResults().empty())
+      continueBlock = remainingOpsBlock;
+    else
+      llvm_unreachable("NYI");
+
+    // Inline the region
+    mlir::Block *thenBeforeBody = &ifOp.getThenRegion().front();
+    mlir::Block *thenAfterBody = &ifOp.getThenRegion().back();
+    rewriter.inlineRegionBefore(ifOp.getThenRegion(), continueBlock);
+
+    rewriter.setInsertionPointToEnd(thenAfterBody);
+    if (auto thenYieldOp =
+            dyn_cast<cir::YieldOp>(thenAfterBody->getTerminator())) {
+      rewriter.replaceOpWithNewOp<cir::BrOp>(thenYieldOp, thenYieldOp.getArgs(),
+                                             continueBlock);
+    }
+
+    rewriter.setInsertionPointToEnd(continueBlock);
+
+    // Has else region: inline it.
+    mlir::Block *elseBeforeBody = nullptr;
+    mlir::Block *elseAfterBody = nullptr;
+    if (!emptyElse) {
+      elseBeforeBody = &ifOp.getElseRegion().front();
+      elseAfterBody = &ifOp.getElseRegion().back();
+      rewriter.inlineRegionBefore(ifOp.getElseRegion(), continueBlock);
+    } else {
+      elseBeforeBody = elseAfterBody = continueBlock;
+    }
+
+    rewriter.setInsertionPointToEnd(currentBlock);
+    rewriter.create<cir::BrCondOp>(loc, ifOp.getCondition(), thenBeforeBody,
+                                   elseBeforeBody);
+
+    if (!emptyElse) {
+      rewriter.setInsertionPointToEnd(elseAfterBody);
+      if (auto elseYieldOP =
+              dyn_cast<cir::YieldOp>(elseAfterBody->getTerminator())) {
+        rewriter.replaceOpWithNewOp<cir::BrOp>(
+            elseYieldOP, elseYieldOP.getArgs(), continueBlock);
+      }
+    }
+
+    rewriter.replaceOp(ifOp, continueBlock->getArguments());
+    return mlir::success();
+  }
+};
+
 class CIRScopeOpFlattening : public mlir::OpRewritePattern<cir::ScopeOp> {
 public:
   using OpRewritePattern<cir::ScopeOp>::OpRewritePattern;
@@ -191,8 +254,9 @@ class CIRLoopOpInterfaceFlattening
 };
 
 void populateFlattenCFGPatterns(RewritePatternSet &patterns) {
-  patterns.add<CIRLoopOpInterfaceFlattening, CIRScopeOpFlattening>(
-      patterns.getContext());
+  patterns
+      .add<CIRIfFlattening, CIRLoopOpInterfaceFlattening, CIRScopeOpFlattening>(
+          patterns.getContext());
 }
 
 void CIRFlattenCFGPass::runOnOperation() {
@@ -206,7 +270,7 @@ void CIRFlattenCFGPass::runOnOperation() {
     assert(!cir::MissingFeatures::switchOp());
     assert(!cir::MissingFeatures::ternaryOp());
     assert(!cir::MissingFeatures::tryOp());
-    if (isa<ScopeOp, LoopOpInterface>(op))
+    if (isa<IfOp, ScopeOp, LoopOpInterface>(op))
       ops.push_back(op);
   });
 
diff --git a/clang/test/CIR/CodeGen/if.cpp b/clang/test/CIR/CodeGen/if.cpp
new file mode 100644
index 0000000000000..d1be063bb529b
--- /dev/null
+++ b/clang/test/CIR/CodeGen/if.cpp
@@ -0,0 +1,254 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG
+
+int if0(bool a) {
+
+  if (a)
+    return 2;
+
+  return 3;
+
+}
+
+// CIR: cir.func @if0(%arg0: !cir.bool loc({{.*}})) -> !s32i
+// CIR: cir.scope {
+// CIR:   %4 = cir.load %0 : !cir.ptr<!cir.bool>, !cir.bool
+// CIR-NEXT: cir.if %4 {
+// CIR-NEXT:   %5 = cir.const #cir.int<2> : !s32i
+// CIR-NEXT:   cir.store %5, %1 : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT:   %6 = cir.load %1 : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT:   cir.return %6 : !s32i
+// CIR-NEXT:   }
+// CIR-NEXT:  }
+
+
+// LLVM: define i32 @if0(i1 %0)
+// LLVM:   br label %[[ENTRY:.*]]
+// LLVM: [[ENTRY]]:
+// LLVM:   %6 = load i8, ptr %2, align 1
+// LLVM:   %7 = trunc i8 %6 to i1
+// LLVM:   br i1 %7, label %[[THEN:.*]], label %[[END:.*]]
+// LLVM: [[THEN]]:
+// LLVM:   store i32 2, ptr %3, align 4
+// LLVM:   %9 = load i32, ptr %3, align 4
+// LLVM:   ret i32 %9
+// LLVM: [[END]]:
+// LLVM:   br label %[[LABEL4:.*]]
+// LLVM: [[LABEL4]]:
+// LLVM:   store i32 3, ptr %3, align 4
+// LLVM:   %12 = load i32, ptr %3, align 4
+// LLVM:   ret i32 %12
+
+// OGCG: define dso_local noundef i32 @_Z3if0b(i1 noundef zeroext %a)
+// OGCG: entry:
+// OGCG:   %[[RETVAL:.*]] = alloca i32, align 4
+// OGCG:   %[[A_ADDR:.*]] = alloca i8, align 1
+// OGCG:   %[[STOREDV:.*]] = zext i1 %a to i8
+// OGCG:   store i8 %[[STOREDV]], ptr %[[A_ADDR]], align 1
+// OGCG:   %[[LOADTMP:.*]] = load i8, ptr %[[A_ADDR]], align 1
+// OGCG:   %[[LOADEDV:.*]] = trunc i8 %[[LOADTMP]] to i1
+// OGCG:   br i1 %[[LOADEDV]], label %[[THEN_LABEL:.*]], label %[[END_LABEL:.*]]
+// OGCG: [[THEN_LABEL]]:
+// OGCG:   store i32 2, ptr %[[RETVAL]], align 4
+// OGCG:   br label %[[RETURN_LABEL:.*]]
+// OGCG: [[END_LABEL]]:
+// OGCG:   store i32 3, ptr %[[RETVAL]], align 4
+// OGCG:   br label %[[RETURN_LABEL]]
+// OGCG: [[RETURN_LABEL]]:
+// OGCG:   %[[FINALLOAD:.*]] = load i32, ptr %[[RETVAL]], align 4
+// OGCG:   ret i32 %[[FINALLOAD]]
+
+void if1(int a) {
+  int x = 0;
+  if (a) {
+    x = 3;
+  } else {
+    x = 4;
+  }
+}
+
+// CIR: cir.func @if1(%arg0: !s32i loc({{.*}}))
+// CIR: cir.scope {
+// CIR:   %3 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+// CIR:   %4 = cir.cast(int_to_bool, %3 : !s32i), !cir.bool
+// CIR-NEXT:   cir.if %4 {
+// CIR-NEXT:     %5 = cir.const #cir.int<3> : !s32i
+// CIR-NEXT:     cir.store %5, %1 : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT:   } else {
+// CIR-NEXT:     %5 = cir.const #cir.int<4> : !s32i
+// CIR-NEXT:     cir.store %5, %1 : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT:   }
+// CIR: }
+
+// LLVM: define void @if1(i32 %0)
+// LLVM: %[[A:.*]] = alloca i32, i64 1, align 4
+// LLVM: %[[X:.*]] = alloca i32, i64 1, align 4
+// LLVM: store i32 %0, ptr %[[A]], align 4
+// LLVM: store i32 0, ptr %[[X]], align 4
+// LLVM: br label %[[ENTRY:.*]]
+// LLVM: [[ENTRY]]:
+// LLVM:   %[[LOADED:.*]] = load i32, ptr %[[A]], align 4
+// LLVM:   %[[COND:.*]] = icmp ne i32 %[[LOADED]], 0
+// LLVM:   br i1 %[[COND]], label %[[THEN:.*]], label %[[ELSE:.*]]
+// LLVM: [[THEN]]:
+// LLVM:   store i32 3, ptr %[[X]], align 4
+// LLVM:   br label %[[END:.*]]
+// LLVM: [[ELSE]]:
+// LLVM:   store i32 4, ptr %[[X]], align 4
+// LLVM:   br label %[[END]]
+// LLVM: [[END]]:
+// LLVM:   br label %[[EXIT:.*]]
+// LLVM: [[EXIT]]:
+// LLVM:   ret void
+
+// OGCG: define dso_local void @_Z3if1i(i32 noundef %[[A:.*]])
+// OGCG: entry:
+// OGCG:   %[[A_ADDR:.*]] = alloca i32, align 4
+// OGCG:   %[[X:.*]] = alloca i32, align 4
+// OGCG:   store i32 %[[A]], ptr %[[A_ADDR]], align 4
+// OGCG:   store i32 0, ptr %[[X]], align 4
+// OGCG:   %[[LOADED_A:.*]] = load i32, ptr %[[A_ADDR]], align 4
+// OGCG:   %[[TOBOOL:.*]] = icmp ne i32 %[[LOADED_A]], 0
+// OGCG:   br i1 %[[TOBOOL]], label %[[THEN_LABEL:.*]], label %[[ELSE_LABEL:.*]]
+// OGCG: [[THEN_LABEL]]:
+// OGCG:   store i32 3, ptr %[[X]], align 4
+// OGCG:   br label %[[END_LABEL:.*]]
+// OGCG: [[ELSE_LABEL]]:
+// OGCG:   store i32 4, ptr %[[X]], align 4
+// OGCG:   br label %[[END_LABEL]]
+// OGCG: [[END_LABEL]]:
+// OGCG:   ret void
+
+void if2(int a, bool b, bool c) {
+  int x = 0;
+  if (a) {
+    x = 3;
+    if (b) {
+      x = 8;
+    }
+  } else {
+    if (c) {
+      x = 14;
+    }
+    x = 4;
+  }
+}
+
+// CIR: cir.func @if2(%arg0: !s32i loc({{.*}}), %arg1: !cir.bool loc({{.*}}), %arg2: !cir.bool loc({{.*}}))
+// CIR: cir.scope {
+// CIR:   %5 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+// CIR:   %6 = cir.cast(int_to_bool, %5 : !s32i), !cir.bool
+// CIR:   cir.if %6 {
+// CIR:     %7 = cir.const #cir.int<3> : !s32i
+// CIR:     cir.store %7, %3 : !s32i, !cir.ptr<!s32i>
+// CIR:     cir.scope {
+// CIR:       %8 = cir.load %1 : !cir.ptr<!cir.bool>, !cir.bool
+// CIR-NEXT:       cir.if %8 {
+// CIR-NEXT:         %9 = cir.const #cir.int<8> : !s32i
+// CIR-NEXT:         cir.store %9, %3 : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT:       }
+// CIR:     }
+// CIR:   } else {
+// CIR:     cir.scope {
+// CIR:       %8 = cir.load %2 : !cir.ptr<!cir.bool>, !cir.bool
+// CIR-NEXT:       cir.if %8 {
+// CIR-NEXT:         %9 = cir.const #cir.int<14> : !s32i
+// CIR-NEXT:         cir.store %9, %3 : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT:       }
+// CIR:     }
+// CIR:     %7 = cir.const #cir.int<4> : !s32i
+// CIR:     cir.store %7, %3 : !s32i, !cir.ptr<!s32i>
+// CIR:   }
+// CIR: }
+
+// LLVM: define void @if2(i32 %[[A:.*]], i1 %[[B:.*]], i1 %[[C:.*]])
+// LLVM:   %[[VARA:.*]] = alloca i32, i64 1, align 4
+// LLVM:   %[[VARB:.*]] = alloca i8, i64 1, align 1
+// LLVM:   %[[VARC:.*]] = alloca i8, i64 1, align 1
+// LLVM:   %[[VARX:.*]] = alloca i32, i64 1, align 4
+// LLVM:   store i32 %[[A]], ptr %[[VARA]], align 4
+// LLVM:   %[[B_EXT:.*]] = zext i1 %[[B]] to i8
+// LLVM:   store i8 %[[B_EXT]], ptr %[[VARB]], align 1
+// LLVM:   %[[C_EXT:.*]] = zext i1 %[[C]] to i8
+// LLVM:   store i8 %[[C_EXT]], ptr %[[VARC]], align 1
+// LLVM:   store i32 0, ptr %[[VARX]], align 4
+// LLVM:   br label %[[ENTRY:.*]]
+// LLVM: [[ENTRY]]:
+// LLVM:   %[[LOAD_A:.*]] = load i32, ptr %[[VARA]], align 4
+// LLVM:   %[[CMP_A:.*]] = icmp ne i32 %[[LOAD_A]], 0
+// LLVM:   br i1 %[[CMP_A]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+// LLVM: [[IF_THEN]]:
+// LLVM:   store i32 3, ptr %[[VARX]], align 4
+// LLVM:   br label %[[LABEL14:.*]]
+// LLVM: [[LABEL14]]:
+// LLVM:   %[[LOAD_B:.*]] = load i8, ptr %[[VARB]], align 1
+// LLVM:   %[[TRUNC_B:.*]] = trunc i8 %[[LOAD_B]] to i1
+// LLVM:   br i1 %[[TRUNC_B]], label %[[IF_THEN2:.*]], label %[[IF_END2:.*]]
+// LLVM: [[IF_THEN2]]:
+// LLVM:   store i32 8, ptr %[[VARX]], align 4
+// LLVM:   br label %[[IF_END2]]
+// LLVM: [[IF_END2]]:
+// LLVM:   br label %[[LABEL19:.*]]
+// LLVM: [[LABEL19]]:
+// LLVM:   br label %[[LABEL27:.*]]
+// LLVM: [[IF_ELSE]]:
+// LLVM:   br label %[[LABEL21:.*]]
+// LLVM: [[LABEL21]]:
+// LLVM:   %[[LOAD_C:.*]] = load i8, ptr %[[VARC]], align 1
+// LLVM:   %[[TRUNC_C:.*]] = trunc i8 %[[LOAD_C]] to i1
+// LLVM:   br i1 %[[TRUNC_C]], label %[[IF_THEN3:.*]], label %[[IF_END3:.*]]
+// LLVM: [[IF_THEN3]]:
+// LLVM:   store i32 14, ptr %[[VARX]], align 4
+// LLVM:   br label %[[IF_END3]]
+// LLVM: [[IF_END3]]:
+// LLVM:   br label %[[LABEL26:.*]]
+// LLVM: [[LABEL26]]:
+// LLVM:   store i32 4, ptr %[[VARX]], align 4
+// LLVM:   br label %[[LABEL27]]
+// LLVM: [[LABEL27]]:
+// LLVM:   br label %[[LABEL28:.*]]
+// LLVM: [[LABEL28]]:
+// LLVM:   ret void
+
+// OGCG: define dso_local void @_Z3if2ibb(i32 noundef %[[A:.*]], i1 noundef zeroext %[[B:.*]], i1 noundef zeroext %[[C:.*]])
+// OGCG: entry:
+// OGCG:   %[[A_ADDR:.*]] = alloca i32, align 4
+// OGCG:   %[[B_ADDR:.*]] = alloca i8, align 1
+// OGCG:   %[[C_ADDR:.*]] = alloca i8, align 1
+// OGCG:   %[[X:.*]] = alloca i32, align 4
+// OGCG:   store i32 %[[A]], ptr %[[A_ADDR]], align 4
+// OGCG:   %[[B_EXT:.*]] = zext i1 %[[B]] to i8
+// OGCG:   store i8 %[[B_EXT]], ptr %[[B_ADDR]], align 1
+// OGCG:   %[[C_EXT:.*]] = zext i1 %[[C]] to i8
+// OGCG:   store i8 %[[C_EXT]], ptr %[[C_ADDR]], align 1
+// OGCG:   store i32 0, ptr %[[X]], align 4
+// OGCG:   %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]], align 4
+// OGCG:   %[[A_BOOL:.*]] = icmp ne i32 %[[A_VAL]], 0
+// OGCG:   br i1 %[[A_BOOL]], label %[[IF_THEN:.*]], label %[[IF_ELSE:.*]]
+// OGCG: [[IF_THEN]]:
+// OGCG:   store i32 3, ptr %[[X]], align 4
+// OGCG:   %[[B_LOAD:.*]] = load i8, ptr %[[B_ADDR]], align 1
+// OGCG:   %[[B_TRUNC:.*]] = trunc i8 %[[B_LOAD]] to i1
+// OGCG:   br i1 %[[B_TRUNC]], label %[[IF_THEN2:.*]], label %[[IF_END:.*]]
+// OGCG: [[IF_THEN2]]:
+// OGCG:   store i32 8, ptr %[[X]], align 4
+// OGCG:   br label %[[IF_END]]
+// OGCG: [[IF_END]]:
+// OGCG:   br label %[[IF_END6:.*]]
+// OGCG: [[IF_ELSE]]:
+// OGCG:   %[[C_LOAD:.*]] = load i8, ptr %[[C_ADDR]], align 1
+// OGCG:   %[[C_TRUNC:.*]] = trunc i8 %[[C_LOAD]] to i1
+// OGCG:   br i1 %[[C_TRUNC]], label %[[IF_THEN4:.*]], label %[[IF_END5:.*]]
+// OGCG: [[IF_THEN4]]:
+// OGCG:   store i32 14, ptr %[[X]], align 4
+// OGCG:   br label %[[IF_END5]]
+// OGCG: [[IF_END5]]:
+// OGCG:   store i32 4, ptr %[[X]], align 4
+// OGCG:   br label %[[IF_END6]]
+// OGCG: [[IF_END6]]:
+// OGCG:   ret void
+
diff --git a/clang/test/CIR/Lowering/if.cir b/clang/test/CIR/Lowering/if.cir
new file mode 100644
index 0000000000000..3a077aa9ef057
--- /dev/null
+++ b/clang/test/CIR/Lowering/if.cir
@@ -0,0 +1,99 @@
+// RUN: cir-opt %s -cir-to-llvm -o - | FileCheck %s -check-prefix=MLIR
+// RUN: cir-translate %s -cir-to-llvmir --target x86_64-unknown-linux-gnu --disable-cc-lowering  | FileCheck %s -check-prefix=LLVM
+!s32i = !cir.int<s, 32>
+
+module {
+  cir.func @foo(%arg0: !s32i) -> !s32i {
+    %4 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+    cir.if %4 {
+      %5 = cir.const #cir.int<1> : !s32i
+      cir.return %5 : !s32i
+    } else {
+      %5 = cir.const #cir.int<0> : !s32i
+      cir.return %5 : !s32i
+    }
+    cir.return %arg0 : !s32i
+  }
+
+//      MLIR:   llvm.func @foo(%arg0: i32) -> i32
+// MLIR-NEXT:     %0 = llvm.mlir.constant(0 : i32) : i32
+// MLIR-NEXT:     %1 = llvm.icmp "ne" %arg0, %0 : i32
+// MLIR-NEXT:     llvm.cond_br %1, ^bb1, ^bb2
+// MLIR-NEXT:   ^bb1:  // pred: ^bb0
+// MLIR-NEXT:     %2 = llvm.mlir.constant(1 : i32) : i32
+// MLIR-NEXT:     llvm.return %2 : i32
+// MLIR-NEXT:   ^bb2:  // pred: ^bb0
+// MLIR-NEXT:     %3 = llvm.mlir.constant(0 : i32) : i32
+// MLIR-NEXT:     llvm.return %3 : i32
+// MLIR-NEXT:   ^bb3:  // no predecessors
+// MLIR-NEXT:     llvm.return %arg0 : i32
+// MLIR-NEXT:   }
+
+//       LLVM: define i32 @foo(i32 %0)
+//  LLVM-NEXT:   %2 = icmp ne i32 %0, 0
+//  LLVM-NEXT:   br i1 %2, label %3, label %4
+// LLVM-EMPTY:
+//  LLVM-NEXT: 3:
+//  LLVM-NEXT:   ret i32 1
+// LLVM-EMPTY:
+//  LLVM-NEXT: 4:
+//  LLVM-NEXT:   ret i32 0
+// LLVM-EMPTY:
+//  LLVM-NEXT: 5:
+//  LLVM-NEXT:   ret i32 %0
+//  LLVM-NEXT: }
+
+  cir.func @onlyIf(%arg0: !s32i) -> !s32i {
+    %4 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+    cir.if %4 {
+      %5 = cir.const #cir.int<1> : !s32i
+      cir.return %5 : !s32i
+    }
+    cir.return %arg0 : !s32i
+  }
+
+  //      MLIR: llvm.func @onlyIf(%arg0: i32) -> i32
+  // MLIR-NEXT:   %0 = llvm.mlir.constant(0 : i32) : i32
+  // MLIR-NEXT:   %1 = llvm.icmp "ne" %arg0, %0 : i32
+  // MLIR-NEXT:   llvm.cond_br %1, ^bb1, ^bb2
+  // MLIR-NEXT: ^bb1:  // pred: ^bb0
+  // MLIR-NEXT:   %2 = llvm.mlir.constant(1 : i32) : i32
+  // MLIR-NEXT:   llvm.return %2 : i32
+  // MLIR-NEXT: ^bb2:  // pred: ^bb0
+  // MLIR-NEXT:   llvm.return %arg0 : i32
+  // MLIR-NEXT: }
+
+  // Verify empty if clause is properly lowered to empty block
+  cir.func @emptyIfClause(%arg0: !s32i) -> !s32i {
+    // MLIR-LABEL: llvm.func @emptyIfClause
+    %4 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+    // MLIR: llvm.cond_br {{%.*}}, ^[[T:.*]], ^[[PHI:.*]]
+    cir.if %4 {
+      // MLIR-NEXT: ^[[T]]:
+      // MLIR-NEXT:   llvm.br ^[[PHI]]
+    }
+    // MLIR-NEXT: ^[[PHI]]:
+    // MLIR-NEXT:   llvm.return
+    cir.return %arg0 : !s32i
+  }
+
+  // Verify empty if-else clauses are properly lowered to empty blocks
+  // TODO: Fix reversed order of blocks in the test once Issue clangir/#1094 is
+  // addressed
+  cir.func @emptyIfElseClause(%arg0: !s32i) -> !s32i {
+    // MLIR-LABEL: llvm.func @emptyIfElseClause
+    %4 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+    // MLIR: llvm.cond_br {{%.*}}, ^[[T:.*]], ^[[F:.*]]
+    cir.if %4 {
+    // MLIR-NEXT: ^[[T]]:
+    // MLIR-NEXT:   llvm.br ^[[PHI:.*]]
+    } else {
+    // MLIR-NEXT: ^[[F]]:
+    // MLIR-NEXT:   llvm.br ^[[PHI]]
+    }
+    // MLIR-NEXT: ^[[PHI]]:
+    // MLIR-NEXT:   llvm.return
+    cir.return %arg0 : !s32i
+  }
+
+}
diff --git a/clang/test/CIR/Transforms/if.cir b/clang/test/CIR/Transforms/if.cir
new file mode 100644
index 0000000000000..03848bf8d0633
--- /dev/null
+++ b/clang/test/CIR/Transforms/if.cir
@@ -0,0 +1,48 @@
+// RUN: cir-opt %s -cir-flatten-cfg -o - | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+module {
+  cir.func @foo(%arg0: !s32i) -> !s32i {
+    %4 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+    cir.if %4 {
+      %5 = cir.const #cir.int<1> : !s32i
+      cir.return %5 : !s32i
+    } else {
+      %5 = cir.const #cir.int<0> : !s32i
+      cir.return %5 : !s32i
+    }
+    cir.return %arg0 : !s32i
+  }
+//      CHECK: cir.func @foo(%arg0: !s32i) -> !s32i {
+// CHECK-NEXT:   %0 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+// CHECK-NEXT:   cir.brcond %0 ^bb1, ^bb2
+// CHECK-NEXT: ^bb1:  // pred: ^bb0
+// CHECK-NEXT:   %1 = cir.const #cir.int<1> : !s32i
+// CHECK-NEXT:   cir.return %1 : !s32i
+// CHECK-NEXT: ^bb2:  // pred: ^bb0
+// CHECK-NEXT:   %2 = cir.const #cir.int<0> : !s32i
+// CHECK-NEXT:   cir.return %2 : !s32i
+// CHECK-NEXT: ^bb3:  // no predecessors
+// CHECK-NEXT:   cir.return %arg0 : !s32i
+// CHECK-NEXT: }
+
+  cir.func @onlyIf(%arg0: !s32i) -> !s32i {
+    %4 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+    cir.if %4 {
+      %5 = cir.const #cir.int<1> : !s32i
+      cir.return %5 : !s32i
+    }
+    cir.return %arg0 : !s32i
+  }
+//      CHECK: cir.func @onlyIf(%arg0: !s32i) -> !s32i {
+// CHECK-NEXT:   %0 = cir.cast(int_to_bool, %arg0 : !s32i), !cir.bool
+// CHECK-NEXT:   cir.brcond %0 ^bb1, ^bb2
+// CHECK-NEXT: ^bb1:  // pred: ^bb0
+// CHECK-NEXT:   %1 = cir.const #cir.int<1> : !s32i
+// CHECK-NEXT:   cir.return %1 : !s32i
+// CHECK-NEXT: ^bb2:  // pred: ^bb0
+// CHECK-NEXT:   cir.return %arg0 : !s32i
+// CHECK-NEXT: }
+
+}



More information about the cfe-commits mailing list