Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ class SameFirstOperandAndResultType
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.
Expand Down
60 changes: 56 additions & 4 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -472,8 +472,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 = [{
Expand Down Expand Up @@ -510,6 +510,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 %cond {
...
} else {
...
}

cir.if %cond {
...
}

cir.if %cond {
...
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
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -560,8 +612,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,
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,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; }
Expand Down Expand Up @@ -110,6 +111,9 @@ struct MissingFeatures {
static bool lvalueBaseInfo() { return false; }
static bool alignCXXRecordDecl() { return false; }
static bool setNonGC() { return false; }
static bool constantFoldsToSimpleInteger() { return false; }
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't used now. It can be removed.

static bool incrementProfileCounter() { return false; }
static bool insertBuiltinUnpredictable() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
85 changes: 85 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/MissingFeatures.h"

Expand Down Expand Up @@ -474,6 +475,90 @@ 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) {
mlir::Location thenLoc = getLoc(thenS->getSourceRange());
std::optional<mlir::Location> elseLoc;
if (elseS)
elseLoc = getLoc(elseS->getSourceRange());

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) {
// Attempt to be as accurate as possible with IfOp location, generate
// one fused location that has either 2 or 4 total locations, depending
// on else's availability.
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): see EmitBranchOnBoolExpr for extra ideas).
mlir::Value CIRGenFunction::emitOpOnBoolExpr(mlir::Location loc,
const Expr *cond) {
assert(!cir::MissingFeatures::pgoUse());
assert(!cir::MissingFeatures::generateDebugInfo());
cond = cond->IgnoreParens();

// 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 (isa<ConditionalOperator>(cond)) {
cgm.errorNYI(cond->getExprLoc(), "Ternary NYI");
assert(!cir::MissingFeatures::ternaryOp());
return createDummyValue(loc, cond->getType());
}

if (isa<CXXThrowExpr>(cond)) {
cgm.errorNYI("NYI");
return createDummyValue(loc, cond->getType());
}

// 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.
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,
Expand Down
14 changes: 14 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,20 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *e) {
return {};
}

/// 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,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you rename this to constantFoldsToBool and move it to the same file as the other constantFoldsToInteger implementation?

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(
Expand Down
49 changes: 49 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// If this is a switch statement, we want to ignore cases below it.
// If this is a switch statement, we want to ignore case statements when we
// recursively process the sub-statements of the switch. If we haven't
// encountered a switch statement, we treat case statements like labels, but
// if we are processing a switch statement, case statements are expected.

This is intended to capture the outcome of the discussion below.

if (isa<SwitchStmt>(s))
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is necessary? SwitchStmt we probably still want the lables for (since you can jump into them), but CaseStmt are the ones we want to ignore.

So the comment isn't really accurate

Copy link
Member

Choose a reason for hiding this comment

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

This matches OG and it's probably being conservative, I wouldn't go about changing it right now (at least not until this is also tested / changed in OG as well).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Its actually being the inverse of conservative, isn't it? It is going to end up skipping blocks that it shouldn't, causing compile-issues.

Copy link
Contributor

Choose a reason for hiding this comment

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

I dug into this a bit more and I think the code here is correct. This function is calling itself recursively to walk through all the sub-statements in the original statement. If we hit a case statement without having seen a switch statement, we treat the case statement as a label (on line 153 above). However, if we have seen a switch statement and we're recursing into the sub-statements of that switch statement, then case statements are expected and we don't want to treat them as labels.

This is basically happening in the context of determining whether or not we can constant-fold the statement. I put together an experiment to show how the classic codegen handles labels in a switch statement.

https://godbolt.org/z/dY14fWezT

If there is a label in the middle of a switch statement, an if statement that uses that switch (foo()) won't be constant folded, but the same switch without labels (bar()) is folded.

Note that the clang IR incubator doesn't fold either case, but it does if the if is part of a constexpr.

https://godbolt.org/z/TqWf69joe

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
/// fold 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()) {
Expand Down
34 changes: 34 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -166,6 +167,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
Expand Down Expand Up @@ -459,6 +474,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.
/// In the future, we may apply code generation simplifications here,
/// similar to those used in classic LLVM codegen
/// See `EmitBranchOnBoolExpr` for inspiration.
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);
Expand Down
Loading