Skip to content

Commit ccf2284

Browse files
[CIR] Add if statement support (#134333)
This patch adds support for if statements in the CIR dialect Additionally, multiple RUN lines were introduced to improve codegen test coverage
1 parent 34d586f commit ccf2284

File tree

13 files changed

+1019
-9
lines changed

13 files changed

+1019
-9
lines changed

clang/include/clang/CIR/Dialect/IR/CIRDialect.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class SameFirstOperandAndResultType
6060
using BuilderCallbackRef =
6161
llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>;
6262

63+
namespace cir {
64+
void buildTerminatedBody(mlir::OpBuilder &builder, mlir::Location loc);
65+
} // namespace cir
66+
6367
// TableGen'erated files for MLIR dialects require that a macro be defined when
6468
// they are included. GET_OP_CLASSES tells the file to define the classes for
6569
// the operations of that dialect.

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -472,8 +472,8 @@ def StoreOp : CIR_Op<"store", [
472472
// ReturnOp
473473
//===----------------------------------------------------------------------===//
474474

475-
def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "DoWhileOp",
476-
"WhileOp", "ForOp"]>,
475+
def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "IfOp",
476+
"DoWhileOp", "WhileOp", "ForOp"]>,
477477
Terminator]> {
478478
let summary = "Return from function";
479479
let description = [{
@@ -510,6 +510,58 @@ def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "DoWhileOp",
510510
let hasVerifier = 1;
511511
}
512512

513+
//===----------------------------------------------------------------------===//
514+
// IfOp
515+
//===----------------------------------------------------------------------===//
516+
517+
def IfOp : CIR_Op<"if",
518+
[DeclareOpInterfaceMethods<RegionBranchOpInterface>,
519+
RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]>{
520+
521+
let summary = "the if-then-else operation";
522+
let description = [{
523+
The `cir.if` operation represents an if-then-else construct for
524+
conditionally executing two regions of code. The operand is a `cir.bool`
525+
type.
526+
527+
Examples:
528+
529+
```mlir
530+
cir.if %cond {
531+
...
532+
} else {
533+
...
534+
}
535+
536+
cir.if %cond {
537+
...
538+
}
539+
540+
cir.if %cond {
541+
...
542+
cir.br ^a
543+
^a:
544+
cir.yield
545+
}
546+
```
547+
548+
`cir.if` defines no values and the 'else' can be omitted. The if/else
549+
regions must be terminated. If the region has only one block, the terminator
550+
can be left out, and `cir.yield` terminator will be inserted implictly.
551+
Otherwise, the region must be explicitly terminated.
552+
}];
553+
let arguments = (ins CIR_BoolType:$condition);
554+
let regions = (region AnyRegion:$thenRegion, AnyRegion:$elseRegion);
555+
let hasCustomAssemblyFormat=1;
556+
let hasVerifier=1;
557+
let skipDefaultBuilders=1;
558+
let builders = [
559+
OpBuilder<(ins "mlir::Value":$cond, "bool":$withElseRegion,
560+
CArg<"BuilderCallbackRef", "buildTerminatedBody">:$thenBuilder,
561+
CArg<"BuilderCallbackRef", "nullptr">:$elseBuilder)>
562+
];
563+
}
564+
513565
//===----------------------------------------------------------------------===//
514566
// ConditionOp
515567
//===----------------------------------------------------------------------===//
@@ -560,8 +612,8 @@ def ConditionOp : CIR_Op<"condition", [
560612
//===----------------------------------------------------------------------===//
561613

562614
def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
563-
ParentOneOf<["ScopeOp", "WhileOp", "ForOp",
564-
"DoWhileOp"]>]> {
615+
ParentOneOf<["IfOp", "ScopeOp", "WhileOp",
616+
"ForOp", "DoWhileOp"]>]> {
565617
let summary = "Represents the default branching behaviour of a region";
566618
let description = [{
567619
The `cir.yield` operation terminates regions on different CIR operations,

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ struct MissingFeatures {
8080

8181
// Clang early optimizations or things defered to LLVM lowering.
8282
static bool mayHaveIntegerOverflow() { return false; }
83+
static bool shouldReverseUnaryCondOnBoolExpr() { return false; }
8384

8485
// Misc
8586
static bool cxxABI() { return false; }
@@ -110,6 +111,8 @@ struct MissingFeatures {
110111
static bool lvalueBaseInfo() { return false; }
111112
static bool alignCXXRecordDecl() { return false; }
112113
static bool setNonGC() { return false; }
114+
static bool incrementProfileCounter() { return false; }
115+
static bool insertBuiltinUnpredictable() { return false; }
113116

114117
// Missing types
115118
static bool dataMemberType() { return false; }

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "clang/AST/CharUnits.h"
1919
#include "clang/AST/Decl.h"
2020
#include "clang/AST/Expr.h"
21+
#include "clang/AST/ExprCXX.h"
2122
#include "clang/CIR/Dialect/IR/CIRDialect.h"
2223
#include "clang/CIR/MissingFeatures.h"
2324

@@ -474,6 +475,90 @@ void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
474475
emitLValue(e);
475476
}
476477

478+
/// Emit an `if` on a boolean condition, filling `then` and `else` into
479+
/// appropriated regions.
480+
mlir::LogicalResult CIRGenFunction::emitIfOnBoolExpr(const Expr *cond,
481+
const Stmt *thenS,
482+
const Stmt *elseS) {
483+
mlir::Location thenLoc = getLoc(thenS->getSourceRange());
484+
std::optional<mlir::Location> elseLoc;
485+
if (elseS)
486+
elseLoc = getLoc(elseS->getSourceRange());
487+
488+
mlir::LogicalResult resThen = mlir::success(), resElse = mlir::success();
489+
emitIfOnBoolExpr(
490+
cond, /*thenBuilder=*/
491+
[&](mlir::OpBuilder &, mlir::Location) {
492+
LexicalScope lexScope{*this, thenLoc, builder.getInsertionBlock()};
493+
resThen = emitStmt(thenS, /*useCurrentScope=*/true);
494+
},
495+
thenLoc,
496+
/*elseBuilder=*/
497+
[&](mlir::OpBuilder &, mlir::Location) {
498+
assert(elseLoc && "Invalid location for elseS.");
499+
LexicalScope lexScope{*this, *elseLoc, builder.getInsertionBlock()};
500+
resElse = emitStmt(elseS, /*useCurrentScope=*/true);
501+
},
502+
elseLoc);
503+
504+
return mlir::LogicalResult::success(resThen.succeeded() &&
505+
resElse.succeeded());
506+
}
507+
508+
/// Emit an `if` on a boolean condition, filling `then` and `else` into
509+
/// appropriated regions.
510+
cir::IfOp CIRGenFunction::emitIfOnBoolExpr(
511+
const clang::Expr *cond, BuilderCallbackRef thenBuilder,
512+
mlir::Location thenLoc, BuilderCallbackRef elseBuilder,
513+
std::optional<mlir::Location> elseLoc) {
514+
// Attempt to be as accurate as possible with IfOp location, generate
515+
// one fused location that has either 2 or 4 total locations, depending
516+
// on else's availability.
517+
SmallVector<mlir::Location, 2> ifLocs{thenLoc};
518+
if (elseLoc)
519+
ifLocs.push_back(*elseLoc);
520+
mlir::Location loc = mlir::FusedLoc::get(&getMLIRContext(), ifLocs);
521+
522+
// Emit the code with the fully general case.
523+
mlir::Value condV = emitOpOnBoolExpr(loc, cond);
524+
return builder.create<cir::IfOp>(loc, condV, elseLoc.has_value(),
525+
/*thenBuilder=*/thenBuilder,
526+
/*elseBuilder=*/elseBuilder);
527+
}
528+
529+
/// TODO(cir): see EmitBranchOnBoolExpr for extra ideas).
530+
mlir::Value CIRGenFunction::emitOpOnBoolExpr(mlir::Location loc,
531+
const Expr *cond) {
532+
assert(!cir::MissingFeatures::pgoUse());
533+
assert(!cir::MissingFeatures::generateDebugInfo());
534+
cond = cond->IgnoreParens();
535+
536+
// In LLVM the condition is reversed here for efficient codegen.
537+
// This should be done in CIR prior to LLVM lowering, if we do now
538+
// we can make CIR based diagnostics misleading.
539+
// cir.ternary(!x, t, f) -> cir.ternary(x, f, t)
540+
assert(!cir::MissingFeatures::shouldReverseUnaryCondOnBoolExpr());
541+
542+
if (isa<ConditionalOperator>(cond)) {
543+
cgm.errorNYI(cond->getExprLoc(), "Ternary NYI");
544+
assert(!cir::MissingFeatures::ternaryOp());
545+
return createDummyValue(loc, cond->getType());
546+
}
547+
548+
if (isa<CXXThrowExpr>(cond)) {
549+
cgm.errorNYI("NYI");
550+
return createDummyValue(loc, cond->getType());
551+
}
552+
553+
// If the branch has a condition wrapped by __builtin_unpredictable,
554+
// create metadata that specifies that the branch is unpredictable.
555+
// Don't bother if not optimizing because that metadata would not be used.
556+
assert(!cir::MissingFeatures::insertBuiltinUnpredictable());
557+
558+
// Emit the code with the fully general case.
559+
return evaluateExprAsBool(cond);
560+
}
561+
477562
mlir::Value CIRGenFunction::emitAlloca(StringRef name, mlir::Type ty,
478563
mlir::Location loc, CharUnits alignment,
479564
bool insertIntoFnEntryBlock,

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,71 @@ mlir::Location CIRGenFunction::getLoc(mlir::Location lhs, mlir::Location rhs) {
135135
return mlir::FusedLoc::get(locs, metadata, &getMLIRContext());
136136
}
137137

138+
bool CIRGenFunction::containsLabel(const Stmt *s, bool ignoreCaseStmts) {
139+
// Null statement, not a label!
140+
if (!s)
141+
return false;
142+
143+
// If this is a label, we have to emit the code, consider something like:
144+
// if (0) { ... foo: bar(); } goto foo;
145+
//
146+
// TODO: If anyone cared, we could track __label__'s, since we know that you
147+
// can't jump to one from outside their declared region.
148+
if (isa<LabelStmt>(s))
149+
return true;
150+
151+
// If this is a case/default statement, and we haven't seen a switch, we
152+
// have to emit the code.
153+
if (isa<SwitchCase>(s) && !ignoreCaseStmts)
154+
return true;
155+
156+
// If this is a switch statement, we want to ignore case statements when we
157+
// recursively process the sub-statements of the switch. If we haven't
158+
// encountered a switch statement, we treat case statements like labels, but
159+
// if we are processing a switch statement, case statements are expected.
160+
if (isa<SwitchStmt>(s))
161+
ignoreCaseStmts = true;
162+
163+
// Scan subexpressions for verboten labels.
164+
return std::any_of(s->child_begin(), s->child_end(),
165+
[=](const Stmt *subStmt) {
166+
return containsLabel(subStmt, ignoreCaseStmts);
167+
});
168+
}
169+
170+
/// If the specified expression does not fold to a constant, or if it does but
171+
/// contains a label, return false. If it constant folds return true and set
172+
/// the boolean result in Result.
173+
bool CIRGenFunction::constantFoldsToBool(const Expr *cond, bool &resultBool,
174+
bool allowLabels) {
175+
llvm::APSInt resultInt;
176+
if (!constantFoldsToSimpleInteger(cond, resultInt, allowLabels))
177+
return false;
178+
179+
resultBool = resultInt.getBoolValue();
180+
return true;
181+
}
182+
183+
/// If the specified expression does not fold to a constant, or if it does
184+
/// fold but contains a label, return false. If it constant folds, return
185+
/// true and set the folded value.
186+
bool CIRGenFunction::constantFoldsToSimpleInteger(const Expr *cond,
187+
llvm::APSInt &resultInt,
188+
bool allowLabels) {
189+
// FIXME: Rename and handle conversion of other evaluatable things
190+
// to bool.
191+
Expr::EvalResult result;
192+
if (!cond->EvaluateAsInt(result, getContext()))
193+
return false; // Not foldable, not integer or not fully evaluatable.
194+
195+
llvm::APSInt intValue = result.Val.getInt();
196+
if (!allowLabels && containsLabel(cond))
197+
return false; // Contains a label.
198+
199+
resultInt = intValue;
200+
return true;
201+
}
202+
138203
void CIRGenFunction::emitAndUpdateRetAlloca(QualType type, mlir::Location loc,
139204
CharUnits alignment) {
140205
if (!type->isVoidType()) {

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "clang/AST/ASTContext.h"
2525
#include "clang/AST/CharUnits.h"
2626
#include "clang/AST/Decl.h"
27+
#include "clang/AST/Stmt.h"
2728
#include "clang/AST/Type.h"
2829
#include "clang/CIR/Dialect/IR/CIRDialect.h"
2930
#include "clang/CIR/MissingFeatures.h"
@@ -166,6 +167,20 @@ class CIRGenFunction : public CIRGenTypeCache {
166167
/// that it requires no code to be generated.
167168
bool isTrivialInitializer(const Expr *init);
168169

170+
/// If the specified expression does not fold to a constant, or if it does but
171+
/// contains a label, return false. If it constant folds return true and set
172+
/// the boolean result in Result.
173+
bool constantFoldsToBool(const clang::Expr *cond, bool &resultBool,
174+
bool allowLabels = false);
175+
bool constantFoldsToSimpleInteger(const clang::Expr *cond,
176+
llvm::APSInt &resultInt,
177+
bool allowLabels = false);
178+
179+
/// Return true if the statement contains a label in it. If
180+
/// this statement is not executed normally, it not containing a label means
181+
/// that we can just remove the code.
182+
bool containsLabel(const clang::Stmt *s, bool ignoreCaseStmts = false);
183+
169184
struct AutoVarEmission {
170185
const clang::VarDecl *Variable;
171186
/// The address of the alloca for languages with explicit address space
@@ -459,6 +474,25 @@ class CIRGenFunction : public CIRGenTypeCache {
459474
mlir::LogicalResult emitDeclStmt(const clang::DeclStmt &s);
460475
LValue emitDeclRefLValue(const clang::DeclRefExpr *e);
461476

477+
/// Emit an `if` on a boolean condition to the specified blocks.
478+
/// FIXME: Based on the condition, this might try to simplify the codegen of
479+
/// the conditional based on the branch.
480+
/// In the future, we may apply code generation simplifications here,
481+
/// similar to those used in classic LLVM codegen
482+
/// See `EmitBranchOnBoolExpr` for inspiration.
483+
mlir::LogicalResult emitIfOnBoolExpr(const clang::Expr *cond,
484+
const clang::Stmt *thenS,
485+
const clang::Stmt *elseS);
486+
cir::IfOp emitIfOnBoolExpr(const clang::Expr *cond,
487+
BuilderCallbackRef thenBuilder,
488+
mlir::Location thenLoc,
489+
BuilderCallbackRef elseBuilder,
490+
std::optional<mlir::Location> elseLoc = {});
491+
492+
mlir::Value emitOpOnBoolExpr(mlir::Location loc, const clang::Expr *cond);
493+
494+
mlir::LogicalResult emitIfStmt(const clang::IfStmt &s);
495+
462496
/// Emit code to compute the specified expression,
463497
/// ignoring the result.
464498
void emitIgnoredExpr(const clang::Expr *e);

0 commit comments

Comments
 (0)