[clang] [CIR] Add support for ternary operator as lvalue (PR #163580)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 15 08:56:14 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clangir
Author: Morris Hafner (mmha)
<details>
<summary>Changes</summary>
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.
---
Patch is 35.56 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/163580.diff
7 Files Affected:
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+179)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+6)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+4)
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+8-3)
- (modified) clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp (+25-6)
- (modified) clang/test/CIR/CodeGen/opaque.cpp (+163)
- (modified) clang/test/CIR/CodeGen/ternary.cpp (+297-12)
``````````diff
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index f416571181153..27ce368f57fec 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<LValue> 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<CXXThrowExpr>(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<LValue> emitLValueOrThrowExpression(CIRGenFunction &cgf,
+ const Expr *operand) {
+ if (auto *throwExpr = dyn_cast<CXXThrowExpr>(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 <typename FuncTy>
+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<mlir::OpBuilder::InsertPoint, 2> 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<cir::VoidType>(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<LValue> &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<cir::UnreachableOp>(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<LValue> res =
+ handleConditionalOperatorLValueSimpleCase(*this, expr))
+ return *res;
+
+ ConditionalInfo info =
+ emitConditionalBlocks(expr, [](CIRGenFunction &cgf, const Expr *e) {
+ return emitLValueOrThrowExpression(cgf, e);
+ });
+
+ if ((info.lhs && !info.lhs->isSimple()) ||
+ (info.rhs && !info.rhs->isSimple())) {
+ cgm.errorNYI(expr->getSourceRange(),
+ "unsupported conditional operator 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 7a774e0441bbb..c7fc912436f2b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -814,6 +814,10 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
std::string("l-value not implemented for '") +
e->getStmtClassName() + "'");
return LValue();
+ case Expr::ConditionalOperatorClass:
+ return emitConditionalOperatorLValue(cast<ConditionalOperator>(e));
+ case Expr::BinaryConditionalOperatorClass:
+ return emitConditionalOperatorLValue(cast<BinaryConditionalOperator>(e));
case Expr::ArraySubscriptExprClass:
return emitArraySubscriptExpr(cast<ArraySubscriptExpr>(e));
case Expr::UnaryOperatorClass:
@@ -858,6 +862,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
return emitCastLValue(cast<CastExpr>(e));
case Expr::MaterializeTemporaryExprClass:
return emitMaterializeTemporaryExpr(cast<MaterializeTemporaryExpr>(e));
+ case Expr::OpaqueValueExprClass:
+ return emitOpaqueValueLValue(cast<OpaqueValueExpr>(e));
case Expr::ChooseExprClass:
return emitLValue(cast<ChooseExpr>(e)->getChosenSubExpr());
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index d71de2ffde6a1..d7cb9914944f0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1486,6 +1486,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 5f88590c48d30..d38b7135f0476 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1894,13 +1894,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<YieldOp>(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<YieldOp>(trueRegion->back().getTerminator());
+ if (!yield)
+ yield = dyn_cast_or_null<YieldOp>(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<cir::TernaryOp> {
Block *trueBlock = &trueRegion.front();
mlir::Operation *trueTerminator = trueRegion.back().getTerminator();
rewriter.setInsertionPointToEnd(&trueRegion.back());
- auto trueYieldOp = dyn_cast<cir::YieldOp>(trueTerminator);
- rewriter.replaceOpWithNewOp<cir::BrOp>(trueYieldOp, trueYieldOp.getArgs(),
- continueBlock);
+ // Handle both yield and unreachable terminators (throw expressions)
+ if (auto trueYieldOp = dyn_cast<cir::YieldOp>(trueTerminator)) {
+ rewriter.replaceOpWithNewOp<cir::BrOp>(trueYieldOp, trueYieldOp.getArgs(),
+ continueBlock);
+ } else if (isa<cir::UnreachableOp>(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<cir::TernaryOp> {
falseBlock = &falseRegion.front();
mlir::Operation *falseTerminator = falseRegion.back().getTerminator();
rewriter.setInsertionPointToEnd(&falseRegion.back());
- auto falseYieldOp = dyn_cast<cir::YieldOp>(falseTerminator);
- rewriter.replaceOpWithNewOp<cir::BrOp>(falseYieldOp, falseYieldOp.getArgs(),
- continueBlock);
+
+ // Handle both yield and unreachable terminators (throw expressions)
+ if (auto falseYieldOp = dyn_cast<cir::YieldOp>(falseTerminator)) {
+ rewriter.replaceOpWithNewOp<cir::BrOp>(
+ falseYieldOp, falseYieldOp.getArgs(), continueBlock);
+ } else if (isa<cir::UnreachableOp>(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<!s32i>, ["a", init]
+// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
+// CIR: %[[A_VAL:.*]] = cir.load{{.*}} %[[A]] : !cir.ptr<!s32i>, !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<!s32i>
+// CIR: }, false {
+// CIR: cir.yield %[[B]] : !cir.ptr<!s32i>
+// CIR: }) : (!cir.bool) -> !cir.ptr<!s32i>
+// CIR: cir.store{{.*}} %{{.*}}, %[[TERNARY_PTR]] : !s32i, !cir.ptr<!s32i>
+
+// 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<!s32i>, ["a", init]
+// CIR: %[[B:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
+// CIR: %[[A_VAL:.*]] = cir.load{{.*}} %[[A]] : !cir.ptr<!s32i>, !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<!s32i>
+// CIR: }, false {
+// CIR: cir.yield %[[B]] : !cir.ptr<!s32i>
+// CIR: }) : (!cir.bool) -> !cir.ptr<!s32i>
+// 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<!s32i>, ["x", init]
+// CIR: %[[Y:.*]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["y", init]
+// CIR: %[[P:.*]] = cir.alloca !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>, ["p", init]
+// CIR: %[[Q:.*]] = cir.alloca !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>, ["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<!s32i>
+// CIR: }, false {
+// CIR: %[[Q_LOAD:.*]] = cir.load{{.*}} %[[Q]]
+// CIR: cir.yield %[[Q_LOAD]] : !cir.ptr<!s32i>
+// CIR: }) : (!cir.bool) -> !cir.ptr<!s32i>
+// 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:.*]]
+//...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/163580
More information about the cfe-commits
mailing list