Skip to content

Commit cf978f1

Browse files
committed
[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.
1 parent 4aba5ed commit cf978f1

File tree

7 files changed

+684
-23
lines changed

7 files changed

+684
-23
lines changed

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,6 +2394,185 @@ 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 condExprInt;
2409+
if (cgf.constantFoldsToSimpleInteger(condExpr, condExprInt)) {
2410+
bool condExprBool = condExprInt.getBoolValue();
2411+
const Expr *live = e->getTrueExpr(), *dead = e->getFalseExpr();
2412+
if (!condExprBool)
2413+
std::swap(live, dead);
2414+
2415+
if (!cgf.containsLabel(dead)) {
2416+
// If the true case is live, we need to track its region.
2417+
assert(!cir::MissingFeatures::incrementProfileCounter());
2418+
assert(!cir::MissingFeatures::pgoUse());
2419+
// If a throw expression we emit it and return an undefined lvalue
2420+
// because it can't be used.
2421+
if (auto *throwExpr = dyn_cast<CXXThrowExpr>(live->IgnoreParens())) {
2422+
cgf.emitCXXThrowExpr(throwExpr);
2423+
// Return an undefined lvalue - the throw terminates execution
2424+
// so this value will never actually be used
2425+
mlir::Type elemTy = cgf.convertType(dead->getType());
2426+
mlir::Type ptrTy = cir::PointerType::get(elemTy);
2427+
mlir::Value undefPtr = cgf.getBuilder().getNullValue(
2428+
ptrTy, cgf.getLoc(throwExpr->getSourceRange()));
2429+
return cgf.makeAddrLValue(Address(undefPtr, elemTy, CharUnits::One()),
2430+
dead->getType());
2431+
}
2432+
return cgf.emitLValue(live);
2433+
}
2434+
}
2435+
return std::nullopt;
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 patchVoidOrThrowSites = [&] {
2468+
if (insertPoints.empty())
2469+
return;
2470+
// If both arms are void, so be it.
2471+
if (!yieldTy)
2472+
yieldTy = VoidTy;
2473+
2474+
// Insert required yields.
2475+
for (mlir::OpBuilder::InsertPoint &toInsert : insertPoints) {
2476+
mlir::OpBuilder::InsertionGuard guard(builder);
2477+
builder.restoreInsertionPoint(toInsert);
2478+
2479+
// Block does not return: build empty yield.
2480+
if (mlir::isa<cir::VoidType>(yieldTy)) {
2481+
cir::YieldOp::create(builder, loc);
2482+
} else { // Block returns: set null yield value.
2483+
mlir::Value op0 = builder.getNullValue(yieldTy, loc);
2484+
cir::YieldOp::create(builder, loc, op0);
2485+
}
2486+
}
2487+
};
2488+
2489+
auto emitBranch = [&](mlir::OpBuilder &b, mlir::Location loc,
2490+
const Expr *expr, std::optional<LValue> &resultLV) {
2491+
CIRGenFunction::LexicalScope lexScope{*this, loc, b.getInsertionBlock()};
2492+
curLexScope->setAsTernary();
2493+
2494+
assert(!cir::MissingFeatures::incrementProfileCounter());
2495+
eval.beginEvaluation();
2496+
resultLV = branchGenFunc(*this, expr);
2497+
mlir::Value resultPtr = resultLV ? resultLV->getPointer() : mlir::Value();
2498+
eval.endEvaluation();
2499+
2500+
if (resultPtr) {
2501+
yieldTy = resultPtr.getType();
2502+
cir::YieldOp::create(b, loc, resultPtr);
2503+
} else {
2504+
// If LHS or RHS is a void expression we need
2505+
// to patch arms as to properly match yield types.
2506+
// If the current block's terminator is an UnreachableOp (from a throw),
2507+
// we don't need a yield
2508+
if (builder.getInsertionBlock()->mightHaveTerminator()) {
2509+
mlir::Operation *terminator =
2510+
builder.getInsertionBlock()->getTerminator();
2511+
if (isa_and_nonnull<cir::UnreachableOp>(terminator))
2512+
insertPoints.push_back(b.saveInsertionPoint());
2513+
}
2514+
}
2515+
};
2516+
2517+
info.result = cir::TernaryOp::create(
2518+
builder, loc, condV,
2519+
/*trueBuilder=*/
2520+
[&](mlir::OpBuilder &b, mlir::Location loc) {
2521+
emitBranch(b, loc, e->getTrueExpr(), info.lhs);
2522+
},
2523+
/*falseBuilder=*/
2524+
[&](mlir::OpBuilder &b, mlir::Location loc) {
2525+
emitBranch(b, loc, e->getFalseExpr(), info.rhs);
2526+
patchVoidOrThrowSites();
2527+
})
2528+
.getResult();
2529+
2530+
return info;
2531+
}
2532+
2533+
LValue CIRGenFunction::emitConditionalOperatorLValue(
2534+
const AbstractConditionalOperator *expr) {
2535+
if (!expr->isGLValue()) {
2536+
// ?: here should be an aggregate.
2537+
assert(hasAggregateEvaluationKind(expr->getType()) &&
2538+
"Unexpected conditional operator!");
2539+
return emitAggExprToLValue(expr);
2540+
}
2541+
2542+
OpaqueValueMapping binding(*this, expr);
2543+
if (std::optional<LValue> res =
2544+
handleConditionalOperatorLValueSimpleCase(*this, expr))
2545+
return *res;
2546+
2547+
ConditionalInfo info =
2548+
emitConditionalBlocks(expr, [](CIRGenFunction &cgf, const Expr *e) {
2549+
return emitLValueOrThrowExpression(cgf, e);
2550+
});
2551+
2552+
if ((info.lhs && !info.lhs->isSimple()) ||
2553+
(info.rhs && !info.rhs->isSimple())) {
2554+
cgm.errorNYI(expr->getSourceRange(),
2555+
"unsupported conditional operator with non-simple lvalue");
2556+
return LValue();
2557+
}
2558+
2559+
if (info.lhs && info.rhs) {
2560+
Address lhsAddr = info.lhs->getAddress();
2561+
Address rhsAddr = info.rhs->getAddress();
2562+
Address result(info.result, lhsAddr.getElementType(),
2563+
std::min(lhsAddr.getAlignment(), rhsAddr.getAlignment()));
2564+
AlignmentSource alignSource =
2565+
std::max(info.lhs->getBaseInfo().getAlignmentSource(),
2566+
info.rhs->getBaseInfo().getAlignmentSource());
2567+
assert(!cir::MissingFeatures::opTBAA());
2568+
return makeAddrLValue(result, expr->getType(), LValueBaseInfo(alignSource));
2569+
}
2570+
2571+
assert((info.lhs || info.rhs) &&
2572+
"both operands of glvalue conditional are throw-expressions?");
2573+
return info.lhs ? *info.lhs : *info.rhs;
2574+
}
2575+
23972576
/// An LValue is a candidate for having its loads and stores be made atomic if
23982577
/// we are operating under /volatile:ms *and* the LValue itself is volatile and
23992578
/// 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
@@ -1518,6 +1518,10 @@ class CIRGenFunction : public CIRGenTypeCache {
15181518

15191519
LValue emitMemberExpr(const MemberExpr *e);
15201520

1521+
LValue emitOpaqueValueLValue(const OpaqueValueExpr *e);
1522+
1523+
LValue emitConditionalOperatorLValue(const AbstractConditionalOperator *expr);
1524+
15211525
/// Given an expression with a pointer type, emit the value and compute our
15221526
/// best estimate of the alignment of the pointee.
15231527
///

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1978,13 +1978,18 @@ 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+
Block *trueBlock = builder.createBlock(trueRegion);
19821982
trueBuilder(builder, result.location);
19831983
Region *falseRegion = result.addRegion();
1984-
builder.createBlock(falseRegion);
1984+
Block *falseBlock = 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+
YieldOp yield = dyn_cast_or_null<YieldOp>(trueRegion->back().getTerminator());
1990+
if (!yield)
1991+
yield = dyn_cast_or_null<YieldOp>(falseRegion->back().getTerminator());
1992+
19881993
assert((yield && yield.getNumOperands() <= 1) &&
19891994
"expected zero or one result type");
19901995
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)