[clang] [CIR] Add if statement support (PR #134333)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 3 18:45:33 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: None (Andres-Salamanca)
<details>
<summary>Changes</summary>
This patch adds support for if statements in the CIR dialect
Additionally, multiple RUN lines were introduced to improve codegen test coverage
---
Patch is 43.50 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/134333.diff
13 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/IR/CIRDialect.h (+4)
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+56-4)
- (modified) clang/include/clang/CIR/MissingFeatures.h (+4)
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+100)
- (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+14)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+49)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+34)
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+69-2)
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+128)
- (modified) clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp (+67-3)
- (added) clang/test/CIR/CodeGen/if.cpp (+254)
- (added) clang/test/CIR/Lowering/if.cir (+99)
- (added) clang/test/CIR/Transforms/if.cir (+48)
``````````diff
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/CIRDi...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/134333
More information about the cfe-commits
mailing list