llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang 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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits