From cf978f126dd93068683737e96f41e26e599005d4 Mon Sep 17 00:00:00 2001 From: Morris Hafner Date: Wed, 15 Oct 2025 22:45:06 +0700 Subject: [PATCH 1/4] [CIR] Add support for ternary operator as lvalue Added support for ConditionalOperator, BinaryConditionalOperator and OpaqueValueExpr as lvalue. Implemented support for ternary operators with one branch being a throw expression. This required weakening the requirement that the true and false regions of the ternary operator must terminate with a `YieldOp`. Instead the true and false regions are now allowed to terminate with an `UnreachableOp` and no `YieldOp` gets emitted when the block throws. --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 179 ++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 6 + clang/lib/CIR/CodeGen/CIRGenFunction.h | 4 + clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 11 +- .../lib/CIR/Dialect/Transforms/FlattenCFG.cpp | 31 +- clang/test/CIR/CodeGen/opaque.cpp | 163 +++++++++ clang/test/CIR/CodeGen/ternary.cpp | 313 +++++++++++++++++- 7 files changed, 684 insertions(+), 23 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 4897c29b58a1f..90cb41b7f4a40 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -2394,6 +2394,185 @@ LValue CIRGenFunction::emitPredefinedLValue(const PredefinedExpr *e) { return emitStringLiteralLValue(sl, gvName); } +LValue CIRGenFunction::emitOpaqueValueLValue(const OpaqueValueExpr *e) { + assert(OpaqueValueMappingData::shouldBindAsLValue(e)); + return getOrCreateOpaqueLValueMapping(e); +} + +namespace { +// 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. +std::optional handleConditionalOperatorLValueSimpleCase( + CIRGenFunction &cgf, const AbstractConditionalOperator *e) { + const Expr *condExpr = e->getCond(); + llvm::APSInt condExprInt; + if (cgf.constantFoldsToSimpleInteger(condExpr, condExprInt)) { + bool condExprBool = condExprInt.getBoolValue(); + 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. + assert(!cir::MissingFeatures::incrementProfileCounter()); + assert(!cir::MissingFeatures::pgoUse()); + // If a throw expression we emit it and return an undefined lvalue + // because it can't be used. + if (auto *throwExpr = dyn_cast(live->IgnoreParens())) { + cgf.emitCXXThrowExpr(throwExpr); + // Return an undefined lvalue - the throw terminates execution + // so this value will never actually be used + mlir::Type elemTy = cgf.convertType(dead->getType()); + mlir::Type ptrTy = cir::PointerType::get(elemTy); + mlir::Value undefPtr = cgf.getBuilder().getNullValue( + ptrTy, cgf.getLoc(throwExpr->getSourceRange())); + return cgf.makeAddrLValue(Address(undefPtr, elemTy, CharUnits::One()), + dead->getType()); + } + 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 emitLValueOrThrowExpression(CIRGenFunction &cgf, + const Expr *operand) { + if (auto *throwExpr = dyn_cast(operand->IgnoreParens())) { + cgf.emitCXXThrowExpr(throwExpr); + return std::nullopt; + } + + return cgf.emitLValue(operand); +} +} // namespace + +// Create and generate the 3 blocks for a conditional operator. +// Leaves the 'current block' in the continuation basic block. +template +CIRGenFunction::ConditionalInfo +CIRGenFunction::emitConditionalBlocks(const AbstractConditionalOperator *e, + const FuncTy &branchGenFunc) { + ConditionalInfo info; + ConditionalEvaluation eval(*this); + mlir::Location loc = getLoc(e->getSourceRange()); + CIRGenBuilderTy &builder = getBuilder(); + + mlir::Value condV = emitOpOnBoolExpr(loc, e->getCond()); + SmallVector insertPoints{}; + mlir::Type yieldTy{}; + + auto patchVoidOrThrowSites = [&] { + if (insertPoints.empty()) + return; + // If both arms are void, so be it. + if (!yieldTy) + yieldTy = 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(yieldTy)) { + cir::YieldOp::create(builder, loc); + } else { // Block returns: set null yield value. + mlir::Value op0 = builder.getNullValue(yieldTy, loc); + cir::YieldOp::create(builder, loc, op0); + } + } + }; + + auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, + const Expr *expr, std::optional &resultLV) { + CIRGenFunction::LexicalScope lexScope{*this, loc, b.getInsertionBlock()}; + curLexScope->setAsTernary(); + + assert(!cir::MissingFeatures::incrementProfileCounter()); + eval.beginEvaluation(); + resultLV = branchGenFunc(*this, expr); + mlir::Value resultPtr = resultLV ? resultLV->getPointer() : mlir::Value(); + eval.endEvaluation(); + + if (resultPtr) { + yieldTy = resultPtr.getType(); + cir::YieldOp::create(b, loc, resultPtr); + } else { + // If LHS or RHS is a void expression we need + // to patch arms as to properly match yield types. + // If the current block's terminator is an UnreachableOp (from a throw), + // we don't need a yield + if (builder.getInsertionBlock()->mightHaveTerminator()) { + mlir::Operation *terminator = + builder.getInsertionBlock()->getTerminator(); + if (isa_and_nonnull(terminator)) + insertPoints.push_back(b.saveInsertionPoint()); + } + } + }; + + info.result = cir::TernaryOp::create( + builder, loc, condV, + /*trueBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + emitBranch(b, loc, e->getTrueExpr(), info.lhs); + }, + /*falseBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + emitBranch(b, loc, e->getFalseExpr(), info.rhs); + patchVoidOrThrowSites(); + }) + .getResult(); + + 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 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 with non-simple lvalue"); + return LValue(); + } + + 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; +} + /// An LValue is a candidate for having its loads and stores be made atomic if /// we are operating under /volatile:ms *and* the LValue itself is volatile and /// performing such an operation can be performed without a libcall. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index ba36cbeeb620e..25a46df406df4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -822,6 +822,10 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { std::string("l-value not implemented for '") + e->getStmtClassName() + "'"); return LValue(); + case Expr::ConditionalOperatorClass: + return emitConditionalOperatorLValue(cast(e)); + case Expr::BinaryConditionalOperatorClass: + return emitConditionalOperatorLValue(cast(e)); case Expr::ArraySubscriptExprClass: return emitArraySubscriptExpr(cast(e)); case Expr::UnaryOperatorClass: @@ -866,6 +870,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) { return emitCastLValue(cast(e)); case Expr::MaterializeTemporaryExprClass: return emitMaterializeTemporaryExpr(cast(e)); + case Expr::OpaqueValueExprClass: + return emitOpaqueValueLValue(cast(e)); case Expr::ChooseExprClass: return emitLValue(cast(e)->getChosenSubExpr()); } diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 3c36f5c697118..e265811e94fea 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -1518,6 +1518,10 @@ class CIRGenFunction : public CIRGenTypeCache { LValue emitMemberExpr(const MemberExpr *e); + LValue emitOpaqueValueLValue(const OpaqueValueExpr *e); + + LValue emitConditionalOperatorLValue(const AbstractConditionalOperator *expr); + /// Given an expression with a pointer type, emit the value and compute our /// best estimate of the alignment of the pointee. /// diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index f03b891079f63..8aede74af802c 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1978,13 +1978,18 @@ void cir::TernaryOp::build( result.addOperands(cond); OpBuilder::InsertionGuard guard(builder); Region *trueRegion = result.addRegion(); - Block *block = builder.createBlock(trueRegion); + Block *trueBlock = builder.createBlock(trueRegion); trueBuilder(builder, result.location); Region *falseRegion = result.addRegion(); - builder.createBlock(falseRegion); + Block *falseBlock = builder.createBlock(falseRegion); falseBuilder(builder, result.location); - auto yield = dyn_cast(block->getTerminator()); + // Get result type from whichever branch has a yield (the other may have + // unreachable from a throw expression) + YieldOp yield = dyn_cast_or_null(trueRegion->back().getTerminator()); + if (!yield) + yield = dyn_cast_or_null(falseRegion->back().getTerminator()); + assert((yield && yield.getNumOperands() <= 1) && "expected zero or one result type"); if (yield.getNumOperands() == 1) diff --git a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp index 26e5c0572f12e..8589a2eefa92f 100644 --- a/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp +++ b/clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp @@ -505,10 +505,19 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern { Block *trueBlock = &trueRegion.front(); mlir::Operation *trueTerminator = trueRegion.back().getTerminator(); rewriter.setInsertionPointToEnd(&trueRegion.back()); - auto trueYieldOp = dyn_cast(trueTerminator); - rewriter.replaceOpWithNewOp(trueYieldOp, trueYieldOp.getArgs(), - continueBlock); + // Handle both yield and unreachable terminators (throw expressions) + if (auto trueYieldOp = dyn_cast(trueTerminator)) { + rewriter.replaceOpWithNewOp(trueYieldOp, trueYieldOp.getArgs(), + continueBlock); + } else if (isa(trueTerminator)) { + // Terminator is unreachable (e.g., from throw), just keep it + } else { + trueTerminator->emitError("unexpected terminator in ternary true region, " + "expected yield or unreachable, got: ") + << trueTerminator->getName(); + return mlir::failure(); + } rewriter.inlineRegionBefore(trueRegion, continueBlock); Block *falseBlock = continueBlock; @@ -517,9 +526,19 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern { falseBlock = &falseRegion.front(); mlir::Operation *falseTerminator = falseRegion.back().getTerminator(); rewriter.setInsertionPointToEnd(&falseRegion.back()); - auto falseYieldOp = dyn_cast(falseTerminator); - rewriter.replaceOpWithNewOp(falseYieldOp, falseYieldOp.getArgs(), - continueBlock); + + // Handle both yield and unreachable terminators (throw expressions) + if (auto falseYieldOp = dyn_cast(falseTerminator)) { + rewriter.replaceOpWithNewOp( + falseYieldOp, falseYieldOp.getArgs(), continueBlock); + } else if (isa(falseTerminator)) { + // Terminator is unreachable (e.g., from throw), just keep it + } else { + falseTerminator->emitError("unexpected terminator in ternary false " + "region, expected yield or unreachable, got: ") + << falseTerminator->getName(); + return mlir::failure(); + } rewriter.inlineRegionBefore(falseRegion, continueBlock); rewriter.setInsertionPointToEnd(condBlock); diff --git a/clang/test/CIR/CodeGen/opaque.cpp b/clang/test/CIR/CodeGen/opaque.cpp index 028bfd9ef4cd0..eac0dfa3755ab 100644 --- a/clang/test/CIR/CodeGen/opaque.cpp +++ b/clang/test/CIR/CodeGen/opaque.cpp @@ -154,3 +154,166 @@ void foo3() { // OGCG: [[COND_END]]: // OGCG: %[[RESULT:.*]] = phi i32 [ %[[TMP_A]], %[[COND_TRUE]] ], [ %[[TMP_B]], %[[COND_FALSE]] ] // OGCG: store i32 %[[RESULT]], ptr %[[C_ADDR]], align 4 + +void test_gnu_binary_lvalue_assign() { + int a = 5; + int b = 10; + (a ?: b) = 42; +} + +// CIR-LABEL: cir.func{{.*}} @_Z29test_gnu_binary_lvalue_assignv( +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR: %[[A_VAL:.*]] = cir.load{{.*}} %[[A]] : !cir.ptr, !s32i +// CIR: %[[A_BOOL:.*]] = cir.cast int_to_bool %[[A_VAL]] : !s32i -> !cir.bool +// CIR: %[[TERNARY_PTR:.*]] = cir.ternary(%[[A_BOOL]], true { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }, false { +// CIR: cir.yield %[[B]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR: cir.store{{.*}} %{{.*}}, %[[TERNARY_PTR]] : !s32i, !cir.ptr + +// LLVM-LABEL: define{{.*}} void @_Z29test_gnu_binary_lvalue_assignv( +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM: %[[A_VAL:.*]] = load i32, ptr %[[A]] +// LLVM: %[[COND:.*]] = icmp ne i32 %[[A_VAL]], 0 +// LLVM: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PHI_PTR:.*]] = phi ptr [ %[[B]], %[[FALSE_BB]] ], [ %[[A]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT_BB:.*]] +// LLVM: [[CONT_BB]]: +// LLVM: store i32 42, ptr %[[PHI_PTR]] + +// OGCG-LABEL: define{{.*}} void @_Z29test_gnu_binary_lvalue_assignv( +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: %[[B:.*]] = alloca i32 +// OGCG: %[[A_VAL:.*]] = load i32, ptr %[[A]] +// OGCG: %[[COND:.*]] = icmp ne i32 %[[A_VAL]], 0 +// OGCG: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PHI_PTR:.*]] = phi ptr [ %[[A]], %[[TRUE_BB]] ], [ %[[B]], %[[FALSE_BB]] ] +// OGCG: store i32 42, ptr %[[PHI_PTR]] + +void test_gnu_binary_lvalue_compound() { + int a = 7; + int b = 14; + (a ?: b) += 5; +} + +// CIR-LABEL: cir.func{{.*}} @_Z31test_gnu_binary_lvalue_compoundv( +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR: %[[A_VAL:.*]] = cir.load{{.*}} %[[A]] : !cir.ptr, !s32i +// CIR: %[[A_BOOL:.*]] = cir.cast int_to_bool %[[A_VAL]] : !s32i -> !cir.bool +// CIR: %[[LVAL_PTR:.*]] = cir.ternary(%[[A_BOOL]], true { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }, false { +// CIR: cir.yield %[[B]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR: %[[OLD_VAL:.*]] = cir.load{{.*}} %[[LVAL_PTR]] +// CIR: %[[NEW_VAL:.*]] = cir.binop(add, %[[OLD_VAL]], %{{.*}}) +// CIR: cir.store{{.*}} %[[NEW_VAL]], %[[LVAL_PTR]] + +// LLVM-LABEL: define{{.*}} void @_Z31test_gnu_binary_lvalue_compoundv( +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM: %[[A_VAL:.*]] = load i32, ptr %[[A]] +// LLVM: %[[COND:.*]] = icmp ne i32 %[[A_VAL]], 0 +// LLVM: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PTR:.*]] = phi ptr [ %[[B]], %[[FALSE_BB]] ], [ %[[A]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT:.*]] +// LLVM: [[CONT]]: +// LLVM: %[[OLD:.*]] = load i32, ptr %[[PTR]] +// LLVM: %[[NEW:.*]] = add{{.*}} i32 %[[OLD]], 5 +// LLVM: store i32 %[[NEW]], ptr %[[PTR]] + +// OGCG-LABEL: define{{.*}} void @_Z31test_gnu_binary_lvalue_compoundv( +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: %[[B:.*]] = alloca i32 +// OGCG: %[[A_VAL:.*]] = load i32, ptr %[[A]] +// OGCG: %[[COND:.*]] = icmp ne i32 %[[A_VAL]], 0 +// OGCG: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PTR:.*]] = phi ptr [ %[[A]], %[[TRUE_BB]] ], [ %[[B]], %[[FALSE_BB]] ] +// OGCG: %[[OLD:.*]] = load i32, ptr %[[PTR]] +// OGCG: %[[NEW:.*]] = add{{.*}} i32 %[[OLD]], 5 +// OGCG: store i32 %[[NEW]], ptr %[[PTR]] + +void test_gnu_binary_lvalue_ptr() { + int x = 1, y = 2; + int *p = &x; + int *q = nullptr; + *(p ?: q) = 99; +} + +// CIR-LABEL: cir.func{{.*}} @_Z26test_gnu_binary_lvalue_ptrv( +// CIR: %[[X:.*]] = cir.alloca !s32i, !cir.ptr, ["x", init] +// CIR: %[[Y:.*]] = cir.alloca !s32i, !cir.ptr, ["y", init] +// CIR: %[[P:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["p", init] +// CIR: %[[Q:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["q", init] +// CIR: %[[P_VAL:.*]] = cir.load{{.*}} %[[P]] +// CIR: %[[P_BOOL:.*]] = cir.cast ptr_to_bool %[[P_VAL]] +// CIR: %[[PTR_RESULT:.*]] = cir.ternary(%[[P_BOOL]], true { +// CIR: %[[P_LOAD:.*]] = cir.load{{.*}} %[[P]] +// CIR: cir.yield %[[P_LOAD]] : !cir.ptr +// CIR: }, false { +// CIR: %[[Q_LOAD:.*]] = cir.load{{.*}} %[[Q]] +// CIR: cir.yield %[[Q_LOAD]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR: cir.store{{.*}} %{{.*}}, %[[PTR_RESULT]] + +// LLVM-LABEL: define{{.*}} void @_Z26test_gnu_binary_lvalue_ptrv( +// LLVM: %[[X:.*]] = alloca i32 +// LLVM: %[[Y:.*]] = alloca i32 +// LLVM: %[[P:.*]] = alloca ptr +// LLVM: %[[Q:.*]] = alloca ptr +// LLVM: %[[P_VAL:.*]] = load ptr, ptr %[[P]] +// LLVM: %[[COND:.*]] = icmp ne ptr %[[P_VAL]], null +// LLVM: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: %[[P_LOAD:.*]] = load ptr, ptr %[[P]] +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: %[[Q_LOAD:.*]] = load ptr, ptr %[[Q]] +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PHI:.*]] = phi ptr [ %[[Q_LOAD]], %[[FALSE_BB]] ], [ %[[P_LOAD]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT:.*]] +// LLVM: [[CONT]]: +// LLVM: store i32 99, ptr %[[PHI]] + +// OGCG-LABEL: define{{.*}} void @_Z26test_gnu_binary_lvalue_ptrv( +// OGCG: %[[X:.*]] = alloca i32 +// OGCG: %[[Y:.*]] = alloca i32 +// OGCG: %[[P:.*]] = alloca ptr +// OGCG: %[[Q:.*]] = alloca ptr +// OGCG: %[[P_VAL:.*]] = load ptr, ptr %[[P]] +// OGCG: %[[COND:.*]] = icmp ne ptr %[[P_VAL]], null +// OGCG: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: %[[P_LOAD:.*]] = load ptr, ptr %[[P]] +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: %[[Q_LOAD:.*]] = load ptr, ptr %[[Q]] +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PHI:.*]] = phi ptr [ %[[P_LOAD]], %[[TRUE_BB]] ], [ %[[Q_LOAD]], %[[FALSE_BB]] ] +// OGCG: store i32 99, ptr %[[PHI]] diff --git a/clang/test/CIR/CodeGen/ternary.cpp b/clang/test/CIR/CodeGen/ternary.cpp index e7b72708e26a4..8dba31127d684 100644 --- a/clang/test/CIR/CodeGen/ternary.cpp +++ b/clang/test/CIR/CodeGen/ternary.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fexceptions -fcxx-exceptions -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: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fexceptions -fcxx-exceptions -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: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm %s -o %t.ll // RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG int x(int y) { @@ -10,11 +10,11 @@ int x(int y) { } // CIR-LABEL: cir.func{{.*}} @_Z1xi( -// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}) -> !s32i -// CIR: [[Y:%.+]] = cir.alloca !s32i, !cir.ptr, ["y", init] {alignment = 4 : i64} -// CIR: [[RETVAL:%.+]] = cir.alloca !s32i, !cir.ptr, ["__retval"] {alignment = 4 : i64} +// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}) -> !s32i{{.*}}{ +// CIR: [[Y:%.+]] = cir.alloca !s32i, !cir.ptr, ["y", init] +// CIR: [[RETVAL:%.+]] = cir.alloca !s32i, !cir.ptr, ["__retval"] // CIR: cir.store %[[ARG0]], [[Y]] : !s32i, !cir.ptr -// CIR: [[YVAL:%.+]] = cir.load align(4) [[Y]] : !cir.ptr, !s32i +// CIR: [[YVAL:%.+]] = cir.load{{.*}} [[Y]] : !cir.ptr, !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 @@ -52,21 +52,21 @@ int foo(int a, int b) { } // CIR-LABEL: cir.func{{.*}} @_Z3fooii( -// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}, %[[ARG1:.*]]: !s32i {{.*}}) -> !s32i -// CIR: [[A:%.+]] = cir.alloca !s32i, !cir.ptr, ["a", init] {alignment = 4 : i64} -// CIR: [[B:%.+]] = cir.alloca !s32i, !cir.ptr, ["b", init] {alignment = 4 : i64} -// CIR: [[RETVAL:%.+]] = cir.alloca !s32i, !cir.ptr, ["__retval"] {alignment = 4 : i64} +// CIR-SAME: %[[ARG0:.*]]: !s32i {{.*}}, %[[ARG1:.*]]: !s32i {{.*}}) -> !s32i{{.*}}{ +// CIR: [[A:%.+]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: [[B:%.+]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR: [[RETVAL:%.+]] = cir.alloca !s32i, !cir.ptr, ["__retval"] // CIR: cir.store %[[ARG0]], [[A]] : !s32i, !cir.ptr // CIR: cir.store %[[ARG1]], [[B]] : !s32i, !cir.ptr // CIR: cir.scope { -// CIR: [[ALOAD:%.+]] = cir.load align(4) [[A]] : !cir.ptr, !s32i -// CIR: [[BLOAD:%.+]] = cir.load align(4) [[B]] : !cir.ptr, !s32i +// CIR: [[ALOAD:%.+]] = cir.load{{.*}} [[A]] : !cir.ptr, !s32i +// CIR: [[BLOAD:%.+]] = cir.load{{.*}} [[B]] : !cir.ptr, !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 +// CIR: [[ALOAD2:%.+]] = cir.load{{.*}} [[A]] : !cir.ptr, !s32i // CIR: cir.yield [[ALOAD2]] : !s32i // CIR: }) : (!cir.bool) -> !s32i // CIR: [[CAST:%.+]] = cir.cast int_to_bool [[TERNARY_RES]] : !s32i -> !cir.bool @@ -145,3 +145,288 @@ int foo(int a, int b) { // OGCG: [[RETURN]]: // OGCG: %[[RET2:.*]] = load i32, ptr %[[RETVAL]] // OGCG: ret i32 %[[RET2]] + +void test_cond_lvalue_assign(bool flag) { + int a = 1; + int b = 2; + (flag ? a : b) = 42; +} + +// CIR-LABEL: cir.func{{.*}} @_Z23test_cond_lvalue_assignb( +// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] +// CIR: %[[TERNARY_PTR:.*]] = cir.ternary(%[[FLAG_VAL]], true { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }, false { +// CIR: cir.yield %[[B]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR: cir.store{{.*}} %{{.*}}, %[[TERNARY_PTR]] : !s32i, !cir.ptr + +// LLVM-LABEL: define{{.*}} void @_Z23test_cond_lvalue_assignb( +// LLVM: %[[FLAG:.*]] = alloca i8 +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM: %[[FLAG_VAL:.*]] = load i8, ptr %[[FLAG]] +// LLVM: %[[COND:.*]] = trunc i8 %[[FLAG_VAL]] to i1 +// LLVM: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PHI_PTR:.*]] = phi ptr [ %[[B]], %[[FALSE_BB]] ], [ %[[A]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT_BB:.*]] +// LLVM: [[CONT_BB]]: +// LLVM: store i32 42, ptr %[[PHI_PTR]] + +// OGCG-LABEL: define{{.*}} void @_Z23test_cond_lvalue_assignb( +// OGCG: %[[FLAG:.*]] = alloca i8 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: %[[B:.*]] = alloca i32 +// OGCG: %[[FLAG_VAL:.*]] = load i8, ptr %[[FLAG]] +// OGCG: %[[COND:.*]] = trunc i8 %[[FLAG_VAL]] to i1 +// OGCG: br i1 %[[COND]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PHI_PTR:.*]] = phi ptr [ %[[A]], %[[TRUE_BB]] ], [ %[[B]], %[[FALSE_BB]] ] +// OGCG: store i32 42, ptr %[[PHI_PTR]] + +int& test_cond_lvalue_ref(bool cond, int x, int y) { + return cond ? x : y; +} + +// CIR-LABEL: cir.func{{.*}} @_Z20test_cond_lvalue_refbii( +// CIR: %[[COND:.*]] = cir.alloca !cir.bool, !cir.ptr, ["cond", init] +// CIR: %[[X:.*]] = cir.alloca !s32i, !cir.ptr, ["x", init] +// CIR: %[[Y:.*]] = cir.alloca !s32i, !cir.ptr, ["y", init] +// CIR: %[[COND_VAL:.*]] = cir.load{{.*}} %[[COND]] +// CIR: %[[REF_PTR:.*]] = cir.ternary(%[[COND_VAL]], true { +// CIR: cir.yield %[[X]] : !cir.ptr +// CIR: }, false { +// CIR: cir.yield %[[Y]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR: cir.store %[[REF_PTR]] +// CIR: %[[RET_VAL:.*]] = cir.load +// CIR: cir.return %[[RET_VAL]] : !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z20test_cond_lvalue_refbii( +// LLVM: %[[COND:.*]] = alloca i8 +// LLVM: %[[X:.*]] = alloca i32 +// LLVM: %[[Y:.*]] = alloca i32 +// LLVM: %[[COND_VAL:.*]] = load i8, ptr %[[COND]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[COND_VAL]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PHI:.*]] = phi ptr [ %[[Y]], %[[FALSE_BB]] ], [ %[[X]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT:.*]] +// LLVM: [[CONT]]: +// LLVM: store ptr %[[PHI]] +// LLVM: %[[RET:.*]] = load ptr +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z20test_cond_lvalue_refbii( +// OGCG: %[[COND:.*]] = alloca i8 +// OGCG: %[[X:.*]] = alloca i32 +// OGCG: %[[Y:.*]] = alloca i32 +// OGCG: %[[COND_VAL:.*]] = load i8, ptr %[[COND]] +// OGCG: %[[BOOL:.*]] = trunc i8 %[[COND_VAL]] to i1 +// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PHI:.*]] = phi ptr [ %[[X]], %[[TRUE_BB]] ], [ %[[Y]], %[[FALSE_BB]] ] +// OGCG: ret ptr %[[PHI]] + +// Test ConditionalOperator as lvalue - compound assignment +void test_cond_lvalue_compound(bool flag) { + int a = 5; + int b = 10; + (flag ? a : b) += 3; +} + +// CIR-LABEL: cir.func{{.*}} @_Z25test_cond_lvalue_compoundb( +// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] +// CIR: %[[LVAL_PTR:.*]] = cir.ternary(%[[FLAG_VAL]], true { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }, false { +// CIR: cir.yield %[[B]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR: %[[OLD_VAL:.*]] = cir.load{{.*}} %[[LVAL_PTR]] +// CIR: %[[NEW_VAL:.*]] = cir.binop(add, %[[OLD_VAL]], %{{.*}}) +// CIR: cir.store{{.*}} %[[NEW_VAL]], %[[LVAL_PTR]] + +// LLVM-LABEL: define{{.*}} void @_Z25test_cond_lvalue_compoundb( +// LLVM: %[[FLAG:.*]] = alloca i8 +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM: %[[FLAG_VAL:.*]] = load i8, ptr %[[FLAG]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[FLAG_VAL]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[MERGE_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[MERGE_BB]] +// LLVM: [[MERGE_BB]]: +// LLVM: %[[PTR:.*]] = phi ptr [ %[[B]], %[[FALSE_BB]] ], [ %[[A]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT:.*]] +// LLVM: [[CONT]]: +// LLVM: %[[OLD:.*]] = load i32, ptr %[[PTR]] +// LLVM: %[[NEW:.*]] = add{{.*}} i32 %[[OLD]], 3 +// LLVM: store i32 %[[NEW]], ptr %[[PTR]] + +// OGCG-LABEL: define{{.*}} void @_Z25test_cond_lvalue_compoundb( +// OGCG: %[[FLAG:.*]] = alloca i8 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: %[[B:.*]] = alloca i32 +// OGCG: %[[FLAG_VAL:.*]] = load i8, ptr %[[FLAG]] +// OGCG: %[[BOOL:.*]] = trunc i8 %[[FLAG_VAL]] to i1 +// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[MERGE_BB:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[MERGE_BB]] +// OGCG: [[MERGE_BB]]: +// OGCG: %[[PTR:.*]] = phi ptr [ %[[A]], %[[TRUE_BB]] ], [ %[[B]], %[[FALSE_BB]] ] +// OGCG: %[[OLD:.*]] = load i32, ptr %[[PTR]] +// OGCG: %[[NEW:.*]] = add{{.*}} i32 %[[OLD]], 3 +// OGCG: store i32 %[[NEW]], ptr %[[PTR]] + +const int& test_cond_throw_false(bool flag) { + const int a = 10; + return flag ? a : throw 0; +} + +// CIR-LABEL: cir.func{{.*}} @_Z21test_cond_throw_falseb( +// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] +// CIR: %[[TEN:.*]] = cir.const #cir.int<10> : !s32i +// CIR: cir.store{{.*}} %[[TEN]], %[[A]] : !s32i, !cir.ptr +// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] : !cir.ptr, !cir.bool +// CIR: %[[RESULT:.*]] = cir.ternary(%[[FLAG_VAL]], true { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }, false { +// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i +// CIR: cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr +// CIR: cir.throw %[[EXCEPTION]] : !cir.ptr, @_ZTIi +// CIR: cir.unreachable +// CIR: }) : (!cir.bool) -> !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z21test_cond_throw_falseb( +// LLVM: %[[FLAG_ALLOCA:.*]] = alloca i8 +// LLVM: %[[RET_ALLOCA:.*]] = alloca ptr +// LLVM: %[[A_ALLOCA:.*]] = alloca i32 +// LLVM: %[[ZEXT:.*]] = zext i1 %{{.*}} to i8 +// LLVM: store i8 %[[ZEXT]], ptr %[[FLAG_ALLOCA]] +// LLVM: store i32 10, ptr %[[A_ALLOCA]] +// LLVM: %[[LOAD:.*]] = load i8, ptr %[[FLAG_ALLOCA]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[LOAD]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[PHI_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: %[[EXC:.*]] = call{{.*}} ptr @__cxa_allocate_exception +// LLVM: store i32 0, ptr %[[EXC]] +// LLVM: call void @__cxa_throw(ptr %[[EXC]], ptr @_ZTIi +// LLVM: unreachable +// LLVM: [[PHI_BB]]: +// LLVM: %[[PHI:.*]] = phi ptr [ %[[A_ALLOCA]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT_BB:.*]] +// LLVM: [[CONT_BB]]: +// LLVM: store ptr %[[A_ALLOCA]], ptr %[[RET_ALLOCA]] +// LLVM: %[[RET:.*]] = load ptr, ptr %[[RET_ALLOCA]] +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z21test_cond_throw_falseb( +// OGCG: %{{.*}} = alloca i8 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: store i32 10, ptr %[[A]] +// OGCG: %{{.*}} = load i8, ptr %{{.*}} +// OGCG: %[[BOOL:.*]] = trunc i8 %{{.*}} to i1 +// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[END:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: %{{.*}} = call{{.*}} ptr @__cxa_allocate_exception +// OGCG: store i32 0, ptr %{{.*}} +// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi +// OGCG: unreachable +// OGCG: [[END]]: +// OGCG: ret ptr %[[A]] + +const int& test_cond_throw_true(bool flag) { + const int a = 10; + return flag ? throw 0 : a; +} + +// CIR-LABEL: cir.func{{.*}} @_Z20test_cond_throw_trueb( +// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] +// CIR: %[[TEN:.*]] = cir.const #cir.int<10> : !s32i +// CIR: cir.store{{.*}} %[[TEN]], %[[A]] : !s32i, !cir.ptr +// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] : !cir.ptr, !cir.bool +// CIR: %[[RESULT:.*]] = cir.ternary(%[[FLAG_VAL]], true { +// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i +// CIR: cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr +// CIR: cir.throw %[[EXCEPTION]] : !cir.ptr, @_ZTIi +// CIR: cir.unreachable +// CIR: }, false { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z20test_cond_throw_trueb( +// LLVM: %[[FLAG_ALLOCA:.*]] = alloca i8 +// LLVM: %[[RET_ALLOCA:.*]] = alloca ptr +// LLVM: %[[A_ALLOCA:.*]] = alloca i32 +// LLVM: %[[ZEXT:.*]] = zext i1 %{{.*}} to i8 +// LLVM: store i8 %[[ZEXT]], ptr %[[FLAG_ALLOCA]] +// LLVM: store i32 10, ptr %[[A_ALLOCA]] +// LLVM: %[[LOAD:.*]] = load i8, ptr %[[FLAG_ALLOCA]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[LOAD]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: %[[EXC:.*]] = call{{.*}} ptr @__cxa_allocate_exception +// LLVM: store i32 0, ptr %[[EXC]] +// LLVM: call void @__cxa_throw(ptr %[[EXC]], ptr @_ZTIi +// LLVM: unreachable +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[PHI_BB:.*]] +// LLVM: [[PHI_BB]]: +// LLVM: %[[PHI:.*]] = phi ptr [ %[[A_ALLOCA]], %[[FALSE_BB]] ] +// LLVM: br label %[[CONT_BB:.*]] +// LLVM: [[CONT_BB]]: +// LLVM: store ptr %[[A_ALLOCA]], ptr %[[RET_ALLOCA]] +// LLVM: %[[RET:.*]] = load ptr, ptr %[[RET_ALLOCA]] +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z20test_cond_throw_trueb( +// OGCG: %{{.*}} = alloca i8 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: store i32 10, ptr %[[A]] +// OGCG: %{{.*}} = load i8, ptr %{{.*}} +// OGCG: %[[BOOL:.*]] = trunc i8 %{{.*}} to i1 +// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: %{{.*}} = call{{.*}} ptr @__cxa_allocate_exception +// OGCG: store i32 0, ptr %{{.*}} +// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi +// OGCG: unreachable +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[END:.*]] +// OGCG: [[END]]: +// OGCG: ret ptr %[[A]] From bb9039f4d0566fa3f1562348dbd764dcb6a07991 Mon Sep 17 00:00:00 2001 From: Morris Hafner Date: Wed, 15 Oct 2025 22:58:24 +0700 Subject: [PATCH 2/4] clang-format --- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 8aede74af802c..2b41cf9bf4908 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1989,7 +1989,7 @@ void cir::TernaryOp::build( YieldOp yield = dyn_cast_or_null(trueRegion->back().getTerminator()); if (!yield) yield = dyn_cast_or_null(falseRegion->back().getTerminator()); - + assert((yield && yield.getNumOperands() <= 1) && "expected zero or one result type"); if (yield.getNumOperands() == 1) From 0a9cceea934d89a58efcc5cf8085fe5e368db19d Mon Sep 17 00:00:00 2001 From: Morris Hafner Date: Mon, 20 Oct 2025 20:54:10 +0700 Subject: [PATCH 3/4] Address review feedback --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 95 ++++++----- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 4 +- clang/test/CIR/CodeGen/ternary-throw.cpp | 197 +++++++++++++++++++++++ clang/test/CIR/CodeGen/ternary.cpp | 160 +++++------------- 4 files changed, 287 insertions(+), 169 deletions(-) create mode 100644 clang/test/CIR/CodeGen/ternary-throw.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index 90cb41b7f4a40..0d364e1bb1595 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -2405,34 +2405,34 @@ namespace { std::optional handleConditionalOperatorLValueSimpleCase( CIRGenFunction &cgf, const AbstractConditionalOperator *e) { const Expr *condExpr = e->getCond(); - llvm::APSInt condExprInt; - if (cgf.constantFoldsToSimpleInteger(condExpr, condExprInt)) { - bool condExprBool = condExprInt.getBoolValue(); - 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. - assert(!cir::MissingFeatures::incrementProfileCounter()); - assert(!cir::MissingFeatures::pgoUse()); - // If a throw expression we emit it and return an undefined lvalue - // because it can't be used. - if (auto *throwExpr = dyn_cast(live->IgnoreParens())) { - cgf.emitCXXThrowExpr(throwExpr); - // Return an undefined lvalue - the throw terminates execution - // so this value will never actually be used - mlir::Type elemTy = cgf.convertType(dead->getType()); - mlir::Type ptrTy = cir::PointerType::get(elemTy); - mlir::Value undefPtr = cgf.getBuilder().getNullValue( - ptrTy, cgf.getLoc(throwExpr->getSourceRange())); - return cgf.makeAddrLValue(Address(undefPtr, elemTy, CharUnits::One()), - dead->getType()); - } - return cgf.emitLValue(live); - } + llvm::APSInt condExprVal; + if (!cgf.constantFoldsToSimpleInteger(condExpr, condExprVal)) + return std::nullopt; + + const Expr *live = e->getTrueExpr(), *dead = e->getFalseExpr(); + if (!condExprVal.getBoolValue()) + std::swap(live, dead); + + if (cgf.containsLabel(dead)) + return std::nullopt; + + // If the true case is live, we need to track its region. + assert(!cir::MissingFeatures::incrementProfileCounter()); + assert(!cir::MissingFeatures::pgoUse()); + // If a throw expression we emit it and return an undefined lvalue + // because it can't be used. + if (auto *throwExpr = dyn_cast(live->IgnoreParens())) { + cgf.emitCXXThrowExpr(throwExpr); + // Return an undefined lvalue - the throw terminates execution + // so this value will never actually be used + mlir::Type elemTy = cgf.convertType(dead->getType()); + mlir::Value undefPtr = + cgf.getBuilder().getNullPtr(cgf.getBuilder().getPointerTo(elemTy), + cgf.getLoc(throwExpr->getSourceRange())); + return cgf.makeAddrLValue(Address(undefPtr, elemTy, CharUnits::One()), + dead->getType()); } - return std::nullopt; + return cgf.emitLValue(live); } /// Emit the operand of a glvalue conditional operator. This is either a glvalue @@ -2464,28 +2464,6 @@ CIRGenFunction::emitConditionalBlocks(const AbstractConditionalOperator *e, SmallVector insertPoints{}; mlir::Type yieldTy{}; - auto patchVoidOrThrowSites = [&] { - if (insertPoints.empty()) - return; - // If both arms are void, so be it. - if (!yieldTy) - yieldTy = 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(yieldTy)) { - cir::YieldOp::create(builder, loc); - } else { // Block returns: set null yield value. - mlir::Value op0 = builder.getNullValue(yieldTy, loc); - cir::YieldOp::create(builder, loc, op0); - } - } - }; - auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc, const Expr *expr, std::optional &resultLV) { CIRGenFunction::LexicalScope lexScope{*this, loc, b.getInsertionBlock()}; @@ -2523,10 +2501,27 @@ CIRGenFunction::emitConditionalBlocks(const AbstractConditionalOperator *e, /*falseBuilder=*/ [&](mlir::OpBuilder &b, mlir::Location loc) { emitBranch(b, loc, e->getFalseExpr(), info.rhs); - patchVoidOrThrowSites(); }) .getResult(); + // If both arms are void, so be it. + if (!yieldTy) + yieldTy = 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 (!yieldTy) { + cir::YieldOp::create(builder, loc); + } else { // Block returns: set null yield value. + mlir::Value op0 = builder.getNullValue(yieldTy, loc); + cir::YieldOp::create(builder, loc, op0); + } + } + return info; } diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 2b41cf9bf4908..cf0905dd19387 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1978,10 +1978,10 @@ void cir::TernaryOp::build( result.addOperands(cond); OpBuilder::InsertionGuard guard(builder); Region *trueRegion = result.addRegion(); - Block *trueBlock = builder.createBlock(trueRegion); + builder.createBlock(trueRegion); trueBuilder(builder, result.location); Region *falseRegion = result.addRegion(); - Block *falseBlock = builder.createBlock(falseRegion); + builder.createBlock(falseRegion); falseBuilder(builder, result.location); // Get result type from whichever branch has a yield (the other may have diff --git a/clang/test/CIR/CodeGen/ternary-throw.cpp b/clang/test/CIR/CodeGen/ternary-throw.cpp new file mode 100644 index 0000000000000..fb8897fa18a74 --- /dev/null +++ b/clang/test/CIR/CodeGen/ternary-throw.cpp @@ -0,0 +1,197 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fexceptions -fcxx-exceptions -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 -fexceptions -fcxx-exceptions -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 -fexceptions -fcxx-exceptions -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG + +const int& test_cond_throw_false(bool flag) { + const int a = 10; + return flag ? a : throw 0; +} + +// CIR-LABEL: cir.func{{.*}} @_Z21test_cond_throw_falseb( +// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] +// CIR: %[[TEN:.*]] = cir.const #cir.int<10> : !s32i +// CIR: cir.store{{.*}} %[[TEN]], %[[A]] : !s32i, !cir.ptr +// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] : !cir.ptr, !cir.bool +// CIR: %[[RESULT:.*]] = cir.ternary(%[[FLAG_VAL]], true { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }, false { +// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i +// CIR: cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr +// CIR: cir.throw %[[EXCEPTION]] : !cir.ptr, @_ZTIi +// CIR: cir.unreachable +// CIR: }) : (!cir.bool) -> !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z21test_cond_throw_falseb( +// LLVM: %[[FLAG_ALLOCA:.*]] = alloca i8 +// LLVM: %[[RET_ALLOCA:.*]] = alloca ptr +// LLVM: %[[A_ALLOCA:.*]] = alloca i32 +// LLVM: %[[ZEXT:.*]] = zext i1 %{{.*}} to i8 +// LLVM: store i8 %[[ZEXT]], ptr %[[FLAG_ALLOCA]] +// LLVM: store i32 10, ptr %[[A_ALLOCA]] +// LLVM: %[[LOAD:.*]] = load i8, ptr %[[FLAG_ALLOCA]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[LOAD]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: br label %[[PHI_BB:.*]] +// LLVM: [[FALSE_BB]]: +// LLVM: %[[EXC:.*]] = call{{.*}} ptr @__cxa_allocate_exception +// LLVM: store i32 0, ptr %[[EXC]] +// LLVM: call void @__cxa_throw(ptr %[[EXC]], ptr @_ZTIi +// LLVM: unreachable +// LLVM: [[PHI_BB]]: +// LLVM: %[[PHI:.*]] = phi ptr [ %[[A_ALLOCA]], %[[TRUE_BB]] ] +// LLVM: br label %[[CONT_BB:.*]] +// LLVM: [[CONT_BB]]: +// LLVM: store ptr %[[A_ALLOCA]], ptr %[[RET_ALLOCA]] +// LLVM: %[[RET:.*]] = load ptr, ptr %[[RET_ALLOCA]] +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z21test_cond_throw_falseb( +// OGCG: %{{.*}} = alloca i8 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: store i32 10, ptr %[[A]] +// OGCG: %{{.*}} = load i8, ptr %{{.*}} +// OGCG: %[[BOOL:.*]] = trunc i8 %{{.*}} to i1 +// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: br label %[[END:.*]] +// OGCG: [[FALSE_BB]]: +// OGCG: %{{.*}} = call{{.*}} ptr @__cxa_allocate_exception +// OGCG: store i32 0, ptr %{{.*}} +// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi +// OGCG: unreachable +// OGCG: [[END]]: +// OGCG: ret ptr %[[A]] + +const int& test_cond_throw_true(bool flag) { + const int a = 10; + return flag ? throw 0 : a; +} + +// CIR-LABEL: cir.func{{.*}} @_Z20test_cond_throw_trueb( +// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] +// CIR: %[[TEN:.*]] = cir.const #cir.int<10> : !s32i +// CIR: cir.store{{.*}} %[[TEN]], %[[A]] : !s32i, !cir.ptr +// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] : !cir.ptr, !cir.bool +// CIR: %[[RESULT:.*]] = cir.ternary(%[[FLAG_VAL]], true { +// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i +// CIR: cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr +// CIR: cir.throw %[[EXCEPTION]] : !cir.ptr, @_ZTIi +// CIR: cir.unreachable +// CIR: }, false { +// CIR: cir.yield %[[A]] : !cir.ptr +// CIR: }) : (!cir.bool) -> !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z20test_cond_throw_trueb( +// LLVM: %[[FLAG_ALLOCA:.*]] = alloca i8 +// LLVM: %[[RET_ALLOCA:.*]] = alloca ptr +// LLVM: %[[A_ALLOCA:.*]] = alloca i32 +// LLVM: %[[ZEXT:.*]] = zext i1 %{{.*}} to i8 +// LLVM: store i8 %[[ZEXT]], ptr %[[FLAG_ALLOCA]] +// LLVM: store i32 10, ptr %[[A_ALLOCA]] +// LLVM: %[[LOAD:.*]] = load i8, ptr %[[FLAG_ALLOCA]] +// LLVM: %[[BOOL:.*]] = trunc i8 %[[LOAD]] to i1 +// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// LLVM: [[TRUE_BB]]: +// LLVM: %[[EXC:.*]] = call{{.*}} ptr @__cxa_allocate_exception +// LLVM: store i32 0, ptr %[[EXC]] +// LLVM: call void @__cxa_throw(ptr %[[EXC]], ptr @_ZTIi +// LLVM: unreachable +// LLVM: [[FALSE_BB]]: +// LLVM: br label %[[PHI_BB:.*]] +// LLVM: [[PHI_BB]]: +// LLVM: %[[PHI:.*]] = phi ptr [ %[[A_ALLOCA]], %[[FALSE_BB]] ] +// LLVM: br label %[[CONT_BB:.*]] +// LLVM: [[CONT_BB]]: +// LLVM: store ptr %[[A_ALLOCA]], ptr %[[RET_ALLOCA]] +// LLVM: %[[RET:.*]] = load ptr, ptr %[[RET_ALLOCA]] +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z20test_cond_throw_trueb( +// OGCG: %{{.*}} = alloca i8 +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: store i32 10, ptr %[[A]] +// OGCG: %{{.*}} = load i8, ptr %{{.*}} +// OGCG: %[[BOOL:.*]] = trunc i8 %{{.*}} to i1 +// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] +// OGCG: [[TRUE_BB]]: +// OGCG: %{{.*}} = call{{.*}} ptr @__cxa_allocate_exception +// OGCG: store i32 0, ptr %{{.*}} +// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi +// OGCG: unreachable +// OGCG: [[FALSE_BB]]: +// OGCG: br label %[[END:.*]] +// OGCG: [[END]]: +// OGCG: ret ptr %[[A]] + +// Test constant folding with throw - compile-time true condition, dead throw in false branch +const int& test_cond_const_true_throw_false() { + const int a = 20; + return true ? a : throw 0; +} + +// CIR-LABEL: cir.func{{.*}} @_Z32test_cond_const_true_throw_falsev( +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] +// CIR: %[[TWENTY:.*]] = cir.const #cir.int<20> : !s32i +// CIR: cir.store{{.*}} %[[TWENTY]], %[[A]] : !s32i, !cir.ptr +// CIR-NOT: cir.ternary +// CIR-NOT: cir.throw +// CIR: cir.store %[[A]] +// CIR: %[[RET:.*]] = cir.load +// CIR: cir.return %[[RET]] : !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z32test_cond_const_true_throw_falsev( +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: store i32 20, ptr %[[A]] +// LLVM-NOT: br i1 +// LLVM-NOT: __cxa_throw +// LLVM: store ptr %[[A]] +// LLVM: %[[RET:.*]] = load ptr +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z32test_cond_const_true_throw_falsev( +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: store i32 20, ptr %[[A]] +// OGCG-NOT: br i1 +// OGCG-NOT: __cxa_throw +// OGCG: ret ptr %[[A]] + +// Test constant folding with throw - compile-time false condition, dead throw in true branch +const int& test_cond_const_false_throw_true() { + const int a = 30; + return false ? throw 0 : a; +} + +// CIR-LABEL: cir.func{{.*}} @_Z32test_cond_const_false_throw_truev( +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] +// CIR: %[[THIRTY:.*]] = cir.const #cir.int<30> : !s32i +// CIR: cir.store{{.*}} %[[THIRTY]], %[[A]] : !s32i, !cir.ptr +// CIR-NOT: cir.ternary +// CIR-NOT: cir.throw +// CIR: cir.store %[[A]] +// CIR: %[[RET:.*]] = cir.load +// CIR: cir.return %[[RET]] : !cir.ptr + +// LLVM-LABEL: define{{.*}} ptr @_Z32test_cond_const_false_throw_truev( +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: store i32 30, ptr %[[A]] +// LLVM-NOT: br i1 +// LLVM-NOT: __cxa_throw +// LLVM: store ptr %[[A]] +// LLVM: %[[RET:.*]] = load ptr +// LLVM: ret ptr %[[RET]] + +// OGCG-LABEL: define{{.*}} ptr @_Z32test_cond_const_false_throw_truev( +// OGCG: %[[A:.*]] = alloca i32 +// OGCG: store i32 30, ptr %[[A]] +// OGCG-NOT: br i1 +// OGCG-NOT: __cxa_throw +// OGCG: ret ptr %[[A]] + diff --git a/clang/test/CIR/CodeGen/ternary.cpp b/clang/test/CIR/CodeGen/ternary.cpp index 8dba31127d684..847c0b4a04009 100644 --- a/clang/test/CIR/CodeGen/ternary.cpp +++ b/clang/test/CIR/CodeGen/ternary.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fexceptions -fcxx-exceptions -emit-cir %s -o %t.cir +// 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 -fexceptions -fcxx-exceptions -emit-llvm %s -o %t-cir.ll +// 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 -fexceptions -fcxx-exceptions -emit-llvm %s -o %t.ll +// 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) { @@ -305,128 +305,54 @@ void test_cond_lvalue_compound(bool flag) { // OGCG: %[[NEW:.*]] = add{{.*}} i32 %[[OLD]], 3 // OGCG: store i32 %[[NEW]], ptr %[[PTR]] -const int& test_cond_throw_false(bool flag) { - const int a = 10; - return flag ? a : throw 0; +// Test constant folding - compile-time true condition with lvalue assignment +void test_cond_const_true_lvalue() { + int a = 1; + int b = 2; + (true ? a : b) = 99; } -// CIR-LABEL: cir.func{{.*}} @_Z21test_cond_throw_falseb( -// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] -// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] -// CIR: %[[TEN:.*]] = cir.const #cir.int<10> : !s32i -// CIR: cir.store{{.*}} %[[TEN]], %[[A]] : !s32i, !cir.ptr -// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] : !cir.ptr, !cir.bool -// CIR: %[[RESULT:.*]] = cir.ternary(%[[FLAG_VAL]], true { -// CIR: cir.yield %[[A]] : !cir.ptr -// CIR: }, false { -// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr -// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i -// CIR: cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr -// CIR: cir.throw %[[EXCEPTION]] : !cir.ptr, @_ZTIi -// CIR: cir.unreachable -// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR-LABEL: cir.func{{.*}} @_Z27test_cond_const_true_lvaluev( +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR-NOT: cir.ternary +// CIR: %[[NINETYNINE:.*]] = cir.const #cir.int<99> : !s32i +// CIR: cir.store{{.*}} %[[NINETYNINE]], %[[A]] : !s32i, !cir.ptr -// LLVM-LABEL: define{{.*}} ptr @_Z21test_cond_throw_falseb( -// LLVM: %[[FLAG_ALLOCA:.*]] = alloca i8 -// LLVM: %[[RET_ALLOCA:.*]] = alloca ptr -// LLVM: %[[A_ALLOCA:.*]] = alloca i32 -// LLVM: %[[ZEXT:.*]] = zext i1 %{{.*}} to i8 -// LLVM: store i8 %[[ZEXT]], ptr %[[FLAG_ALLOCA]] -// LLVM: store i32 10, ptr %[[A_ALLOCA]] -// LLVM: %[[LOAD:.*]] = load i8, ptr %[[FLAG_ALLOCA]] -// LLVM: %[[BOOL:.*]] = trunc i8 %[[LOAD]] to i1 -// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] -// LLVM: [[TRUE_BB]]: -// LLVM: br label %[[PHI_BB:.*]] -// LLVM: [[FALSE_BB]]: -// LLVM: %[[EXC:.*]] = call{{.*}} ptr @__cxa_allocate_exception -// LLVM: store i32 0, ptr %[[EXC]] -// LLVM: call void @__cxa_throw(ptr %[[EXC]], ptr @_ZTIi -// LLVM: unreachable -// LLVM: [[PHI_BB]]: -// LLVM: %[[PHI:.*]] = phi ptr [ %[[A_ALLOCA]], %[[TRUE_BB]] ] -// LLVM: br label %[[CONT_BB:.*]] -// LLVM: [[CONT_BB]]: -// LLVM: store ptr %[[A_ALLOCA]], ptr %[[RET_ALLOCA]] -// LLVM: %[[RET:.*]] = load ptr, ptr %[[RET_ALLOCA]] -// LLVM: ret ptr %[[RET]] +// LLVM-LABEL: define{{.*}} void @_Z27test_cond_const_true_lvaluev( +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM-NOT: br i1 +// LLVM: store i32 99, ptr %[[A]] -// OGCG-LABEL: define{{.*}} ptr @_Z21test_cond_throw_falseb( -// OGCG: %{{.*}} = alloca i8 +// OGCG-LABEL: define{{.*}} void @_Z27test_cond_const_true_lvaluev( // OGCG: %[[A:.*]] = alloca i32 -// OGCG: store i32 10, ptr %[[A]] -// OGCG: %{{.*}} = load i8, ptr %{{.*}} -// OGCG: %[[BOOL:.*]] = trunc i8 %{{.*}} to i1 -// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] -// OGCG: [[TRUE_BB]]: -// OGCG: br label %[[END:.*]] -// OGCG: [[FALSE_BB]]: -// OGCG: %{{.*}} = call{{.*}} ptr @__cxa_allocate_exception -// OGCG: store i32 0, ptr %{{.*}} -// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi -// OGCG: unreachable -// OGCG: [[END]]: -// OGCG: ret ptr %[[A]] +// OGCG: %[[B:.*]] = alloca i32 +// OGCG-NOT: br i1 +// OGCG: store i32 99, ptr %[[A]] -const int& test_cond_throw_true(bool flag) { - const int a = 10; - return flag ? throw 0 : a; +// Test constant folding - compile-time false condition with lvalue assignment +void test_cond_const_false_lvalue() { + int a = 1; + int b = 2; + (false ? a : b) = 88; } -// CIR-LABEL: cir.func{{.*}} @_Z20test_cond_throw_trueb( -// CIR: %[[FLAG:.*]] = cir.alloca !cir.bool, !cir.ptr, ["flag", init] -// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init, const] -// CIR: %[[TEN:.*]] = cir.const #cir.int<10> : !s32i -// CIR: cir.store{{.*}} %[[TEN]], %[[A]] : !s32i, !cir.ptr -// CIR: %[[FLAG_VAL:.*]] = cir.load{{.*}} %[[FLAG]] : !cir.ptr, !cir.bool -// CIR: %[[RESULT:.*]] = cir.ternary(%[[FLAG_VAL]], true { -// CIR: %[[EXCEPTION:.*]] = cir.alloc.exception{{.*}} -> !cir.ptr -// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i -// CIR: cir.store{{.*}} %[[ZERO]], %[[EXCEPTION]] : !s32i, !cir.ptr -// CIR: cir.throw %[[EXCEPTION]] : !cir.ptr, @_ZTIi -// CIR: cir.unreachable -// CIR: }, false { -// CIR: cir.yield %[[A]] : !cir.ptr -// CIR: }) : (!cir.bool) -> !cir.ptr +// CIR-LABEL: cir.func{{.*}} @_Z28test_cond_const_false_lvaluev( +// CIR: %[[A:.*]] = cir.alloca !s32i, !cir.ptr, ["a", init] +// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr, ["b", init] +// CIR-NOT: cir.ternary +// CIR: %[[EIGHTYEIGHT:.*]] = cir.const #cir.int<88> : !s32i +// CIR: cir.store{{.*}} %[[EIGHTYEIGHT]], %[[B]] : !s32i, !cir.ptr -// LLVM-LABEL: define{{.*}} ptr @_Z20test_cond_throw_trueb( -// LLVM: %[[FLAG_ALLOCA:.*]] = alloca i8 -// LLVM: %[[RET_ALLOCA:.*]] = alloca ptr -// LLVM: %[[A_ALLOCA:.*]] = alloca i32 -// LLVM: %[[ZEXT:.*]] = zext i1 %{{.*}} to i8 -// LLVM: store i8 %[[ZEXT]], ptr %[[FLAG_ALLOCA]] -// LLVM: store i32 10, ptr %[[A_ALLOCA]] -// LLVM: %[[LOAD:.*]] = load i8, ptr %[[FLAG_ALLOCA]] -// LLVM: %[[BOOL:.*]] = trunc i8 %[[LOAD]] to i1 -// LLVM: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] -// LLVM: [[TRUE_BB]]: -// LLVM: %[[EXC:.*]] = call{{.*}} ptr @__cxa_allocate_exception -// LLVM: store i32 0, ptr %[[EXC]] -// LLVM: call void @__cxa_throw(ptr %[[EXC]], ptr @_ZTIi -// LLVM: unreachable -// LLVM: [[FALSE_BB]]: -// LLVM: br label %[[PHI_BB:.*]] -// LLVM: [[PHI_BB]]: -// LLVM: %[[PHI:.*]] = phi ptr [ %[[A_ALLOCA]], %[[FALSE_BB]] ] -// LLVM: br label %[[CONT_BB:.*]] -// LLVM: [[CONT_BB]]: -// LLVM: store ptr %[[A_ALLOCA]], ptr %[[RET_ALLOCA]] -// LLVM: %[[RET:.*]] = load ptr, ptr %[[RET_ALLOCA]] -// LLVM: ret ptr %[[RET]] +// LLVM-LABEL: define{{.*}} void @_Z28test_cond_const_false_lvaluev( +// LLVM: %[[A:.*]] = alloca i32 +// LLVM: %[[B:.*]] = alloca i32 +// LLVM-NOT: br i1 +// LLVM: store i32 88, ptr %[[B]] -// OGCG-LABEL: define{{.*}} ptr @_Z20test_cond_throw_trueb( -// OGCG: %{{.*}} = alloca i8 +// OGCG-LABEL: define{{.*}} void @_Z28test_cond_const_false_lvaluev( // OGCG: %[[A:.*]] = alloca i32 -// OGCG: store i32 10, ptr %[[A]] -// OGCG: %{{.*}} = load i8, ptr %{{.*}} -// OGCG: %[[BOOL:.*]] = trunc i8 %{{.*}} to i1 -// OGCG: br i1 %[[BOOL]], label %[[TRUE_BB:.*]], label %[[FALSE_BB:.*]] -// OGCG: [[TRUE_BB]]: -// OGCG: %{{.*}} = call{{.*}} ptr @__cxa_allocate_exception -// OGCG: store i32 0, ptr %{{.*}} -// OGCG: call void @__cxa_throw(ptr %{{.*}}, ptr @_ZTIi -// OGCG: unreachable -// OGCG: [[FALSE_BB]]: -// OGCG: br label %[[END:.*]] -// OGCG: [[END]]: -// OGCG: ret ptr %[[A]] +// OGCG: %[[B:.*]] = alloca i32 +// OGCG-NOT: br i1 +// OGCG: store i32 88, ptr %[[B]] From 5c9654d9d18cfad34b003523d9aa5292f06223fb Mon Sep 17 00:00:00 2001 From: Morris Hafner Date: Mon, 20 Oct 2025 23:51:06 +0700 Subject: [PATCH 4/4] explicit namespace --- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index cf0905dd19387..0c0d91501219b 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1986,9 +1986,10 @@ void cir::TernaryOp::build( // Get result type from whichever branch has a yield (the other may have // unreachable from a throw expression) - YieldOp yield = dyn_cast_or_null(trueRegion->back().getTerminator()); + auto yield = + dyn_cast_or_null(trueRegion->back().getTerminator()); if (!yield) - yield = dyn_cast_or_null(falseRegion->back().getTerminator()); + yield = dyn_cast_or_null(falseRegion->back().getTerminator()); assert((yield && yield.getNumOperands() <= 1) && "expected zero or one result type");