Author: Morris Hafner Date: 2025-06-03T13:29:23+02:00 New Revision: 4b2cb118bc5825c309724d536053c6f9817e2eb9
URL: https://github.com/llvm/llvm-project/commit/4b2cb118bc5825c309724d536053c6f9817e2eb9 DIFF: https://github.com/llvm/llvm-project/commit/4b2cb118bc5825c309724d536053c6f9817e2eb9.diff LOG: [CIR] Upstream lowering of conditional operators to TernaryOp (#138156) 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. --------- Co-authored-by: Erich Keane <eke...@nvidia.com> Added: clang/test/CIR/CodeGen/binop.c clang/test/CIR/CodeGen/ternary.cpp Modified: clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenDecl.cpp clang/lib/CIR/CodeGen/CIRGenExpr.cpp clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/lib/CIR/CodeGen/CIRGenValue.h clang/test/CIR/CodeGen/binop.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index e9d8a2baedf2f..5cd0caa823ca1 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -300,6 +300,24 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return createBinop(loc, lhs, cir::BinOpKind::Or, rhs); } + mlir::Value createSelect(mlir::Location loc, mlir::Value condition, + mlir::Value trueValue, mlir::Value falseValue) { + assert(trueValue.getType() == falseValue.getType() && + "trueValue and falseValue should have the same type"); + return create<cir::SelectOp>(loc, trueValue.getType(), condition, trueValue, + falseValue); + } + + mlir::Value createLogicalAnd(mlir::Location loc, mlir::Value lhs, + mlir::Value rhs) { + return createSelect(loc, lhs, rhs, getBool(false, loc)); + } + + mlir::Value createLogicalOr(mlir::Location loc, mlir::Value lhs, + mlir::Value rhs) { + return createSelect(loc, lhs, getBool(true, loc), rhs); + } + mlir::Value createMul(mlir::Location loc, mlir::Value lhs, mlir::Value rhs, OverflowBehavior ob = OverflowBehavior::None) { auto op = diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 65978e51a23e9..2a7cd464b8f6b 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -208,6 +208,8 @@ struct MissingFeatures { static bool deferredDecls() { return false; } static bool setTargetAttributes() { return false; } static bool coverageMapping() { return false; } + static bool peepholeProtection() { return false; } + static bool instrumentation() { return false; } // Missing types static bool dataMemberType() { return false; } @@ -232,8 +234,9 @@ struct MissingFeatures { static bool ptrDiffOp() { return false; } static bool ptrStrideOp() { 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 61af33053dc0a..80b0172090aa3 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 5424c6a8d6f3c..1175fdc0be2cf 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; @@ -229,7 +231,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()); @@ -259,7 +261,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; } @@ -1259,10 +1261,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)) { @@ -1394,13 +1414,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 diff erent 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 b375bcf2f483f..56d7ea3884ba7 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); @@ -274,3 +273,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 36d52251a0106..77287ec45972d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -98,6 +98,14 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value emitPromoted(const Expr *e, QualType promotionType); + mlir::Value maybePromoteBoolResult(mlir::Value value, + mlir::Type dstTy) const { + if (mlir::isa<cir::IntType>(dstTy)) + return builder.createBoolToInt(value, dstTy); + if (mlir::isa<cir::BoolType>(dstTy)) + return value; + } + //===--------------------------------------------------------------------===// // Visitor Methods //===--------------------------------------------------------------------===// @@ -334,6 +342,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) { @@ -934,6 +944,71 @@ 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 {}; + } + + assert(!cir::MissingFeatures::instrumentation()); + mlir::Type resTy = cgf.convertType(e->getType()); + mlir::Location loc = cgf.getLoc(e->getExprLoc()); + + CIRGenFunction::ConditionalEvaluation eval(cgf); + + mlir::Value lhsCondV = cgf.evaluateExprAsBool(e->getLHS()); + auto resOp = builder.create<cir::TernaryOp>( + loc, lhsCondV, /*trueBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScope lexScope{cgf, loc, + b.getInsertionBlock()}; + cgf.curLexScope->setAsTernary(); + b.create<cir::YieldOp>(loc, cgf.evaluateExprAsBool(e->getRHS())); + }, + /*falseBuilder*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScope lexScope{cgf, loc, + b.getInsertionBlock()}; + cgf.curLexScope->setAsTernary(); + auto res = b.create<cir::ConstantOp>(loc, builder.getFalseAttr()); + b.create<cir::YieldOp>(loc, res.getRes()); + }); + return maybePromoteBoolResult(resOp.getResult(), resTy); + } + + mlir::Value VisitBinLOr(const clang::BinaryOperator *e) { + if (e->getType()->isVectorType()) { + assert(!cir::MissingFeatures::vectorType()); + return {}; + } + + assert(!cir::MissingFeatures::instrumentation()); + mlir::Type resTy = cgf.convertType(e->getType()); + mlir::Location loc = cgf.getLoc(e->getExprLoc()); + + CIRGenFunction::ConditionalEvaluation eval(cgf); + + mlir::Value lhsCondV = cgf.evaluateExprAsBool(e->getLHS()); + auto resOp = builder.create<cir::TernaryOp>( + loc, lhsCondV, /*trueBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScope lexScope{cgf, loc, + b.getInsertionBlock()}; + cgf.curLexScope->setAsTernary(); + auto res = b.create<cir::ConstantOp>(loc, builder.getTrueAttr()); + b.create<cir::YieldOp>(loc, res.getRes()); + }, + /*falseBuilder*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScope lexScope{cgf, loc, + b.getInsertionBlock()}; + cgf.curLexScope->setAsTernary(); + b.create<cir::YieldOp>(loc, cgf.evaluateExprAsBool(e->getRHS())); + }); + + return maybePromoteBoolResult(resOp.getResult(), resTy); + } }; LValue ScalarExprEmitter::emitCompoundAssignLValue( @@ -1781,11 +1856,7 @@ mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *e) { boolVal = builder.createNot(boolVal); // ZExt result to the expr type. - mlir::Type dstTy = cgf.convertType(e->getType()); - if (mlir::isa<cir::IntType>(dstTy)) - return builder.createBoolToInt(boolVal, dstTy); - if (mlir::isa<cir::BoolType>(dstTy)) - return boolVal; + return maybePromoteBoolResult(boolVal, cgf.convertType(e->getType())); cgf.cgm.errorNYI("destination type for logical-not unary operator is NYI"); return {}; @@ -1828,6 +1899,162 @@ mlir::Value ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr( cgf.cgm.UInt64Ty, e->EvaluateKnownConstInt(cgf.getContext()))); } +/// Return true if the specified expression is cheap enough and side-effect-free +/// enough to evaluate unconditionally instead of conditionally. This is used +/// to convert control flow into selects in some cases. +/// TODO(cir): can be shared with LLVM codegen. +static bool isCheapEnoughToEvaluateUnconditionally(const Expr *e, + CIRGenFunction &cgf) { + // Anything that is an integer or floating point constant is fine. + return e->IgnoreParens()->isEvaluatable(cgf.getContext()); + + // Even non-volatile automatic variables can't be evaluated unconditionally. + // Referencing a thread_local may cause non-trivial initialization work to + // occur. If we're inside a lambda and one of the variables is from the scope + // outside the lambda, that function may have returned already. Reading its + // locals is a bad idea. Also, these reads may introduce races there didn't + // exist in the source-level program. +} + +mlir::Value ScalarExprEmitter::VisitAbstractConditionalOperator( + const AbstractConditionalOperator *e) { + CIRGenBuilderTy &builder = cgf.getBuilder(); + mlir::Location loc = cgf.getLoc(e->getSourceRange()); + ignoreResultAssign = false; + + // Bind the common expression if necessary. + CIRGenFunction::OpaqueValueMapping binding(cgf, e); + + Expr *condExpr = e->getCond(); + Expr *lhsExpr = e->getTrueExpr(); + Expr *rhsExpr = e->getFalseExpr(); + + // If the condition constant folds and can be elided, try to avoid emitting + // the condition and the dead arm. + bool condExprBool; + if (cgf.constantFoldsToBool(condExpr, condExprBool)) { + Expr *live = lhsExpr, *dead = rhsExpr; + if (!condExprBool) + std::swap(live, dead); + + // If the dead side doesn't have labels we need, just emit the Live part. + if (!cgf.containsLabel(dead)) { + if (condExprBool) + assert(!cir::MissingFeatures::incrementProfileCounter()); + mlir::Value result = Visit(live); + + // If the live part is a throw expression, it acts like it has a void + // type, so evaluating it returns a null Value. However, a conditional + // with non-void type must return a non-null Value. + if (!result && !e->getType()->isVoidType()) { + cgf.cgm.errorNYI(e->getSourceRange(), + "throw expression in conditional operator"); + result = {}; + } + + return result; + } + } + + // OpenCL: If the condition is a vector, we can treat this condition like + // the select function. + if ((cgf.getLangOpts().OpenCL && condExpr->getType()->isVectorType()) || + condExpr->getType()->isExtVectorType()) { + assert(!cir::MissingFeatures::vectorType()); + cgf.cgm.errorNYI(e->getSourceRange(), "vector ternary op"); + } + + if (condExpr->getType()->isVectorType() || + condExpr->getType()->isSveVLSBuiltinType()) { + assert(!cir::MissingFeatures::vecTernaryOp()); + cgf.cgm.errorNYI(e->getSourceRange(), "vector ternary op"); + return {}; + } + + // If this is a really simple expression (like x ? 4 : 5), emit this as a + // select instead of as control flow. We can only do this if it is cheap + // and safe to evaluate the LHS and RHS unconditionally. + if (isCheapEnoughToEvaluateUnconditionally(lhsExpr, cgf) && + isCheapEnoughToEvaluateUnconditionally(rhsExpr, cgf)) { + bool lhsIsVoid = false; + mlir::Value condV = cgf.evaluateExprAsBool(condExpr); + assert(!cir::MissingFeatures::incrementProfileCounter()); + + mlir::Value lhs = Visit(lhsExpr); + if (!lhs) { + lhs = builder.getNullValue(cgf.VoidTy, loc); + lhsIsVoid = true; + } + + mlir::Value rhs = Visit(rhsExpr); + if (lhsIsVoid) { + assert(!rhs && "lhs and rhs types must match"); + rhs = builder.getNullValue(cgf.VoidTy, loc); + } + + return builder.createSelect(loc, condV, lhs, rhs); + } + + mlir::Value condV = cgf.emitOpOnBoolExpr(loc, condExpr); + CIRGenFunction::ConditionalEvaluation eval(cgf); + SmallVector<mlir::OpBuilder::InsertPoint, 2> insertPoints{}; + mlir::Type yieldTy{}; + + auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, Expr *expr) { + CIRGenFunction::LexicalScope lexScope{cgf, loc, b.getInsertionBlock()}; + cgf.curLexScope->setAsTernary(); + + assert(!cir::MissingFeatures::incrementProfileCounter()); + eval.beginEvaluation(); + mlir::Value branch = Visit(expr); + eval.endEvaluation(); + + 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()); + } + }; + + mlir::Value result = builder + .create<cir::TernaryOp>( + loc, condV, + /*trueBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + emitBranch(b, loc, lhsExpr); + }, + /*falseBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + emitBranch(b, loc, rhsExpr); + }) + .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 result; +} + mlir::Value CIRGenFunction::emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv, bool isInc, bool isPre) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index ef61aa7f4ee6d..ee014adc961be 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -118,6 +118,144 @@ class CIRGenFunction : public CIRGenTypeCache { const TargetInfo &getTarget() const { return cgm.getTarget(); } mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); } + // --------------------- + // Opaque value handling + // --------------------- + + /// Keeps track of the current set of opaque value expressions. + llvm::DenseMap<const OpaqueValueExpr *, LValue> opaqueLValues; + llvm::DenseMap<const OpaqueValueExpr *, RValue> opaqueRValues; + +public: + /// A non-RAII class containing all the information about a bound + /// opaque value. OpaqueValueMapping, below, is a RAII wrapper for + /// this which makes individual mappings very simple; using this + /// class directly is useful when you have a variable number of + /// opaque values or don't want the RAII functionality for some + /// reason. + class OpaqueValueMappingData { + const OpaqueValueExpr *opaqueValue; + bool boundLValue; + + OpaqueValueMappingData(const OpaqueValueExpr *ov, bool boundLValue) + : opaqueValue(ov), boundLValue(boundLValue) {} + + public: + OpaqueValueMappingData() : opaqueValue(nullptr) {} + + static bool shouldBindAsLValue(const Expr *expr) { + // gl-values should be bound as l-values for obvious reasons. + // Records should be bound as l-values because IR generation + // always keeps them in memory. Expressions of function type + // act exactly like l-values but are formally required to be + // r-values in C. + return expr->isGLValue() || expr->getType()->isFunctionType() || + hasAggregateEvaluationKind(expr->getType()); + } + + static OpaqueValueMappingData + bind(CIRGenFunction &cgf, const OpaqueValueExpr *ov, const Expr *e) { + if (shouldBindAsLValue(ov)) + return bind(cgf, ov, cgf.emitLValue(e)); + return bind(cgf, ov, cgf.emitAnyExpr(e)); + } + + static OpaqueValueMappingData + bind(CIRGenFunction &cgf, const OpaqueValueExpr *ov, const LValue &lv) { + assert(shouldBindAsLValue(ov)); + cgf.opaqueLValues.insert(std::make_pair(ov, lv)); + return OpaqueValueMappingData(ov, true); + } + + static OpaqueValueMappingData + bind(CIRGenFunction &cgf, const OpaqueValueExpr *ov, const RValue &rv) { + assert(!shouldBindAsLValue(ov)); + cgf.opaqueRValues.insert(std::make_pair(ov, rv)); + + OpaqueValueMappingData data(ov, false); + + // Work around an extremely aggressive peephole optimization in + // EmitScalarConversion which assumes that all other uses of a + // value are extant. + assert(!cir::MissingFeatures::peepholeProtection() && "NYI"); + return data; + } + + bool isValid() const { return opaqueValue != nullptr; } + void clear() { opaqueValue = nullptr; } + + void unbind(CIRGenFunction &cgf) { + assert(opaqueValue && "no data to unbind!"); + + if (boundLValue) { + cgf.opaqueLValues.erase(opaqueValue); + } else { + cgf.opaqueRValues.erase(opaqueValue); + assert(!cir::MissingFeatures::peepholeProtection() && "NYI"); + } + } + }; + + /// An RAII object to set (and then clear) a mapping for an OpaqueValueExpr. + class OpaqueValueMapping { + CIRGenFunction &cgf; + OpaqueValueMappingData data; + + public: + static bool shouldBindAsLValue(const Expr *expr) { + return OpaqueValueMappingData::shouldBindAsLValue(expr); + } + + /// Build the opaque value mapping for the given conditional + /// operator if it's the GNU ?: extension. This is a common + /// enough pattern that the convenience operator is really + /// helpful. + /// + OpaqueValueMapping(CIRGenFunction &cgf, + const AbstractConditionalOperator *op) + : cgf(cgf) { + if (mlir::isa<ConditionalOperator>(op)) + // Leave Data empty. + return; + + const BinaryConditionalOperator *e = + mlir::cast<BinaryConditionalOperator>(op); + data = OpaqueValueMappingData::bind(cgf, e->getOpaqueValue(), + e->getCommon()); + } + + /// Build the opaque value mapping for an OpaqueValueExpr whose source + /// expression is set to the expression the OVE represents. + OpaqueValueMapping(CIRGenFunction &cgf, const OpaqueValueExpr *ov) + : cgf(cgf) { + if (ov) { + assert(ov->getSourceExpr() && "wrong form of OpaqueValueMapping used " + "for OVE with no source expression"); + data = OpaqueValueMappingData::bind(cgf, ov, ov->getSourceExpr()); + } + } + + OpaqueValueMapping(CIRGenFunction &cgf, const OpaqueValueExpr *opaqueValue, + LValue lvalue) + : cgf(cgf), + data(OpaqueValueMappingData::bind(cgf, opaqueValue, lvalue)) {} + + OpaqueValueMapping(CIRGenFunction &cgf, const OpaqueValueExpr *opaqueValue, + RValue rvalue) + : cgf(cgf), + data(OpaqueValueMappingData::bind(cgf, opaqueValue, rvalue)) {} + + void pop() { + data.unbind(cgf); + data.clear(); + } + + ~OpaqueValueMapping() { + if (data.isValid()) + data.unbind(cgf); + } + }; + private: /// Declare a variable in the current scope, return success if the variable /// wasn't declared yet. @@ -272,7 +410,7 @@ class CIRGenFunction : public CIRGenTypeCache { /// Returns the address of the object within this declaration. /// Note that this does not chase the forwarding pointer for /// __block decls. - Address getObjectAddress(CIRGenFunction &CGF) const { + Address getObjectAddress(CIRGenFunction &cgf) const { if (!IsEscapingByRef) return Addr; @@ -501,6 +639,8 @@ class CIRGenFunction : public CIRGenTypeCache { void emitAggExpr(const clang::Expr *e, AggValueSlot slot); + LValue emitAggExprToLValue(const Expr *e); + /// Emit code to compute the specified expression which can have any type. The /// result is returned as an RValue struct. If this is an aggregate /// expression, the aggloc/agglocvolatile arguments indicate where the result @@ -747,12 +887,101 @@ class CIRGenFunction : public CIRGenTypeCache { void emitNullabilityCheck(LValue lhs, mlir::Value rhs, clang::SourceLocation loc); + /// An object to manage conditionally-evaluated expressions. + class ConditionalEvaluation { + CIRGenFunction &cgf; + mlir::OpBuilder::InsertPoint insertPt; + + public: + ConditionalEvaluation(CIRGenFunction &cgf) + : cgf(cgf), insertPt(cgf.builder.saveInsertionPoint()) {} + ConditionalEvaluation(CIRGenFunction &cgf, mlir::OpBuilder::InsertPoint ip) + : cgf(cgf), insertPt(ip) {} + + void beginEvaluation() { + assert(cgf.outermostConditional != this); + if (!cgf.outermostConditional) + cgf.outermostConditional = this; + } + + void endEvaluation() { + assert(cgf.outermostConditional != nullptr); + if (cgf.outermostConditional == this) + cgf.outermostConditional = nullptr; + } + + /// Returns the insertion point which will be executed prior to each + /// evaluation of the conditional code. In LLVM OG, this method + /// is called getStartingBlock. + mlir::OpBuilder::InsertPoint getInsertPoint() const { return insertPt; } + }; + + struct ConditionalInfo { + std::optional<LValue> lhs{}, rhs{}; + mlir::Value result{}; + }; + + // Return true if we're currently emitting one branch or the other of a + // conditional expression. + bool isInConditionalBranch() const { return outermostConditional != nullptr; } + + void setBeforeOutermostConditional(mlir::Value value, Address addr) { + assert(isInConditionalBranch()); + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(outermostConditional->getInsertPoint()); + builder.createStore( + value.getLoc(), value, addr, + mlir::IntegerAttr::get( + mlir::IntegerType::get(value.getContext(), 64), + (uint64_t)addr.getAlignment().getAsAlign().value())); + } + } + + // Points to the outermost active conditional control. This is used so that + // we know if a temporary should be destroyed conditionally. + ConditionalEvaluation *outermostConditional = nullptr; + + template <typename FuncTy> + ConditionalInfo emitConditionalBlocks(const AbstractConditionalOperator *e, + const FuncTy &branchGenFunc); + + mlir::Value emitTernaryOnBoolExpr(const clang::Expr *cond, mlir::Location loc, + const clang::Stmt *thenS, + const clang::Stmt *elseS); + /// ---------------------- /// CIR build helpers /// ----------------- public: + cir::AllocaOp createTempAlloca(mlir::Type ty, mlir::Location loc, + const Twine &name = "tmp", + mlir::Value arraySize = nullptr, + bool insertIntoFnEntryBlock = false); + cir::AllocaOp createTempAlloca(mlir::Type ty, mlir::Location loc, + const Twine &name = "tmp", + mlir::OpBuilder::InsertPoint ip = {}, + mlir::Value arraySize = nullptr); Address createTempAlloca(mlir::Type ty, CharUnits align, mlir::Location loc, - const Twine &name, bool insertIntoFnEntryBlock); + const Twine &name = "tmp", + mlir::Value arraySize = nullptr, + Address *alloca = nullptr, + mlir::OpBuilder::InsertPoint ip = {}); + Address createTempAllocaWithoutCast(mlir::Type ty, CharUnits align, + mlir::Location loc, + const Twine &name = "tmp", + mlir::Value arraySize = nullptr, + mlir::OpBuilder::InsertPoint ip = {}); + + /// Create a temporary memory object of the given type, with + /// appropriate alignmen and cast it to the default address space. Returns + /// the original alloca instruction by \p Alloca if it is not nullptr. + Address createMemTemp(QualType t, mlir::Location loc, + const Twine &name = "tmp", Address *alloca = nullptr, + mlir::OpBuilder::InsertPoint ip = {}); + Address createMemTemp(QualType t, CharUnits align, mlir::Location loc, + const Twine &name = "tmp", Address *alloca = nullptr, + mlir::OpBuilder::InsertPoint ip = {}); //===--------------------------------------------------------------------===// // OpenACC Emission diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h index 3feadfaf56354..56177c948df94 100644 --- a/clang/lib/CIR/CodeGen/CIRGenValue.h +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -164,6 +164,7 @@ class LValue { clang::Qualifiers &getQuals() { return quals; } LValueBaseInfo getBaseInfo() const { return baseInfo; } + void setBaseInfo(LValueBaseInfo info) { baseInfo = info; } static LValue makeAddr(Address address, clang::QualType t, LValueBaseInfo baseInfo) { diff --git a/clang/test/CIR/CodeGen/binop.c b/clang/test/CIR/CodeGen/binop.c new file mode 100644 index 0000000000000..280fd29b067f9 --- /dev/null +++ b/clang/test/CIR/CodeGen/binop.c @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s + +void conditionalResultIimplicitCast(int a, int b, float f) { + // Should implicit cast back to int. + int x = a && b; + // CHECK: %[[#INT:]] = cir.ternary + // CHECK: %{{.+}} = cir.cast(bool_to_int, %[[#INT]] : !cir.bool), !s32i + float y = f && f; + // CHECK: %[[#BOOL:]] = cir.ternary + // CHECK: %[[#INT:]] = cir.cast(bool_to_int, %[[#BOOL]] : !cir.bool), !s32i + // CHECK: %{{.+}} = cir.cast(int_to_float, %[[#INT]] : !s32i), !cir.float +} diff --git a/clang/test/CIR/CodeGen/binop.cpp b/clang/test/CIR/CodeGen/binop.cpp index dbd17fb7ba83d..c728f0d0c1bc1 100644 --- a/clang/test/CIR/CodeGen/binop.cpp +++ b/clang/test/CIR/CodeGen/binop.cpp @@ -540,3 +540,235 @@ void long_shift_example(long long a, short b) { // OGCG: store i64 %[[SHL]], ptr %[[X]] // OGCG: ret void + +void b1(bool a, bool b) { + bool x = a && b; + x = x || b; +} + +// CIR-LABEL: cir.func @_Z2b1bb( +// CIR-SAME: %[[ARG0:.*]]: !cir.bool {{.*}}, %[[ARG1:.*]]: !cir.bool {{.*}}) +// CIR: [[A:%[0-9]+]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["a", init] +// CIR: [[B:%[0-9]+]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["b", init] +// CIR: [[X:%[0-9]+]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["x", init] +// CIR: cir.store %[[ARG0]], [[A]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.store %[[ARG1]], [[B]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: [[AVAL:%[0-9]+]] = cir.load align(1) [[A]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR: [[RES1:%[0-9]+]] = cir.ternary([[AVAL]], true { +// CIR: [[BVAL:%[0-9]+]] = cir.load align(1) [[B]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR: cir.yield [[BVAL]] : !cir.bool +// CIR: }, false { +// CIR: [[FALSE:%[0-9]+]] = cir.const #false +// CIR: cir.yield [[FALSE]] : !cir.bool +// CIR: }) : (!cir.bool) -> !cir.bool +// CIR: cir.store align(1) [[RES1]], [[X]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: [[XVAL:%[0-9]+]] = cir.load align(1) [[X]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR: [[RES2:%[0-9]+]] = cir.ternary([[XVAL]], true { +// CIR: [[TRUE:%[0-9]+]] = cir.const #true +// CIR: cir.yield [[TRUE]] : !cir.bool +// CIR: }, false { +// CIR: [[BVAL2:%[0-9]+]] = cir.load align(1) [[B]] : !cir.ptr<!cir.bool>, !cir.bool +// CIR: cir.yield [[BVAL2]] : !cir.bool +// CIR: }) : (!cir.bool) -> !cir.bool +// CIR: cir.store align(1) [[RES2]], [[X]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.return + + +// LLVM-LABEL: define void @_Z2b1bb( +// LLVM-SAME: i1 %[[ARG0:.+]], i1 %[[ARG1:.+]]) +// LLVM: %[[A_ADDR:.*]] = alloca i8 +// LLVM: %[[B_ADDR:.*]] = alloca i8 +// LLVM: %[[X:.*]] = alloca i8 +// LLVM: %[[ZEXT0:.*]] = zext i1 %[[ARG0]] to i8 +// LLVM: store i8 %[[ZEXT0]], ptr %[[A_ADDR]] +// LLVM: %[[ZEXT1:.*]] = zext i1 %[[ARG1]] to i8 +// LLVM: store i8 %[[ZEXT1]], ptr %[[B_ADDR]] +// LLVM: %[[A_VAL:.*]] = load i8, ptr %[[A_ADDR]] +// LLVM: %[[A_BOOL:.*]] = trunc i8 %[[A_VAL]] to i1 +// LLVM: br i1 %[[A_BOOL]], label %[[AND_TRUE:.+]], label %[[AND_FALSE:.+]] +// LLVM: [[AND_TRUE]]: +// LLVM: %[[B_VAL:.*]] = load i8, ptr %[[B_ADDR]] +// LLVM: %[[B_BOOL:.*]] = trunc i8 %[[B_VAL]] to i1 +// LLVM: br label %[[AND_MERGE:.+]] +// LLVM: [[AND_FALSE]]: +// LLVM: br label %[[AND_MERGE]] +// LLVM: [[AND_MERGE]]: +// LLVM: %[[AND_PHI:.*]] = phi i1 [ false, %[[AND_FALSE]] ], [ %[[B_BOOL]], %[[AND_TRUE]] ] +// LLVM: %[[ZEXT_AND:.*]] = zext i1 %[[AND_PHI]] to i8 +// LLVM: store i8 %[[ZEXT_AND]], ptr %[[X]] +// LLVM: %[[X_VAL:.*]] = load i8, ptr %[[X]] +// LLVM: %[[X_BOOL:.*]] = trunc i8 %[[X_VAL]] to i1 +// LLVM: br i1 %[[X_BOOL]], label %[[OR_TRUE:.+]], label %[[OR_FALSE:.+]] +// LLVM: [[OR_TRUE]]: +// LLVM: br label %[[OR_MERGE:.+]] +// LLVM: [[OR_FALSE]]: +// LLVM: %[[B_VAL2:.*]] = load i8, ptr %[[B_ADDR]] +// LLVM: %[[B_BOOL2:.*]] = trunc i8 %[[B_VAL2]] to i1 +// LLVM: br label %[[OR_MERGE]] +// LLVM: [[OR_MERGE]]: +// LLVM: %[[OR_PHI:.*]] = phi i1 [ %[[B_BOOL2]], %[[OR_FALSE]] ], [ true, %[[OR_TRUE]] ] +// LLVM: %[[ZEXT_OR:.*]] = zext i1 %[[OR_PHI]] to i8 +// LLVM: store i8 %[[ZEXT_OR]], ptr %[[X]] +// LLVM: ret void + +// OGCG-LABEL: define dso_local void @_Z2b1bb +// OGCG-SAME: (i1 {{.*}} %[[ARG0:.+]], i1 {{.*}} %[[ARG1:.+]]) +// OGCG: [[ENTRY:.*]]: +// OGCG: %[[A_ADDR:.*]] = alloca i8 +// OGCG: %[[B_ADDR:.*]] = alloca i8 +// OGCG: %[[X:.*]] = alloca i8 +// OGCG: %[[ZEXT0:.*]] = zext i1 %[[ARG0]] to i8 +// OGCG: store i8 %[[ZEXT0]], ptr %[[A_ADDR]] +// OGCG: %[[ZEXT1:.*]] = zext i1 %[[ARG1]] to i8 +// OGCG: store i8 %[[ZEXT1]], ptr %[[B_ADDR]] +// OGCG: %[[A_VAL:.*]] = load i8, ptr %[[A_ADDR]] +// OGCG: %[[A_BOOL:.*]] = trunc i8 %[[A_VAL]] to i1 +// OGCG: br i1 %[[A_BOOL]], label %[[AND_TRUE:.+]], label %[[AND_MERGE:.+]] +// OGCG: [[AND_TRUE]]: +// OGCG: %[[B_VAL:.*]] = load i8, ptr %[[B_ADDR]] +// OGCG: %[[B_BOOL:.*]] = trunc i8 %[[B_VAL]] to i1 +// OGCG: br label %[[AND_MERGE:.+]] +// OGCG: [[AND_MERGE]]: +// OGCG: %[[AND_PHI:.*]] = phi i1 [ false, %[[ENTRY]] ], [ %[[B_BOOL]], %[[AND_TRUE]] ] +// OGCG: %[[ZEXT_AND:.*]] = zext i1 %[[AND_PHI]] to i8 +// OGCG: store i8 %[[ZEXT_AND]], ptr %[[X]] +// OGCG: %[[X_VAL:.*]] = load i8, ptr %[[X]] +// OGCG: %[[X_BOOL:.*]] = trunc i8 %[[X_VAL]] to i1 +// OGCG: br i1 %[[X_BOOL]], label %[[OR_MERGE:.+]], label %[[OR_FALSE:.+]] +// OGCG: [[OR_FALSE]]: +// OGCG: %[[B_VAL2:.*]] = load i8, ptr %[[B_ADDR]] +// OGCG: %[[B_BOOL2:.*]] = trunc i8 %[[B_VAL2]] to i1 +// OGCG: br label %[[OR_MERGE]] +// OGCG: [[OR_MERGE]]: +// OGCG: %[[OR_PHI:.*]] = phi i1 [ true, %[[AND_MERGE]] ], [ %[[B_BOOL2]], %[[OR_FALSE]] ] +// OGCG: %[[ZEXT_OR:.*]] = zext i1 %[[OR_PHI]] to i8 +// OGCG: store i8 %[[ZEXT_OR]], ptr %[[X]] +// OGCG: ret void + +void b3(int a, int b, int c, int d) { + bool x = (a == b) && (c == d); + x = (a == b) || (c == d); +} + +// CIR-LABEL: cir.func @_Z2b3iiii( +// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}, %[[ARG1:.*]]: !s32i {{.*}}, %[[ARG2:.*]]: !s32i {{.*}}, %[[ARG3:.*]]: !s32i {{.*}}) +// CIR: [[A:%[0-9]+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["a", init] +// CIR: [[B:%[0-9]+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init] +// CIR: [[C:%[0-9]+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["c", init] +// CIR: [[D:%[0-9]+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["d", init] +// CIR: [[X:%[0-9]+]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["x", init] +// CIR: cir.store %[[ARG0]], [[A]] : !s32i, !cir.ptr<!s32i> +// CIR: cir.store %[[ARG1]], [[B]] : !s32i, !cir.ptr<!s32i> +// CIR: cir.store %[[ARG2]], [[C]] : !s32i, !cir.ptr<!s32i> +// CIR: cir.store %[[ARG3]], [[D]] : !s32i, !cir.ptr<!s32i> +// CIR: [[AVAL1:%[0-9]+]] = cir.load align(4) [[A]] : !cir.ptr<!s32i>, !s32i +// CIR: [[BVAL1:%[0-9]+]] = cir.load align(4) [[B]] : !cir.ptr<!s32i>, !s32i +// CIR: [[CMP1:%[0-9]+]] = cir.cmp(eq, [[AVAL1]], [[BVAL1]]) : !s32i, !cir.bool +// CIR: [[AND_RESULT:%[0-9]+]] = cir.ternary([[CMP1]], true { +// CIR: [[CVAL1:%[0-9]+]] = cir.load align(4) [[C]] : !cir.ptr<!s32i>, !s32i +// CIR: [[DVAL1:%[0-9]+]] = cir.load align(4) [[D]] : !cir.ptr<!s32i>, !s32i +// CIR: [[CMP2:%[0-9]+]] = cir.cmp(eq, [[CVAL1]], [[DVAL1]]) : !s32i, !cir.bool +// CIR: cir.yield [[CMP2]] : !cir.bool +// CIR: }, false { +// CIR: [[FALSE:%[0-9]+]] = cir.const #false +// CIR: cir.yield [[FALSE]] : !cir.bool +// CIR: }) : (!cir.bool) -> !cir.bool +// CIR: cir.store align(1) [[AND_RESULT]], [[X]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: [[AVAL2:%[0-9]+]] = cir.load align(4) [[A]] : !cir.ptr<!s32i>, !s32i +// CIR: [[BVAL2:%[0-9]+]] = cir.load align(4) [[B]] : !cir.ptr<!s32i>, !s32i +// CIR: [[CMP3:%[0-9]+]] = cir.cmp(eq, [[AVAL2]], [[BVAL2]]) : !s32i, !cir.bool +// CIR: [[OR_RESULT:%[0-9]+]] = cir.ternary([[CMP3]], true { +// CIR: [[TRUE:%[0-9]+]] = cir.const #true +// CIR: cir.yield [[TRUE]] : !cir.bool +// CIR: }, false { +// CIR: [[CVAL2:%[0-9]+]] = cir.load align(4) [[C]] : !cir.ptr<!s32i>, !s32i +// CIR: [[DVAL2:%[0-9]+]] = cir.load align(4) [[D]] : !cir.ptr<!s32i>, !s32i +// CIR: [[CMP4:%[0-9]+]] = cir.cmp(eq, [[CVAL2]], [[DVAL2]]) : !s32i, !cir.bool +// CIR: cir.yield [[CMP4]] : !cir.bool +// CIR: }) : (!cir.bool) -> !cir.bool +// CIR: cir.store align(1) [[OR_RESULT]], [[X]] : !cir.bool, !cir.ptr<!cir.bool> +// CIR: cir.return + + +// LLVM-LABEL: define void @_Z2b3iiii( +// LLVM-SAME: i32 %[[ARG0:.+]], i32 %[[ARG1:.+]], i32 %[[ARG2:.+]], i32 %[[ARG3:.+]]) +// LLVM: %[[A_ADDR:.*]] = alloca i32, i64 1 +// LLVM: %[[B_ADDR:.*]] = alloca i32, i64 1 +// LLVM: %[[C_ADDR:.*]] = alloca i32, i64 1 +// LLVM: %[[D_ADDR:.*]] = alloca i32, i64 1 +// LLVM: %[[X:.*]] = alloca i8, i64 1 +// LLVM: store i32 %[[ARG0]], ptr %[[A_ADDR]] +// LLVM: store i32 %[[ARG1]], ptr %[[B_ADDR]] +// LLVM: store i32 %[[ARG2]], ptr %[[C_ADDR]] +// LLVM: store i32 %[[ARG3]], ptr %[[D_ADDR]] +// LLVM: %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]] +// LLVM: %[[B_VAL:.*]] = load i32, ptr %[[B_ADDR]] +// LLVM: %[[CMP1:.*]] = icmp eq i32 %[[A_VAL]], %[[B_VAL]] +// LLVM: br i1 %[[CMP1]], label %[[AND_TRUE:.+]], label %[[AND_FALSE:.+]] +// LLVM: [[AND_TRUE]]: +// LLVM: %[[C_VAL:.*]] = load i32, ptr %[[C_ADDR]] +// LLVM: %[[D_VAL:.*]] = load i32, ptr %[[D_ADDR]] +// LLVM: %[[CMP2:.*]] = icmp eq i32 %[[C_VAL]], %[[D_VAL]] +// LLVM: br label %[[AND_MERGE:.+]] +// LLVM: [[AND_FALSE]]: +// LLVM: br label %[[AND_MERGE]] +// LLVM: [[AND_MERGE]]: +// LLVM: %[[AND_PHI:.*]] = phi i1 [ false, %[[AND_FALSE]] ], [ %[[CMP2]], %[[AND_TRUE]] ] +// LLVM: %[[ZEXT_AND:.*]] = zext i1 %[[AND_PHI]] to i8 +// LLVM: store i8 %[[ZEXT_AND]], ptr %[[X]] +// LLVM: %[[A_VAL2:.*]] = load i32, ptr %[[A_ADDR]] +// LLVM: %[[B_VAL2:.*]] = load i32, ptr %[[B_ADDR]] +// LLVM: %[[CMP3:.*]] = icmp eq i32 %[[A_VAL2]], %[[B_VAL2]] +// LLVM: br i1 %[[CMP3]], label %[[OR_TRUE:.+]], label %[[OR_FALSE:.+]] +// LLVM: [[OR_TRUE]]: +// LLVM: br label %[[OR_MERGE:.+]] +// LLVM: [[OR_FALSE]]: +// LLVM: %[[C_VAL2:.*]] = load i32, ptr %[[C_ADDR]] +// LLVM: %[[D_VAL2:.*]] = load i32, ptr %[[D_ADDR]] +// LLVM: %[[CMP4:.*]] = icmp eq i32 %[[C_VAL2]], %[[D_VAL2]] +// LLVM: br label %[[OR_MERGE]] +// LLVM: [[OR_MERGE]]: +// LLVM: %[[OR_PHI:.*]] = phi i1 [ %[[CMP4]], %[[OR_FALSE]] ], [ true, %[[OR_TRUE]] ] +// LLVM: %[[ZEXT_OR:.*]] = zext i1 %[[OR_PHI]] to i8 +// LLVM: store i8 %[[ZEXT_OR]], ptr %[[X]] +// LLVM: ret void + +// OGCG-LABEL: define dso_local void @_Z2b3iiii( +// OGCG-SAME: i32 {{.*}} %[[ARG0:.+]], i32 {{.*}} %[[ARG1:.+]], i32 {{.*}} %[[ARG2:.+]], i32 {{.*}} %[[ARG3:.+]]) +// OGCG: [[ENTRY:.*]]: +// OGCG: %[[A_ADDR:.*]] = alloca i32 +// OGCG: %[[B_ADDR:.*]] = alloca i32 +// OGCG: %[[C_ADDR:.*]] = alloca i32 +// OGCG: %[[D_ADDR:.*]] = alloca i32 +// OGCG: %[[X:.*]] = alloca i8 +// OGCG: store i32 %[[ARG0]], ptr %[[A_ADDR]] +// OGCG: store i32 %[[ARG1]], ptr %[[B_ADDR]] +// OGCG: store i32 %[[ARG2]], ptr %[[C_ADDR]] +// OGCG: store i32 %[[ARG3]], ptr %[[D_ADDR]] +// OGCG: %[[A_VAL:.*]] = load i32, ptr %[[A_ADDR]] +// OGCG: %[[B_VAL:.*]] = load i32, ptr %[[B_ADDR]] +// OGCG: %[[CMP1:.*]] = icmp eq i32 %[[A_VAL]], %[[B_VAL]] +// OGCG: br i1 %[[CMP1]], label %[[AND_TRUE:.+]], label %[[AND_MERGE:.+]] +// OGCG: [[AND_TRUE]]: +// OGCG: %[[C_VAL:.*]] = load i32, ptr %[[C_ADDR]] +// OGCG: %[[D_VAL:.*]] = load i32, ptr %[[D_ADDR]] +// OGCG: %[[CMP2:.*]] = icmp eq i32 %[[C_VAL]], %[[D_VAL]] +// OGCG: br label %[[AND_MERGE:.+]] +// OGCG: [[AND_MERGE]]: +// OGCG: %[[AND_PHI:.*]] = phi i1 [ false, %[[ENTRY]] ], [ %[[CMP2]], %[[AND_TRUE]] ] +// OGCG: %[[ZEXT_AND:.*]] = zext i1 %[[AND_PHI]] to i8 +// OGCG: store i8 %[[ZEXT_AND]], ptr %[[X]] +// OGCG: %[[A_VAL2:.*]] = load i32, ptr %[[A_ADDR]] +// OGCG: %[[B_VAL2:.*]] = load i32, ptr %[[B_ADDR]] +// OGCG: %[[CMP3:.*]] = icmp eq i32 %[[A_VAL2]], %[[B_VAL2]] +// OGCG: br i1 %[[CMP3]], label %[[OR_MERGE:.+]], label %[[OR_FALSE:.+]] +// OGCG: [[OR_FALSE]]: +// OGCG: %[[C_VAL2:.*]] = load i32, ptr %[[C_ADDR]] +// OGCG: %[[D_VAL2:.*]] = load i32, ptr %[[D_ADDR]] +// OGCG: %[[CMP4:.*]] = icmp eq i32 %[[C_VAL2]], %[[D_VAL2]] +// OGCG: br label %[[OR_MERGE]] +// OGCG: [[OR_MERGE]]: +// OGCG: %[[OR_PHI:.*]] = phi i1 [ true, %[[AND_MERGE]] ], [ %[[CMP4]], %[[OR_FALSE]] ] +// OGCG: %[[ZEXT_OR:.*]] = zext i1 %[[OR_PHI]] to i8 +// OGCG: store i8 %[[ZEXT_OR]], ptr %[[X]] +// OGCG: ret void \ No newline at end of file diff --git a/clang/test/CIR/CodeGen/ternary.cpp b/clang/test/CIR/CodeGen/ternary.cpp new file mode 100644 index 0000000000000..3b66f7ccdf54f --- /dev/null +++ b/clang/test/CIR/CodeGen/ternary.cpp @@ -0,0 +1,147 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG + +int x(int y) { + return y > 0 ? 3 : 5; +} + +// CIR-LABEL: cir.func @_Z1xi( +// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}) -> !s32i { +// CIR: [[Y:%.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["y", init] {alignment = 4 : i64} +// CIR: [[RETVAL:%.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] {alignment = 4 : i64} +// CIR: cir.store %[[ARG0]], [[Y]] : !s32i, !cir.ptr<!s32i> +// CIR: [[YVAL:%.+]] = cir.load align(4) [[Y]] : !cir.ptr<!s32i>, !s32i +// CIR: [[ZERO:%.+]] = cir.const #cir.int<0> : !s32i +// CIR: [[CMP:%.+]] = cir.cmp(gt, [[YVAL]], [[ZERO]]) : !s32i, !cir.bool +// CIR: [[THREE:%.+]] = cir.const #cir.int<3> : !s32i +// CIR: [[FIVE:%.+]] = cir.const #cir.int<5> : !s32i +// CIR: [[SELECT_RES:%.+]] = cir.select if [[CMP]] then [[THREE]] else [[FIVE]] : (!cir.bool, !s32i, !s32i) -> !s32i +// CIR: cir.store [[SELECT_RES]], [[RETVAL]] : !s32i, !cir.ptr<!s32i> +// CIR: [[RETVAL_VAL:%.+]] = cir.load [[RETVAL]] : !cir.ptr<!s32i>, !s32i +// CIR: cir.return [[RETVAL_VAL]] : !s32i + +// LLVM-LABEL: define i32 @_Z1xi( +// LLVM-SAME: i32 %[[ARG0:.+]]) +// LLVM: %[[Y:.*]] = alloca i32 +// LLVM: %[[RETVAL:.*]] = alloca i32 +// LLVM: store i32 %[[ARG0]], ptr %[[Y]] +// LLVM: %[[YVAL:.*]] = load i32, ptr %[[Y]] +// LLVM: %[[CMP:.*]] = icmp sgt i32 %[[YVAL]], 0 +// LLVM: %[[SELECT:.*]] = select i1 %[[CMP]], i32 3, i32 5 +// LLVM: store i32 %[[SELECT]], ptr %[[RETVAL]] +// LLVM: %[[RESULT:.*]] = load i32, ptr %[[RETVAL]] +// LLVM: ret i32 %[[RESULT]] + +// OGCG-LABEL: define dso_local noundef i32 @_Z1xi( +// OGCG-SAME: i32 {{.*}} %[[ARG0:.+]]) +// OGCG: %[[Y:.*]] = alloca i32 +// OGCG: store i32 %[[ARG0]], ptr %[[Y]] +// OGCG: %[[YVAL:.*]] = load i32, ptr %[[Y]] +// OGCG: %[[CMP:.*]] = icmp sgt i32 %[[YVAL]], 0 +// OGCG: %[[SELECT:.*]] = select i1 %[[CMP]], i32 3, i32 5 +// OGCG: ret i32 %[[SELECT]] + +int foo(int a, int b) { + if (a < b ? 0 : a) + return -1; + return 0; +} + +// CIR-LABEL: cir.func @_Z3fooii( +// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}, %[[ARG1:.*]]: !s32i {{.*}}) -> !s32i { +// CIR: [[A:%.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["a", init] {alignment = 4 : i64} +// CIR: [[B:%.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init] {alignment = 4 : i64} +// CIR: [[RETVAL:%.+]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"] {alignment = 4 : i64} +// CIR: cir.store %[[ARG0]], [[A]] : !s32i, !cir.ptr<!s32i> +// CIR: cir.store %[[ARG1]], [[B]] : !s32i, !cir.ptr<!s32i> +// CIR: cir.scope { +// CIR: [[ALOAD:%.+]] = cir.load align(4) [[A]] : !cir.ptr<!s32i>, !s32i +// CIR: [[BLOAD:%.+]] = cir.load align(4) [[B]] : !cir.ptr<!s32i>, !s32i +// CIR: [[CMP:%.+]] = cir.cmp(lt, [[ALOAD]], [[BLOAD]]) : !s32i, !cir.bool +// CIR: [[TERNARY_RES:%.+]] = cir.ternary([[CMP]], true { +// CIR: [[ZERO:%.+]] = cir.const #cir.int<0> : !s32i +// CIR: cir.yield [[ZERO]] : !s32i +// CIR: }, false { +// CIR: [[ALOAD2:%.+]] = cir.load align(4) [[A]] : !cir.ptr<!s32i>, !s32i +// CIR: cir.yield [[ALOAD2]] : !s32i +// CIR: }) : (!cir.bool) -> !s32i +// CIR: [[CAST:%.+]] = cir.cast(int_to_bool, [[TERNARY_RES]] : !s32i), !cir.bool +// CIR: cir.if [[CAST]] { +// CIR: [[ONE:%.+]] = cir.const #cir.int<1> : !s32i +// CIR: [[MINUS_ONE:%.+]] = cir.unary(minus, [[ONE]]) nsw : !s32i, !s32i +// CIR: cir.store [[MINUS_ONE]], [[RETVAL]] : !s32i, !cir.ptr<!s32i> +// CIR: [[RETVAL_VAL:%.+]] = cir.load [[RETVAL]] : !cir.ptr<!s32i>, !s32i +// CIR: cir.return [[RETVAL_VAL]] : !s32i +// CIR: } +// CIR: } +// CIR: [[ZERO2:%.+]] = cir.const #cir.int<0> : !s32i +// CIR: cir.store [[ZERO2]], [[RETVAL]] : !s32i, !cir.ptr<!s32i> +// CIR: [[RETVAL_VAL2:%.+]] = cir.load [[RETVAL]] : !cir.ptr<!s32i>, !s32i +// CIR: cir.return [[RETVAL_VAL2]] : !s32i + +// LLVM-LABEL: define i32 @_Z3fooii( +// LLVM-SAME: i32 %[[ARG0:.*]], i32 %[[ARG1:.*]]) +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM: %[[RETVAL:.*]] = alloca i32 +// LLVM: store i32 %[[ARG0]], ptr %[[A]] +// LLVM: store i32 %[[ARG1]], ptr %[[B]] +// LLVM: br label %[[ENTRY_BB:.*]] +// LLVM: [[ENTRY_BB]]: +// LLVM: %[[AVAL:.*]] = load i32, ptr %[[A]] +// LLVM: %[[BVAL:.*]] = load i32, ptr %[[B]] +// LLVM: %[[CMP:.*]] = icmp slt i32 %[[AVAL]], %[[BVAL]] +// LLVM: br i1 %[[CMP]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: %[[AVAL2:.*]] = load i32, ptr %[[A]] +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PHI:.*]] = phi i32 [ %[[AVAL2]], %[[FALSE_BB]] ], [ 0, %[[TRUE_BB]] ] +// LLVM: %[[COND:.*]] = icmp ne i32 %[[PHI]], 0 +// LLVM: br i1 %[[COND]], label %[[RETURN_MINUS_ONE:.*]], label %[[CONT_BB:.*]] +// LLVM: [[RETURN_MINUS_ONE]]: +// LLVM: store i32 -1, ptr %[[RETVAL]] +// LLVM: %[[RET1:.*]] = load i32, ptr %[[RETVAL]] +// LLVM: ret i32 %[[RET1]] +// LLVM: [[CONT_BB]]: +// LLVM: br label %[[RETURN_ZERO:.*]] +// LLVM: [[RETURN_ZERO]]: +// LLVM: store i32 0, ptr %[[RETVAL]] +// LLVM: %[[RET2:.*]] = load i32, ptr %[[RETVAL]] +// LLVM: ret i32 %[[RET2]] + +// OGCG-LABEL: define dso_local noundef i32 @_Z3fooii( +// OGCG-SAME: i32 {{.*}} %[[ARG0:.*]], i32 {{.*}} %[[ARG1:.*]]) +// OGCG: %[[RETVAL:.*]] = alloca i32 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: %[[B:.*]] = alloca i32 +// OGCG: store i32 %[[ARG0]], ptr %[[A]] +// OGCG: store i32 %[[ARG1]], ptr %[[B]] +// OGCG: %[[AVAL:.*]] = load i32, ptr %[[A]] +// OGCG: %[[BVAL:.*]] = load i32, ptr %[[B]] +// OGCG: %[[CMP:.*]] = icmp slt i32 %[[AVAL]], %[[BVAL]] +// OGCG: br i1 %[[CMP]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: %[[AVAL2:.*]] = load i32, ptr %[[A]] +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PHI:.*]] = phi i32 [ 0, %[[TRUE_BB]] ], [ %[[AVAL2]], %[[FALSE_BB]] ] +// OGCG: %[[COND:.*]] = icmp ne i32 %[[PHI]], 0 +// OGCG: br i1 %[[COND]], label %[[RETURN_MINUS_ONE:.*]], label %[[RETURN_ZERO:.*]] +// OGCG: [[RETURN_MINUS_ONE]]: +// OGCG: store i32 -1, ptr %[[RETVAL]] +// OGCG: br label %[[RETURN:.+]] +// OGCG: [[RETURN_ZERO]]: +// OGCG: store i32 0, ptr %[[RETVAL]] +// OGCG: br label %[[RETURN]] +// OGCG: [[RETURN]]: +// OGCG: %[[RET2:.*]] = load i32, ptr %[[RETVAL]] +// OGCG: ret i32 %[[RET2]] _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits