[clang] [CIR] Upstream lowering of conditional operators to TernaryOp (PR #138156)
via cfe-commits
cfe-commits at lists.llvm.org
Thu May 1 08:50:06 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Morris Hafner (mmha)
<details>
<summary>Changes</summary>
This patch adds visitors for BinLAnd, BinLOr and AbstractConditionalOperator. Note that this patch still lacks visitation of OpaqueValueExpr which is needed for the GNU ?: operator.
---
Patch is 63.94 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/138156.diff
12 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+16)
- (modified) clang/include/clang/CIR/MissingFeatures.h (+4-1)
- (modified) clang/lib/CIR/CodeGen/CIRGenDecl.cpp (+1-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+263-13)
- (modified) clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp (+9-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+334)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+14)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+243-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenValue.h (+1)
- (added) clang/test/CIR/CodeGen/binop.c (+13)
- (modified) clang/test/CIR/CodeGen/binop.cpp (+225)
- (added) clang/test/CIR/CodeGen/ternary.cpp (+120)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index d58ced6ec8bff..104cf9e3d3875 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -272,6 +272,22 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return createCast(loc, cir::CastKind::bitcast, src, newTy);
}
+ // TODO(cir): the following function was introduced to keep in sync with LLVM
+ // codegen. CIR does not have "zext" operations. It should eventually be
+ // renamed or removed. For now, we just add whatever cast is required here.
+ mlir::Value createZExtOrBitCast(mlir::Location loc, mlir::Value src,
+ mlir::Type newTy) {
+ mlir::Type srcTy = src.getType();
+
+ if (srcTy == newTy)
+ return src;
+
+ if (mlir::isa<cir::BoolType>(srcTy) && mlir::isa<cir::IntType>(newTy))
+ return createBoolToInt(src, newTy);
+
+ llvm_unreachable("unhandled extension cast");
+ }
+
//===--------------------------------------------------------------------===//
// Binary Operators
//===--------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 4d4951aa0e126..7b345cf1e8c7f 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -163,6 +163,8 @@ struct MissingFeatures {
static bool setDSOLocal() { return false; }
static bool foldCaseStmt() { return false; }
static bool constantFoldSwitchStatement() { return false; }
+ static bool peepholeProtection() { return false; }
+ static bool instrumenation() { return false; }
// Missing types
static bool dataMemberType() { return false; }
@@ -188,8 +190,9 @@ struct MissingFeatures {
static bool ptrStrideOp() { return false; }
static bool selectOp() { return false; }
static bool switchOp() { return false; }
- static bool ternaryOp() { return false; }
+ static bool throwOp() { return false; }
static bool tryOp() { return false; }
+ static bool vecTernaryOp() { return false; }
static bool zextOp() { return false; }
// Future CIR attributes
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 90498cd18f46b..6f1ed2959c1dc 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -50,8 +50,7 @@ CIRGenFunction::emitAutoVarAlloca(const VarDecl &d) {
// A normal fixed sized variable becomes an alloca in the entry block,
mlir::Type allocaTy = convertTypeForMem(ty);
// Create the temp alloca and declare variable using it.
- address = createTempAlloca(allocaTy, alignment, loc, d.getName(),
- /*insertIntoFnEntryBlock=*/false);
+ address = createTempAlloca(allocaTy, alignment, loc, d.getName());
declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()), alignment);
emission.Addr = address;
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index da5a0b97a395e..48102e8c56b3e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -15,6 +15,7 @@
#include "CIRGenModule.h"
#include "CIRGenValue.h"
#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/Value.h"
#include "clang/AST/Attr.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/Decl.h"
@@ -22,6 +23,7 @@
#include "clang/AST/ExprCXX.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/MissingFeatures.h"
+#include <optional>
using namespace clang;
using namespace clang::CIRGen;
@@ -218,7 +220,7 @@ void CIRGenFunction::emitStoreThroughLValue(RValue src, LValue dst,
static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, const Expr *e,
const VarDecl *vd) {
- QualType T = e->getType();
+ QualType t = e->getType();
// If it's thread_local, emit a call to its wrapper function instead.
assert(!cir::MissingFeatures::opGlobalThreadLocal());
@@ -248,7 +250,7 @@ static LValue emitGlobalVarDeclLValue(CIRGenFunction &cgf, const Expr *e,
cgf.cgm.errorNYI(e->getSourceRange(),
"emitGlobalVarDeclLValue: reference type");
else
- lv = cgf.makeAddrLValue(addr, T, AlignmentSource::Decl);
+ lv = cgf.makeAddrLValue(addr, t, AlignmentSource::Decl);
assert(!cir::MissingFeatures::setObjCGCLValueClass());
return lv;
}
@@ -948,6 +950,165 @@ void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
emitLValue(e);
}
+// Handle the case where the condition is a constant evaluatable simple integer,
+// which means we don't have to separately handle the true/false blocks.
+static std::optional<LValue> handleConditionalOperatorLValueSimpleCase(
+ CIRGenFunction &cgf, const AbstractConditionalOperator *e) {
+ const Expr *condExpr = e->getCond();
+ bool condExprBool;
+ if (cgf.constantFoldsToSimpleInteger(condExpr, condExprBool)) {
+ const Expr *live = e->getTrueExpr(), *dead = e->getFalseExpr();
+ if (!condExprBool)
+ std::swap(live, dead);
+
+ if (!cgf.containsLabel(dead)) {
+ // If the true case is live, we need to track its region.
+ if (condExprBool) {
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+ }
+ // If a throw expression we emit it and return an undefined lvalue
+ // because it can't be used.
+ if (isa<CXXThrowExpr>(live->IgnoreParens())) {
+ assert(!cir::MissingFeatures::throwOp());
+ cgf.cgm.errorNYI(live->getSourceRange(),
+ "throw expressions in conditional operator");
+ return std::nullopt;
+ }
+ return cgf.emitLValue(live);
+ }
+ }
+ return std::nullopt;
+}
+
+/// Emit the operand of a glvalue conditional operator. This is either a glvalue
+/// or a (possibly-parenthesized) throw-expression. If this is a throw, no
+/// LValue is returned and the current block has been terminated.
+static std::optional<LValue> emitLValueOrThrowExpression(CIRGenFunction &cgf,
+ const Expr *operand) {
+ if (isa<CXXThrowExpr>(operand->IgnoreParens())) {
+ assert(!cir::MissingFeatures::throwOp());
+ cgf.cgm.errorNYI(operand->getSourceRange(),
+ "throw expressions in conditional operator");
+ return std::nullopt;
+ }
+
+ return cgf.emitLValue(operand);
+}
+
+// Create and generate the 3 blocks for a conditional operator.
+// Leaves the 'current block' in the continuation basic block.
+template <typename FuncTy>
+CIRGenFunction::ConditionalInfo
+CIRGenFunction::emitConditionalBlocks(const AbstractConditionalOperator *e,
+ const FuncTy &branchGenFunc) {
+ ConditionalInfo info;
+ CIRGenFunction &cgf = *this;
+ ConditionalEvaluation eval(cgf);
+ mlir::Location loc = cgf.getLoc(e->getSourceRange());
+ CIRGenBuilderTy &builder = cgf.getBuilder();
+ Expr *trueExpr = e->getTrueExpr();
+ Expr *falseExpr = e->getFalseExpr();
+
+ mlir::Value condV = cgf.emitOpOnBoolExpr(loc, e->getCond());
+ SmallVector<mlir::OpBuilder::InsertPoint, 2> insertPoints{};
+ mlir::Type yieldTy{};
+
+ auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, Expr *expr,
+ std::optional<LValue> &branchInfo) {
+ CIRGenFunction::LexicalScope lexScope{cgf, loc, b.getInsertionBlock()};
+ cgf.curLexScope->setAsTernary();
+
+ assert(!cir::MissingFeatures::incrementProfileCounter());
+ eval.begin(cgf);
+ branchInfo = branchGenFunc(cgf, expr);
+ mlir::Value branch = branchInfo->getPointer();
+ eval.end(cgf);
+
+ if (branch) {
+ yieldTy = branch.getType();
+ b.create<cir::YieldOp>(loc, branch);
+ } else {
+ // If LHS or RHS is a throw or void expression we need to patch
+ // arms as to properly match yield types.
+ insertPoints.push_back(b.saveInsertionPoint());
+ }
+ };
+
+ info.result = builder
+ .create<cir::TernaryOp>(
+ loc, condV, /*trueBuilder=*/
+ [&](mlir::OpBuilder &b, mlir::Location loc) {
+ emitBranch(b, loc, trueExpr, info.lhs);
+ },
+ /*falseBuilder=*/
+ [&](mlir::OpBuilder &b, mlir::Location loc) {
+ emitBranch(b, loc, falseExpr, info.rhs);
+ })
+ .getResult();
+
+ if (!insertPoints.empty()) {
+ // If both arms are void, so be it.
+ if (!yieldTy)
+ yieldTy = cgf.VoidTy;
+
+ // Insert required yields.
+ for (mlir::OpBuilder::InsertPoint &toInsert : insertPoints) {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.restoreInsertionPoint(toInsert);
+
+ // Block does not return: build empty yield.
+ if (mlir::isa<cir::VoidType>(yieldTy)) {
+ builder.create<cir::YieldOp>(loc);
+ } else { // Block returns: set null yield value.
+ mlir::Value op0 = builder.getNullValue(yieldTy, loc);
+ builder.create<cir::YieldOp>(loc, op0);
+ }
+ }
+ }
+ return info;
+}
+
+LValue CIRGenFunction::emitConditionalOperatorLValue(
+ const AbstractConditionalOperator *expr) {
+ if (!expr->isGLValue()) {
+ // ?: here should be an aggregate.
+ assert(hasAggregateEvaluationKind(expr->getType()) &&
+ "Unexpected conditional operator!");
+ return emitAggExprToLValue(expr);
+ }
+
+ OpaqueValueMapping binding(*this, expr);
+ if (std::optional<LValue> res =
+ handleConditionalOperatorLValueSimpleCase(*this, expr))
+ return *res;
+
+ ConditionalInfo info =
+ emitConditionalBlocks(expr, [](CIRGenFunction &cgf, const Expr *e) {
+ return emitLValueOrThrowExpression(cgf, e);
+ });
+
+ if ((info.lhs && !info.lhs->isSimple()) ||
+ (info.rhs && !info.rhs->isSimple())) {
+ cgm.errorNYI(expr->getSourceRange(), "unsupported conditional operator");
+ return {};
+ }
+
+ if (info.lhs && info.rhs) {
+ Address lhsAddr = info.lhs->getAddress();
+ Address rhsAddr = info.rhs->getAddress();
+ Address result(info.result, lhsAddr.getElementType(),
+ std::min(lhsAddr.getAlignment(), rhsAddr.getAlignment()));
+ AlignmentSource alignSource =
+ std::max(info.lhs->getBaseInfo().getAlignmentSource(),
+ info.rhs->getBaseInfo().getAlignmentSource());
+ assert(!cir::MissingFeatures::opTBAA());
+ return makeAddrLValue(result, expr->getType(), LValueBaseInfo(alignSource));
+ }
+ assert((info.lhs || info.rhs) &&
+ "both operands of glvalue conditional are throw-expressions?");
+ return info.lhs ? *info.lhs : *info.rhs;
+}
+
/// Emit an `if` on a boolean condition, filling `then` and `else` into
/// appropriated regions.
mlir::LogicalResult CIRGenFunction::emitIfOnBoolExpr(const Expr *cond,
@@ -1012,10 +1173,28 @@ mlir::Value CIRGenFunction::emitOpOnBoolExpr(mlir::Location loc,
// 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 (const ConditionalOperator *condOp = dyn_cast<ConditionalOperator>(cond)) {
+ Expr *trueExpr = condOp->getTrueExpr();
+ Expr *falseExpr = condOp->getFalseExpr();
+ mlir::Value condV = emitOpOnBoolExpr(loc, condOp->getCond());
+
+ mlir::Value ternaryOpRes =
+ builder
+ .create<cir::TernaryOp>(
+ loc, condV, /*thenBuilder=*/
+ [this, trueExpr](mlir::OpBuilder &b, mlir::Location loc) {
+ mlir::Value lhs = emitScalarExpr(trueExpr);
+ b.create<cir::YieldOp>(loc, lhs);
+ },
+ /*elseBuilder=*/
+ [this, falseExpr](mlir::OpBuilder &b, mlir::Location loc) {
+ mlir::Value rhs = emitScalarExpr(falseExpr);
+ b.create<cir::YieldOp>(loc, rhs);
+ })
+ .getResult();
+
+ return emitScalarConversion(ternaryOpRes, condOp->getType(),
+ getContext().BoolTy, condOp->getExprLoc());
}
if (isa<CXXThrowExpr>(cond)) {
@@ -1118,13 +1297,84 @@ mlir::Value CIRGenFunction::createDummyValue(mlir::Location loc,
return builder.createDummyValue(loc, t, alignment);
}
-/// This creates an alloca and inserts it into the entry block if
-/// \p insertIntoFnEntryBlock is true, otherwise it inserts it at the current
-/// insertion point of the builder.
+//===----------------------------------------------------------------------===//
+// CIR builder helpers
+//===----------------------------------------------------------------------===//
+
+Address CIRGenFunction::createMemTemp(QualType ty, mlir::Location loc,
+ const Twine &name, Address *alloca,
+ mlir::OpBuilder::InsertPoint ip) {
+ // FIXME: Should we prefer the preferred type alignment here?
+ return createMemTemp(ty, getContext().getTypeAlignInChars(ty), loc, name,
+ alloca, ip);
+}
+
+Address CIRGenFunction::createMemTemp(QualType ty, CharUnits align,
+ mlir::Location loc, const Twine &name,
+ Address *alloca,
+ mlir::OpBuilder::InsertPoint ip) {
+ Address result = createTempAlloca(convertTypeForMem(ty), align, loc, name,
+ /*ArraySize=*/nullptr, alloca, ip);
+ if (ty->isConstantMatrixType()) {
+ assert(!cir::MissingFeatures::matrixType());
+ cgm.errorNYI(loc, "temporary matrix value");
+ }
+ return result;
+}
+
+/// This creates a alloca and inserts it into the entry block of the
+/// current region.
+Address CIRGenFunction::createTempAllocaWithoutCast(
+ mlir::Type ty, CharUnits align, mlir::Location loc, const Twine &name,
+ mlir::Value arraySize, mlir::OpBuilder::InsertPoint ip) {
+ cir::AllocaOp alloca = ip.isSet()
+ ? createTempAlloca(ty, loc, name, ip, arraySize)
+ : createTempAlloca(ty, loc, name, arraySize);
+ alloca.setAlignmentAttr(cgm.getSize(align));
+ return Address(alloca, ty, align);
+}
+
+/// This creates a alloca and inserts it into the entry block. The alloca is
+/// casted to default address space if necessary.
Address CIRGenFunction::createTempAlloca(mlir::Type ty, CharUnits align,
mlir::Location loc, const Twine &name,
- bool insertIntoFnEntryBlock) {
- mlir::Value alloca =
- emitAlloca(name.str(), ty, loc, align, insertIntoFnEntryBlock);
- return Address(alloca, ty, align);
+ mlir::Value arraySize,
+ Address *allocaAddr,
+ mlir::OpBuilder::InsertPoint ip) {
+ Address alloca =
+ createTempAllocaWithoutCast(ty, align, loc, name, arraySize, ip);
+ if (allocaAddr)
+ *allocaAddr = alloca;
+ mlir::Value v = alloca.getPointer();
+ // Alloca always returns a pointer in alloca address space, which may
+ // be different from the type defined by the language. For example,
+ // in C++ the auto variables are in the default address space. Therefore
+ // cast alloca to the default address space when necessary.
+ assert(!cir::MissingFeatures::addressSpace());
+ return Address(v, ty, align);
+}
+
+/// This creates an alloca and inserts it into the entry block if \p ArraySize
+/// is nullptr, otherwise inserts it at the current insertion point of the
+/// builder.
+cir::AllocaOp CIRGenFunction::createTempAlloca(mlir::Type ty,
+ mlir::Location loc,
+ const Twine &name,
+ mlir::Value arraySize,
+ bool insertIntoFnEntryBlock) {
+ return cast<cir::AllocaOp>(emitAlloca(name.str(), ty, loc, CharUnits(),
+ insertIntoFnEntryBlock, arraySize)
+ .getDefiningOp());
+}
+
+/// This creates an alloca and inserts it into the provided insertion point
+cir::AllocaOp CIRGenFunction::createTempAlloca(mlir::Type ty,
+ mlir::Location loc,
+ const Twine &name,
+ mlir::OpBuilder::InsertPoint ip,
+ mlir::Value arraySize) {
+ assert(ip.isSet() && "Insertion point is not set");
+ return cast<cir::AllocaOp>(
+ emitAlloca(name.str(), ty, loc, CharUnits(), ip, arraySize)
+ .getDefiningOp());
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
index e006a77c6e7d6..928a5eb8fe3be 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
@@ -155,8 +155,7 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
// Allocate the temporary variable
// to store the pointer to first unitialized element
const Address tmpAddr = cgf.createTempAlloca(
- cirElementPtrType, cgf.getPointerAlign(), loc, "arrayinit.temp",
- /*insertIntoFnEntryBlock=*/false);
+ cirElementPtrType, cgf.getPointerAlign(), loc, "arrayinit.temp");
LValue tmpLV = cgf.makeAddrLValue(tmpAddr, elementPtrType);
cgf.emitStoreThroughLValue(RValue::get(element), tmpLV);
@@ -275,3 +274,11 @@ void AggExprEmitter::visitCXXParenListOrInitListExpr(
void CIRGenFunction::emitAggExpr(const Expr *e, AggValueSlot slot) {
AggExprEmitter(*this, slot).Visit(const_cast<Expr *>(e));
}
+
+LValue CIRGenFunction::emitAggExprToLValue(const Expr *e) {
+ assert(hasAggregateEvaluationKind(e->getType()) && "Invalid argument!");
+ Address temp = createMemTemp(e->getType(), getLoc(e->getSourceRange()));
+ LValue lv = makeAddrLValue(temp, e->getType());
+ emitAggExpr(e, AggValueSlot::forLValue(lv));
+ return lv;
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index aef5b125a2877..cd07a6cc59fc5 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -302,6 +302,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
}
mlir::Value VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *e);
+ mlir::Value
+ VisitAbstractConditionalOperator(const AbstractConditionalOperator *e);
// Unary Operators.
mlir::Value VisitUnaryPostDec(const UnaryOperator *e) {
@@ -875,6 +877,174 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
// NOTE: We don't need to EnsureInsertPoint() like LLVM codegen.
return Visit(e->getRHS());
}
+
+ mlir::Value VisitBinLAnd(const clang::BinaryOperator *e) {
+ if (e->getType()->isVectorType()) {
+ assert(!cir::MissingFeatures::vectorType());
+ return {};
+ }
+
+ bool instrumentRegions = cgf.cgm.getCodeGenOpts().hasProfileClangInstr();
+ mlir::Type resTy = cgf.convertType(e->getType());
+ mlir::Location loc = cgf.getLoc(e->getExprLoc());
+
+ // If we have 0 && RHS, see if we can elide RHS, if so, just return 0.
+ // If we have 1 && X, just emit X without inserting the control flow.
+ bool lhsCondVal;
+ if (cgf.constantFoldsToSimpleInteger(e->getLHS(), lhsCondVal)) {
+ if (lhsCondVal) { // If we have 1 && X, just emit X.
+
+ mlir::Value rhsCond = cgf.evaluateExprAsBool(e->getRHS());
+
+ if (instrumentRegions) {
+ assert(!cir::MissingFeatures::instrumenation());
+ cgf.cgm.errorNYI(e->getExprLoc(), "instrumenation");
+ }
+ // ZExt result to int or bool.
+ return builder.createZExtOrBitCast(rhsCond.getLoc(), rhsCond, resTy);
+ }
+ // 0 && RHS: If it is safe, just elide the RHS, and return 0/false.
+ ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/138156
More information about the cfe-commits
mailing list