Skip to content

Commit 694b748

Browse files
mmhaaokblast
authored andcommitted
[CIR] Add support for ternary operator as lvalue (llvm#163580)
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.
1 parent fb8b730 commit 694b748

File tree

8 files changed

+799
-19
lines changed

8 files changed

+799
-19
lines changed

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,6 +2394,180 @@ LValue CIRGenFunction::emitPredefinedLValue(const PredefinedExpr *e) {
23942394
return emitStringLiteralLValue(sl, gvName);
23952395
}
23962396

2397+
LValue CIRGenFunction::emitOpaqueValueLValue(const OpaqueValueExpr *e) {
2398+
assert(OpaqueValueMappingData::shouldBindAsLValue(e));
2399+
return getOrCreateOpaqueLValueMapping(e);
2400+
}
2401+
2402+
namespace {
2403+
// Handle the case where the condition is a constant evaluatable simple integer,
2404+
// which means we don't have to separately handle the true/false blocks.
2405+
std::optional<LValue> handleConditionalOperatorLValueSimpleCase(
2406+
CIRGenFunction &cgf, const AbstractConditionalOperator *e) {
2407+
const Expr *condExpr = e->getCond();
2408+
llvm::APSInt condExprVal;
2409+
if (!cgf.constantFoldsToSimpleInteger(condExpr, condExprVal))
2410+
return std::nullopt;
2411+
2412+
const Expr *live = e->getTrueExpr(), *dead = e->getFalseExpr();
2413+
if (!condExprVal.getBoolValue())
2414+
std::swap(live, dead);
2415+
2416+
if (cgf.containsLabel(dead))
2417+
return std::nullopt;
2418+
2419+
// If the true case is live, we need to track its region.
2420+
assert(!cir::MissingFeatures::incrementProfileCounter());
2421+
assert(!cir::MissingFeatures::pgoUse());
2422+
// If a throw expression we emit it and return an undefined lvalue
2423+
// because it can't be used.
2424+
if (auto *throwExpr = dyn_cast<CXXThrowExpr>(live->IgnoreParens())) {
2425+
cgf.emitCXXThrowExpr(throwExpr);
2426+
// Return an undefined lvalue - the throw terminates execution
2427+
// so this value will never actually be used
2428+
mlir::Type elemTy = cgf.convertType(dead->getType());
2429+
mlir::Value undefPtr =
2430+
cgf.getBuilder().getNullPtr(cgf.getBuilder().getPointerTo(elemTy),
2431+
cgf.getLoc(throwExpr->getSourceRange()));
2432+
return cgf.makeAddrLValue(Address(undefPtr, elemTy, CharUnits::One()),
2433+
dead->getType());
2434+
}
2435+
return cgf.emitLValue(live);
2436+
}
2437+
2438+
/// Emit the operand of a glvalue conditional operator. This is either a glvalue
2439+
/// or a (possibly-parenthesized) throw-expression. If this is a throw, no
2440+
/// LValue is returned and the current block has been terminated.
2441+
static std::optional<LValue> emitLValueOrThrowExpression(CIRGenFunction &cgf,
2442+
const Expr *operand) {
2443+
if (auto *throwExpr = dyn_cast<CXXThrowExpr>(operand->IgnoreParens())) {
2444+
cgf.emitCXXThrowExpr(throwExpr);
2445+
return std::nullopt;
2446+
}
2447+
2448+
return cgf.emitLValue(operand);
2449+
}
2450+
} // namespace
2451+
2452+
// Create and generate the 3 blocks for a conditional operator.
2453+
// Leaves the 'current block' in the continuation basic block.
2454+
template <typename FuncTy>
2455+
CIRGenFunction::ConditionalInfo
2456+
CIRGenFunction::emitConditionalBlocks(const AbstractConditionalOperator *e,
2457+
const FuncTy &branchGenFunc) {
2458+
ConditionalInfo info;
2459+
ConditionalEvaluation eval(*this);
2460+
mlir::Location loc = getLoc(e->getSourceRange());
2461+
CIRGenBuilderTy &builder = getBuilder();
2462+
2463+
mlir::Value condV = emitOpOnBoolExpr(loc, e->getCond());
2464+
SmallVector<mlir::OpBuilder::InsertPoint, 2> insertPoints{};
2465+
mlir::Type yieldTy{};
2466+
2467+
auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc,
2468+
const Expr *expr, std::optional<LValue> &resultLV) {
2469+
CIRGenFunction::LexicalScope lexScope{*this, loc, b.getInsertionBlock()};
2470+
curLexScope->setAsTernary();
2471+
2472+
assert(!cir::MissingFeatures::incrementProfileCounter());
2473+
eval.beginEvaluation();
2474+
resultLV = branchGenFunc(*this, expr);
2475+
mlir::Value resultPtr = resultLV ? resultLV->getPointer() : mlir::Value();
2476+
eval.endEvaluation();
2477+
2478+
if (resultPtr) {
2479+
yieldTy = resultPtr.getType();
2480+
cir::YieldOp::create(b, loc, resultPtr);
2481+
} else {
2482+
// If LHS or RHS is a void expression we need
2483+
// to patch arms as to properly match yield types.
2484+
// If the current block's terminator is an UnreachableOp (from a throw),
2485+
// we don't need a yield
2486+
if (builder.getInsertionBlock()->mightHaveTerminator()) {
2487+
mlir::Operation *terminator =
2488+
builder.getInsertionBlock()->getTerminator();
2489+
if (isa_and_nonnull<cir::UnreachableOp>(terminator))
2490+
insertPoints.push_back(b.saveInsertionPoint());
2491+
}
2492+
}
2493+
};
2494+
2495+
info.result = cir::TernaryOp::create(
2496+
builder, loc, condV,
2497+
/*trueBuilder=*/
2498+
[&](mlir::OpBuilder &b, mlir::Location loc) {
2499+
emitBranch(b, loc, e->getTrueExpr(), info.lhs);
2500+
},
2501+
/*falseBuilder=*/
2502+
[&](mlir::OpBuilder &b, mlir::Location loc) {
2503+
emitBranch(b, loc, e->getFalseExpr(), info.rhs);
2504+
})
2505+
.getResult();
2506+
2507+
// If both arms are void, so be it.
2508+
if (!yieldTy)
2509+
yieldTy = VoidTy;
2510+
2511+
// Insert required yields.
2512+
for (mlir::OpBuilder::InsertPoint &toInsert : insertPoints) {
2513+
mlir::OpBuilder::InsertionGuard guard(builder);
2514+
builder.restoreInsertionPoint(toInsert);
2515+
2516+
// Block does not return: build empty yield.
2517+
if (!yieldTy) {
2518+
cir::YieldOp::create(builder, loc);
2519+
} else { // Block returns: set null yield value.
2520+
mlir::Value op0 = builder.getNullValue(yieldTy, loc);
2521+
cir::YieldOp::create(builder, loc, op0);
2522+
}
2523+
}
2524+
2525+
return info;
2526+
}
2527+
2528+
LValue CIRGenFunction::emitConditionalOperatorLValue(
2529+
const AbstractConditionalOperator *expr) {
2530+
if (!expr->isGLValue()) {
2531+
// ?: here should be an aggregate.
2532+
assert(hasAggregateEvaluationKind(expr->getType()) &&
2533+
"Unexpected conditional operator!");
2534+
return emitAggExprToLValue(expr);
2535+
}
2536+
2537+
OpaqueValueMapping binding(*this, expr);
2538+
if (std::optional<LValue> res =
2539+
handleConditionalOperatorLValueSimpleCase(*this, expr))
2540+
return *res;
2541+
2542+
ConditionalInfo info =
2543+
emitConditionalBlocks(expr, [](CIRGenFunction &cgf, const Expr *e) {
2544+
return emitLValueOrThrowExpression(cgf, e);
2545+
});
2546+
2547+
if ((info.lhs && !info.lhs->isSimple()) ||
2548+
(info.rhs && !info.rhs->isSimple())) {
2549+
cgm.errorNYI(expr->getSourceRange(),
2550+
"unsupported conditional operator with non-simple lvalue");
2551+
return LValue();
2552+
}
2553+
2554+
if (info.lhs && info.rhs) {
2555+
Address lhsAddr = info.lhs->getAddress();
2556+
Address rhsAddr = info.rhs->getAddress();
2557+
Address result(info.result, lhsAddr.getElementType(),
2558+
std::min(lhsAddr.getAlignment(), rhsAddr.getAlignment()));
2559+
AlignmentSource alignSource =
2560+
std::max(info.lhs->getBaseInfo().getAlignmentSource(),
2561+
info.rhs->getBaseInfo().getAlignmentSource());
2562+
assert(!cir::MissingFeatures::opTBAA());
2563+
return makeAddrLValue(result, expr->getType(), LValueBaseInfo(alignSource));
2564+
}
2565+
2566+
assert((info.lhs || info.rhs) &&
2567+
"both operands of glvalue conditional are throw-expressions?");
2568+
return info.lhs ? *info.lhs : *info.rhs;
2569+
}
2570+
23972571
/// An LValue is a candidate for having its loads and stores be made atomic if
23982572
/// we are operating under /volatile:ms *and* the LValue itself is volatile and
23992573
/// performing such an operation can be performed without a libcall.

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,6 +822,10 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
822822
std::string("l-value not implemented for '") +
823823
e->getStmtClassName() + "'");
824824
return LValue();
825+
case Expr::ConditionalOperatorClass:
826+
return emitConditionalOperatorLValue(cast<ConditionalOperator>(e));
827+
case Expr::BinaryConditionalOperatorClass:
828+
return emitConditionalOperatorLValue(cast<BinaryConditionalOperator>(e));
825829
case Expr::ArraySubscriptExprClass:
826830
return emitArraySubscriptExpr(cast<ArraySubscriptExpr>(e));
827831
case Expr::UnaryOperatorClass:
@@ -866,6 +870,8 @@ LValue CIRGenFunction::emitLValue(const Expr *e) {
866870
return emitCastLValue(cast<CastExpr>(e));
867871
case Expr::MaterializeTemporaryExprClass:
868872
return emitMaterializeTemporaryExpr(cast<MaterializeTemporaryExpr>(e));
873+
case Expr::OpaqueValueExprClass:
874+
return emitOpaqueValueLValue(cast<OpaqueValueExpr>(e));
869875
case Expr::ChooseExprClass:
870876
return emitLValue(cast<ChooseExpr>(e)->getChosenSubExpr());
871877
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,10 @@ class CIRGenFunction : public CIRGenTypeCache {
15231523

15241524
LValue emitMemberExpr(const MemberExpr *e);
15251525

1526+
LValue emitOpaqueValueLValue(const OpaqueValueExpr *e);
1527+
1528+
LValue emitConditionalOperatorLValue(const AbstractConditionalOperator *expr);
1529+
15261530
/// Given an expression with a pointer type, emit the value and compute our
15271531
/// best estimate of the alignment of the pointee.
15281532
///

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,13 +1978,19 @@ void cir::TernaryOp::build(
19781978
result.addOperands(cond);
19791979
OpBuilder::InsertionGuard guard(builder);
19801980
Region *trueRegion = result.addRegion();
1981-
Block *block = builder.createBlock(trueRegion);
1981+
builder.createBlock(trueRegion);
19821982
trueBuilder(builder, result.location);
19831983
Region *falseRegion = result.addRegion();
19841984
builder.createBlock(falseRegion);
19851985
falseBuilder(builder, result.location);
19861986

1987-
auto yield = dyn_cast<YieldOp>(block->getTerminator());
1987+
// Get result type from whichever branch has a yield (the other may have
1988+
// unreachable from a throw expression)
1989+
auto yield =
1990+
dyn_cast_or_null<cir::YieldOp>(trueRegion->back().getTerminator());
1991+
if (!yield)
1992+
yield = dyn_cast_or_null<cir::YieldOp>(falseRegion->back().getTerminator());
1993+
19881994
assert((yield && yield.getNumOperands() <= 1) &&
19891995
"expected zero or one result type");
19901996
if (yield.getNumOperands() == 1)

clang/lib/CIR/Dialect/Transforms/FlattenCFG.cpp

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -505,10 +505,19 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern<cir::TernaryOp> {
505505
Block *trueBlock = &trueRegion.front();
506506
mlir::Operation *trueTerminator = trueRegion.back().getTerminator();
507507
rewriter.setInsertionPointToEnd(&trueRegion.back());
508-
auto trueYieldOp = dyn_cast<cir::YieldOp>(trueTerminator);
509508

510-
rewriter.replaceOpWithNewOp<cir::BrOp>(trueYieldOp, trueYieldOp.getArgs(),
511-
continueBlock);
509+
// Handle both yield and unreachable terminators (throw expressions)
510+
if (auto trueYieldOp = dyn_cast<cir::YieldOp>(trueTerminator)) {
511+
rewriter.replaceOpWithNewOp<cir::BrOp>(trueYieldOp, trueYieldOp.getArgs(),
512+
continueBlock);
513+
} else if (isa<cir::UnreachableOp>(trueTerminator)) {
514+
// Terminator is unreachable (e.g., from throw), just keep it
515+
} else {
516+
trueTerminator->emitError("unexpected terminator in ternary true region, "
517+
"expected yield or unreachable, got: ")
518+
<< trueTerminator->getName();
519+
return mlir::failure();
520+
}
512521
rewriter.inlineRegionBefore(trueRegion, continueBlock);
513522

514523
Block *falseBlock = continueBlock;
@@ -517,9 +526,19 @@ class CIRTernaryOpFlattening : public mlir::OpRewritePattern<cir::TernaryOp> {
517526
falseBlock = &falseRegion.front();
518527
mlir::Operation *falseTerminator = falseRegion.back().getTerminator();
519528
rewriter.setInsertionPointToEnd(&falseRegion.back());
520-
auto falseYieldOp = dyn_cast<cir::YieldOp>(falseTerminator);
521-
rewriter.replaceOpWithNewOp<cir::BrOp>(falseYieldOp, falseYieldOp.getArgs(),
522-
continueBlock);
529+
530+
// Handle both yield and unreachable terminators (throw expressions)
531+
if (auto falseYieldOp = dyn_cast<cir::YieldOp>(falseTerminator)) {
532+
rewriter.replaceOpWithNewOp<cir::BrOp>(
533+
falseYieldOp, falseYieldOp.getArgs(), continueBlock);
534+
} else if (isa<cir::UnreachableOp>(falseTerminator)) {
535+
// Terminator is unreachable (e.g., from throw), just keep it
536+
} else {
537+
falseTerminator->emitError("unexpected terminator in ternary false "
538+
"region, expected yield or unreachable, got: ")
539+
<< falseTerminator->getName();
540+
return mlir::failure();
541+
}
523542
rewriter.inlineRegionBefore(falseRegion, continueBlock);
524543

525544
rewriter.setInsertionPointToEnd(condBlock);

0 commit comments

Comments
 (0)