[flang-commits] [flang] 3486c5d - [flang] Region-based HLFIR operation for conditional expressions lowering (#194411)
via flang-commits
flang-commits at lists.llvm.org
Wed Jul 22 08:55:31 PDT 2026
Author: Caroline Newcombe
Date: 2026-07-22T11:55:27-04:00
New Revision: 3486c5d48a78f5e132d05245f775fc6e27bdcaee
URL: https://github.com/llvm/llvm-project/commit/3486c5d48a78f5e132d05245f775fc6e27bdcaee
DIFF: https://github.com/llvm/llvm-project/commit/3486c5d48a78f5e132d05245f775fc6e27bdcaee.diff
LOG: [flang] Region-based HLFIR operation for conditional expressions lowering (#194411)
Implements `hlfir.conditional`, a region-based HLFIR operation that
represents Fortran 2023 conditional expressions (10.1.2.3) with lazy
branch evaluation.
Issue #176999
Assisted-by: Claude Sonnet 4.5
Added:
flang/test/HLFIR/bufferize-conditional.fir
Modified:
flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h
flang/include/flang/Optimizer/HLFIR/HLFIROps.td
flang/lib/Lower/ConvertExprToHLFIR.cpp
flang/lib/Optimizer/HLFIR/IR/HLFIRDialect.cpp
flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
flang/test/HLFIR/invalid.fir
flang/test/Lower/HLFIR/conditional-expr.f90
Removed:
################################################################################
diff --git a/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h b/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h
index 5152dee14ad65..c5eafe51723ee 100644
--- a/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h
+++ b/flang/include/flang/Optimizer/HLFIR/HLFIRDialect.h
@@ -65,6 +65,12 @@ inline mlir::Type getFortranElementOrSequenceType(mlir::Type type) {
/// variableType.
mlir::Type getExprType(mlir::Type variableType);
+/// Get the canonical HLFIR variable type for an hlfir::ExprType. This is
+/// the type a DeclareOp base result would have for a temp holding the
+/// expression value (without lower bounds). This is the inverse of
+/// getExprType.
+mlir::Type getVariableType(hlfir::ExprType exprType);
+
/// Is this a fir.box or fir.class address type?
inline bool isBoxAddressType(mlir::Type type) {
type = fir::dyn_cast_ptrEleTy(type);
diff --git a/flang/include/flang/Optimizer/HLFIR/HLFIROps.td b/flang/include/flang/Optimizer/HLFIR/HLFIROps.td
index 69647fe7a2c6d..f05554079816c 100644
--- a/flang/include/flang/Optimizer/HLFIR/HLFIROps.td
+++ b/flang/include/flang/Optimizer/HLFIR/HLFIROps.td
@@ -1510,11 +1510,14 @@ def hlfir_RegionAssignOp : hlfir_Op<"region_assign", [hlfir_OrderedAssignmentTre
let hasVerifier = 1;
}
-def hlfir_YieldOp : hlfir_Op<"yield", [Terminator, ParentOneOf<["RegionAssignOp",
- "ElementalAddrOp", "ForallOp", "ForallMaskOp", "WhereOp", "ElseWhereOp",
- "ExactlyOnceOp"]>,
- SingleBlockImplicitTerminator<"fir::FirEndOp">, RecursivelySpeculatable,
- RecursiveMemoryEffects]> {
+def hlfir_YieldOp
+ : hlfir_Op<"yield", [Terminator,
+ ParentOneOf<["RegionAssignOp", "ElementalAddrOp",
+ "ForallOp", "ForallMaskOp", "WhereOp",
+ "ElseWhereOp", "ExactlyOnceOp",
+ "ConditionalOp"]>,
+ SingleBlockImplicitTerminator<"fir::FirEndOp">,
+ RecursivelySpeculatable, RecursiveMemoryEffects]> {
let summary = "Yield a value or variable inside a forall, where or region assignment";
@@ -1988,5 +1991,49 @@ def hlfir_EvaluateInMemoryOp : hlfir_Op<"eval_in_mem", [AttrSizedOperandSegments
let hasVerifier = 1;
}
+def hlfir_ConditionalOp : hlfir_Op<"conditional", [RecursiveMemoryEffects,
+ MemoryEffects<[MemAlloc]>]> {
+ let summary = "Fortran conditional expression";
+ let description = [{
+ Represent a Fortran conditional expression (F2023 10.1.2.3) that produces a
+ value by lazily evaluating one of two branches based on a condition.
+
+ Only one of the two regions is evaluated at runtime. Each region must
+ be terminated by an hlfir.yield that yields the branch's result value.
+
+ The result is an hlfir.expr. The dynamic type, length type parameters, and
+ shape of the result are determined by the selected branch at runtime (F2023
+ 10.1.4.(7)), so they are not operands of this operation.
+
+ Chained conditional expressions are represented by nesting an
+ hlfir.conditional inside the else region.
+
+ Example: ( X ? Y : Z ) where X is logical and Y, Z are real scalars
+ ```
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<f32> {
+ hlfir.yield %y : f32
+ } else {
+ hlfir.yield %z : f32
+ }
+ ```
+ }];
+
+ let arguments = (ins I1:$condition);
+
+ let results = (outs hlfir_ExprType);
+ let regions = (region SizedRegion<1>:$then_region,
+ SizedRegion<1>:$else_region);
+
+ let assemblyFormat = [{
+ $condition attr-dict `:` functional-type(operands, results)
+ $then_region `else` $else_region
+ }];
+
+ let skipDefaultBuilders = 1;
+ let builders = [OpBuilder<(ins "mlir::Type":$result_type,
+ "mlir::Value":$condition)>];
+
+ let hasVerifier = 1;
+}
#endif // FORTRAN_DIALECT_HLFIR_OPS
diff --git a/flang/lib/Lower/ConvertExprToHLFIR.cpp b/flang/lib/Lower/ConvertExprToHLFIR.cpp
index 6f718a8eb5926..f8bb0c1c263b2 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1851,56 +1851,49 @@ class HlfirBuilder {
llvm_unreachable("unknown descriptor inquiry");
}
- /// Build nested if-then-else chain by walking the right-skewed
- /// ConditionalExpr tree. The assignValue callback generates and assigns
- /// each value to avoid evaluating non-taken branches.
- template <typename T, typename Callback>
- void
- buildConditionalIfChain(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
- const Callback &assignValue) {
+ /// Generate a conditional expression as an hlfir.conditional op whose
+ /// regions yield the then/else values. Materialization into memory is
+ /// deferred to the bufferization pass.
+ template <typename T>
+ hlfir::Entity
+ genConditionalOp(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
+ mlir::Type elementType, bool isPolymorphic) {
const mlir::Location loc{getLoc()};
fir::FirOpBuilder &builder{getBuilder()};
+ // Lower the condition to i1.
getStmtCtx().pushScope();
const hlfir::EntityWithAttributes condEntity{gen(condExpr.condition())};
mlir::Value condition{hlfir::loadTrivialScalar(loc, builder, condEntity)};
condition = builder.createConvert(loc, builder.getI1Type(), condition);
- builder.genIfOp(loc, {}, condition, /*withElseRegion=*/true)
- .genThen([&]() {
- getStmtCtx().pushScope();
- assignValue(condExpr.thenValue());
- getStmtCtx().finalizeAndPop();
- })
- .genElse([&]() {
- getStmtCtx().pushScope();
- assignValue(condExpr.elseValue());
- getStmtCtx().finalizeAndPop();
- })
- .end();
getStmtCtx().finalizeAndPop();
- }
-
- /// Generate scalar conditional with lazy evaluation using assignment.
- /// Creates a temporary and assigns the selected branch value to it.
- template <typename T>
- hlfir::Entity
- genScalarConditional(const Fortran::evaluate::ConditionalExpr<T> &condExpr,
- mlir::Type elementType,
- const llvm::SmallVector<mlir::Value, 1> &typeParams) {
- const mlir::Location loc{getLoc()};
- fir::FirOpBuilder &builder{getBuilder()};
- const mlir::Value tempStorage{builder.createTemporary(
- loc, elementType, ".cond.scalar",
- /*shape=*/mlir::ValueRange{}, /*typeParams=*/typeParams)};
- const hlfir::DeclareOp tempDecl{hlfir::DeclareOp::create(
- builder, loc, tempStorage, ".cond.result",
- /*shape=*/mlir::Value{}, /*typeParams=*/typeParams)};
- const hlfir::Entity temp{tempDecl};
- buildConditionalIfChain(
- condExpr, [&](const Fortran::evaluate::Expr<T> &expr) {
- hlfir::Entity entity{gen(expr)};
- hlfir::AssignOp::create(builder, loc, entity, temp);
- });
- return temp;
+ // Build the hlfir.expr result type.
+ const hlfir::ExprType::Shape shape(condExpr.Rank(),
+ hlfir::ExprType::getUnknownExtent());
+ const mlir::Type exprType{hlfir::ExprType::get(builder.getContext(), shape,
+ elementType, isPolymorphic)};
+ auto condOp =
+ hlfir::ConditionalOp::create(builder, loc, exprType, condition);
+ // Cleanups are placed in the yield's cleanup region so that
+ // bufferization can replay them after the assign into the temp.
+ auto genBranch = [&](mlir::Region ®ion, const auto &value) {
+ builder.setInsertionPointToStart(®ion.front());
+ getStmtCtx().pushScope();
+ hlfir::Entity entity{hlfir::derefPointersAndAllocatables(
+ loc, builder, hlfir::Entity{gen(value)})};
+ auto yieldOp = hlfir::YieldOp::create(builder, loc, entity);
+ Fortran::lower::genCleanUpInRegionIfAny(
+ loc, builder, yieldOp.getCleanup(), getStmtCtx());
+ if (yieldOp.getCleanup().empty())
+ getStmtCtx().pop();
+ };
+ genBranch(condOp.getThenRegion(), condExpr.thenValue());
+ genBranch(condOp.getElseRegion(), condExpr.elseValue());
+ builder.setInsertionPointAfter(condOp);
+ fir::FirOpBuilder *const bldr{&builder};
+ mlir::Value result{condOp.getResult()};
+ getStmtCtx().attachCleanup(
+ [=]() { hlfir::DestroyOp::create(*bldr, loc, result); });
+ return hlfir::Entity{result};
}
/// Generate scalar conditional for trivial scalar types using fir.if SSA
@@ -1944,110 +1937,27 @@ class HlfirBuilder {
return hlfir::Entity{results[0]};
}
- /// Generate conditional expression using an allocatable temporary with lazy
- /// evaluation. Creates an unallocated allocatable, then uses assignment to
- /// set the value from the chosen branch (allocation/reallocation handled by
- /// runtime).
- template <typename T>
- hlfir::Entity genAllocatableConditional(
- const Fortran::evaluate::ConditionalExpr<T> &condExpr,
- mlir::Type resultType, llvm::StringRef debugName) {
- const mlir::Location loc{getLoc()};
- fir::FirOpBuilder &builder{getBuilder()};
- // Polymorphic types need fir.class (not fir.box) to carry dynamic type
- // info. Both scalar and array polymorphic types reach here.
- const bool isPolymorphic{fir::isPolymorphicType(resultType)};
- const mlir::Type allocType{
- hlfir::getFortranElementOrSequenceType(resultType)};
- const mlir::Type heapType{fir::HeapType::get(allocType)};
- const mlir::Type boxHeapType{isPolymorphic
- ? mlir::Type{fir::ClassType::get(heapType)}
- : mlir::Type{fir::BoxType::get(heapType)}};
- const mlir::Value tempStorage{
- builder.createTemporary(loc, boxHeapType, debugName)};
- const mlir::Value unallocBox{fir::factory::createUnallocatedBox(
- builder, loc, boxHeapType, /*nonDeferredParams=*/{})};
- builder.createStoreWithConvert(loc, unallocBox, tempStorage);
- const hlfir::DeclareOp tempDecl{
- hlfir::DeclareOp::create(builder, loc, tempStorage, ".cond.result")};
- const hlfir::Entity temp{tempDecl};
- // Lazy evaluation: only the selected branch is evaluated and assigned.
- buildConditionalIfChain(
- condExpr, [&](const Fortran::evaluate::Expr<T> &expr) {
- const hlfir::Entity entity{gen(expr)};
- hlfir::AssignOp::create(builder, loc, entity, temp,
- /*isWholeAllocatableAssignment=*/true,
- /*keepLhsLengthIfRealloc=*/false,
- /*temporary_lhs=*/true);
- });
- fir::FirOpBuilder *const bldr{&builder};
- getStmtCtx().attachCleanup([=]() {
- fir::factory::genFreememIfAllocated(
- *bldr, loc,
- fir::MutableBoxValue{tempStorage, /*lenParams=*/{},
- fir::MutableProperties{}});
- });
- return temp;
- }
-
- /// Generate scalar CHARACTER conditional with proper length handling.
- template <typename T>
- std::optional<hlfir::EntityWithAttributes> genCharacterConditional(
- const Fortran::evaluate::ConditionalExpr<T> &condExpr) {
- const mlir::Location loc{getLoc()};
- fir::FirOpBuilder &builder{getBuilder()};
- const mlir::Type resultType{Fortran::lower::translateSomeExprToFIRType(
- converter, toEvExpr(condExpr))};
- const mlir::Type elementType{hlfir::getFortranElementType(resultType)};
- if (auto charType = mlir::dyn_cast<fir::CharacterType>(elementType)) {
- if (charType.hasConstantLen()) {
- llvm::SmallVector<mlir::Value, 1> typeParams;
- const mlir::Value len{builder.createIntegerConstant(
- loc, builder.getCharacterLengthType(), charType.getLen())};
- typeParams.push_back(len);
- return hlfir::EntityWithAttributes{
- genScalarConditional(condExpr, elementType, typeParams)};
- }
- // Non-constant/varying length: use allocatable conditional to get length
- // from selected branch.
- return hlfir::EntityWithAttributes{
- genAllocatableConditional(condExpr, elementType, ".cond.char")};
- }
- return std::nullopt;
- }
-
/// Conditional expression (Fortran 2023)
template <typename T>
hlfir::EntityWithAttributes
gen(const Fortran::evaluate::ConditionalExpr<T> &condExpr) {
- const int rank{condExpr.Rank()};
mlir::Type resultType{Fortran::lower::translateSomeExprToFIRType(
converter, toEvExpr(condExpr))};
if (fir::isRecordWithTypeParameters(
hlfir::getFortranElementType(resultType)))
TODO(getLoc(), "conditional expression with length-parameterized "
"derived type");
- // Arrays: handle early to avoid unnecessary type checks.
- // Per F2023 10.1.4(7), the shape is determined by the chosen branch.
- if (rank != 0) {
- return hlfir::EntityWithAttributes{
- genAllocatableConditional(condExpr, resultType, ".cond.array")};
- }
- // CHARACTER scalars require special handling for type parameters.
- if constexpr (T::category == Fortran::common::TypeCategory::Character) {
- if (auto result = genCharacterConditional(condExpr))
- return *result;
- }
- // Scalar types (INTEGER, REAL, COMPLEX, LOGICAL, UNSIGNED, Derived).
+ // Trivial scalar types (INTEGER, REAL, COMPLEX, LOGICAL, UNSIGNED)
+ // use fir.if SSA results directly — no temporary needed.
const mlir::Type elementType{hlfir::getFortranElementType(resultType)};
- if (fir::isPolymorphicType(resultType))
- return hlfir::EntityWithAttributes{
- genAllocatableConditional(condExpr, resultType, ".cond.polymorphic")};
- if (fir::isa_trivial(elementType))
+ if (condExpr.Rank() == 0 && !fir::isPolymorphicType(resultType) &&
+ fir::isa_trivial(elementType))
return hlfir::EntityWithAttributes{
genTrivialScalarConditional(condExpr, elementType)};
- return hlfir::EntityWithAttributes{
- genScalarConditional(condExpr, elementType, {})};
+ // All other cases: arrays, CHARACTER, polymorphic, non-trivial derived.
+ // Emit hlfir.conditional to delay materialization to bufferization.
+ return hlfir::EntityWithAttributes{genConditionalOp(
+ condExpr, elementType, fir::isPolymorphicType(resultType))};
}
hlfir::EntityWithAttributes
diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIRDialect.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIRDialect.cpp
index e0fee2f4ab3db..b2adc4b07adf5 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIRDialect.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIRDialect.cpp
@@ -229,6 +229,25 @@ mlir::Type hlfir::getExprType(mlir::Type variableType) {
isPolymorphic);
}
+mlir::Type hlfir::getVariableType(hlfir::ExprType exprType) {
+ const mlir::Type dataType{hlfir::getFortranElementOrSequenceType(exprType)};
+ // Polymorphic: fir.class<T>.
+ if (exprType.isPolymorphic())
+ return fir::ClassType::get(dataType);
+ // Non-polymorphic arrays need a box to carry shape information.
+ if (exprType.isArray())
+ return fir::BoxType::get(dataType);
+ // Scalar dynamic-length character: fir.boxchar<kind>.
+ if (auto charTy{mlir::dyn_cast<fir::CharacterType>(dataType)})
+ if (charTy.hasDynamicLen())
+ return fir::BoxCharType::get(dataType.getContext(), charTy.getFKind());
+ // Scalar derived type with type parameters: fir.box<T>.
+ if (fir::isRecordWithTypeParameters(dataType))
+ return fir::BoxType::get(dataType);
+ // Simple scalar: fir.ref<T>.
+ return fir::ReferenceType::get(dataType);
+}
+
bool hlfir::isFortranIntegerScalarOrArrayObject(mlir::Type type) {
if (isBoxAddressType(type))
return false;
diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
index 9de2d718a9c3a..e5e0b5728a104 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
@@ -2460,6 +2460,37 @@ llvm::LogicalResult hlfir::EvaluateInMemoryOp::verify() {
return mlir::success();
}
+//===----------------------------------------------------------------------===//
+// ConditionalOp
+//===----------------------------------------------------------------------===//
+
+void hlfir::ConditionalOp::build(mlir::OpBuilder &builder,
+ mlir::OperationState &odsState,
+ mlir::Type resultType, mlir::Value condition) {
+ odsState.addTypes(resultType);
+ odsState.addOperands(condition);
+ // Create the then and else regions, each with one empty block.
+ odsState.addRegion()->emplaceBlock();
+ odsState.addRegion()->emplaceBlock();
+}
+
+llvm::LogicalResult hlfir::ConditionalOp::verify() {
+ if (!mlir::isa<hlfir::ExprType>(getResult().getType()))
+ return emitOpError("result must be an hlfir.expr type");
+ const auto checkRegion = [&](mlir::Region ®ion,
+ llvm::StringRef name) -> llvm::LogicalResult {
+ if (!mlir::isa_and_nonnull<hlfir::YieldOp>(getTerminator(region)))
+ return emitOpError(name)
+ << " region must be terminated by an hlfir.yield";
+ return mlir::success();
+ };
+ if (const auto res = checkRegion(getThenRegion(), "then"); failed(res))
+ return res;
+ if (const auto res = checkRegion(getElseRegion(), "else"); failed(res))
+ return res;
+ return mlir::success();
+}
+
#include "flang/Optimizer/HLFIR/HLFIROpInterfaces.cpp.inc"
#define GET_OP_CLASSES
#include "flang/Optimizer/HLFIR/HLFIREnums.cpp.inc"
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
index c5bad209ef8db..3c0686e2f0f45 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
@@ -27,6 +27,7 @@
#include "flang/Optimizer/HLFIR/Passes.h"
#include "flang/Optimizer/OpenMP/Passes.h"
#include "mlir/IR/Dominance.h"
+#include "mlir/IR/IRMapping.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
@@ -39,6 +40,25 @@ namespace hlfir {
namespace {
+/// Cast \p temp to \p targetType so it can be used as a fir.if result.
+/// createTempFromMold may produce a raw ref or unboxed type that
diff ers
+/// from the ExprType-derived result type (e.g. static vs dynamic extents).
+static hlfir::Entity castTempToResultType(mlir::Location loc,
+ fir::FirOpBuilder &builder,
+ hlfir::Entity temp,
+ mlir::Type targetType) {
+ if (temp.getType() == targetType)
+ return temp;
+ if (mlir::isa<fir::BoxCharType>(targetType))
+ return hlfir::Entity{hlfir::genVariableBoxChar(loc, builder, temp)};
+ if (mlir::isa<fir::BaseBoxType>(targetType) &&
+ !mlir::isa<fir::BaseBoxType>(temp.getType())) {
+ return hlfir::genVariableBox(loc, builder, temp,
+ mlir::cast<fir::BaseBoxType>(targetType));
+ }
+ return hlfir::Entity{builder.createConvert(loc, targetType, temp)};
+}
+
/// Helper to create tuple from a bufferized expr storage and clean up
/// instruction flag. The storage is an HLFIR variable so that it can
/// be manipulated as a variable later (all shape and length information
@@ -867,6 +887,91 @@ struct CharExtremumOpConversion
}
};
+struct ConditionalOpConversion
+ : public mlir::OpConversionPattern<hlfir::ConditionalOp> {
+ using mlir::OpConversionPattern<hlfir::ConditionalOp>::OpConversionPattern;
+ explicit ConditionalOpConversion(mlir::MLIRContext *ctx)
+ : mlir::OpConversionPattern<hlfir::ConditionalOp>{ctx} {
+ // This pattern recursively converts nested ConditionalOp's
+ // by cloning and then converting them, so we have to allow
+ // for recursive pattern application. The recursion is bounded
+ // by the nesting level of ConditionalOp's.
+ setHasBoundedRewriteRecursion();
+ }
+ llvm::LogicalResult
+ matchAndRewrite(hlfir::ConditionalOp condOp, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const override {
+ const mlir::Location loc{condOp->getLoc()};
+ fir::FirOpBuilder builder(rewriter, condOp.getOperation());
+ HLFIRListener listener{builder, rewriter};
+ builder.setListener(&listener);
+ // Use ExprType to ensure both branches produce identical MLIR temp types.
+ const auto exprType{
+ mlir::cast<hlfir::ExprType>(condOp.getResult().getType())};
+ const mlir::Type tempBaseType{hlfir::getVariableType(exprType)};
+
+ // Emit one branch: clone the region body, delegate buffer management
+ // to hlfir.associate (which the framework will convert via
+ // AssociateOpConversion, enabling buffer forwarding from producers
+ // like hlfir.elemental without an extra copy).
+ auto emitBranch = [&](mlir::Region ®ion) {
+ mlir::IRMapping mapper;
+ auto yield{mlir::cast<hlfir::YieldOp>(region.front().getTerminator())};
+ const mlir::Value yieldedEntity{yield.getEntity()};
+
+ // Clone all non-terminator ops from the branch body.
+ for (auto &op : region.front().without_terminator())
+ builder.clone(op, mapper);
+
+ mlir::Value exprVal{mapper.lookupOrDefault(yieldedEntity)};
+ bool createdAsExpr = false;
+ // Yielded variables are not hlfir.expr: wrap in a non-move as_expr
+ if (!mlir::isa<hlfir::ExprType>(exprVal.getType())) {
+ exprVal = hlfir::AsExprOp::create(builder, loc, exprVal);
+ createdAsExpr = true;
+ }
+
+ // Associate to obtain a variable + mustFree pair; buffer ownership
+ // transfers out via fir.result (no hlfir.end_associate needed).
+ auto associate = hlfir::AssociateOp::create(
+ builder, loc, exprVal, ".tmp.cond", /*shape=*/mlir::Value{},
+ /*typeparams=*/mlir::ValueRange{}, fir::FortranVariableFlagsAttr{});
+
+ // Replay the yield cleanup after the as_expr/associate so the copy
+ // reads the yielded storage before that storage is destroyed.
+ if (!yield.getCleanup().empty())
+ for (auto &op : yield.getCleanup().front().without_terminator())
+ builder.clone(op, mapper);
+
+ // If we created an hlfir.as_expr, add a destroy for it so
+ // AssociateOpConversion can forward its buffer.
+ if (createdAsExpr)
+ hlfir::DestroyOp::create(builder, loc, exprVal);
+
+ // Forward storage + mustFree, casting to the canonical result type.
+ hlfir::Entity var{associate.getResult(0)};
+ var = castTempToResultType(loc, builder, var, tempBaseType);
+ fir::ResultOp::create(builder, loc,
+ mlir::ValueRange{var, associate.getResult(2)});
+ };
+
+ // Generate fir.if returning (temp, mustFree) as two results.
+ auto ifOp{builder.genIfOp(
+ loc, /*resultTypes=*/{tempBaseType, builder.getI1Type()},
+ adaptor.getCondition(),
+ /*withElseRegion=*/true)};
+ ifOp.genThen([&]() { emitBranch(condOp.getThenRegion()); })
+ .genElse([&]() { emitBranch(condOp.getElseRegion()); })
+ .end();
+ // Package fir.if results into the bufferized expr tuple.
+ const mlir::Value bufferizedExpr{
+ packageBufferizedExpr(loc, builder, hlfir::Entity{ifOp.getResults()[0]},
+ ifOp.getResults()[1])};
+ rewriter.replaceOp(condOp, bufferizedExpr);
+ return mlir::success();
+ }
+};
+
struct EvaluateInMemoryOpConversion
: public mlir::OpConversionPattern<hlfir::EvaluateInMemoryOp> {
using mlir::OpConversionPattern<
@@ -902,12 +1007,13 @@ class BufferizeHLFIR : public hlfir::impl::BufferizeHLFIRBase<BufferizeHLFIR> {
auto module = this->getOperation();
auto *context = &getContext();
mlir::RewritePatternSet patterns(context);
- patterns.insert<ApplyOpConversion, AsExprOpConversion, AssignOpConversion,
- AssociateOpConversion, CharExtremumOpConversion,
- ConcatOpConversion, DestroyOpConversion,
- EndAssociateOpConversion, EvaluateInMemoryOpConversion,
- NoReassocOpConversion, SetLengthOpConversion,
- ShapeOfOpConversion, GetLengthOpConversion>(context);
+ patterns.insert<
+ ApplyOpConversion, AsExprOpConversion, AssignOpConversion,
+ AssociateOpConversion, CharExtremumOpConversion, ConcatOpConversion,
+ ConditionalOpConversion, DestroyOpConversion, EndAssociateOpConversion,
+ EvaluateInMemoryOpConversion, NoReassocOpConversion,
+ SetLengthOpConversion, ShapeOfOpConversion, GetLengthOpConversion>(
+ context);
patterns.insert<ElementalOpConversion>(context, optimizeEmptyElementals);
mlir::ConversionTarget target(*context);
// Note that YieldElementOp is not marked as an illegal operation.
@@ -915,7 +1021,8 @@ class BufferizeHLFIR : public hlfir::impl::BufferizeHLFIRBase<BufferizeHLFIR> {
// conversion pattern to YieldElementOp itself. If any YieldElementOp
// survives this pass, the verifier will detect it because it has to be
// a child of ElementalOp and ElementalOp's are explicitly illegal.
- target.addIllegalOp<hlfir::ApplyOp, hlfir::AssociateOp, hlfir::ElementalOp,
+ target.addIllegalOp<hlfir::ApplyOp, hlfir::AssociateOp,
+ hlfir::ConditionalOp, hlfir::ElementalOp,
hlfir::EndAssociateOp, hlfir::SetLengthOp>();
target.markUnknownOpDynamicallyLegal([](mlir::Operation *op) {
diff --git a/flang/test/HLFIR/bufferize-conditional.fir b/flang/test/HLFIR/bufferize-conditional.fir
new file mode 100644
index 0000000000000..4d72cd07c7029
--- /dev/null
+++ b/flang/test/HLFIR/bufferize-conditional.fir
@@ -0,0 +1,452 @@
+// Test hlfir.conditional bufferization (ConditionalOpConversion).
+// RUN: fir-opt --bufferize-hlfir %s | FileCheck %s
+
+// --- Test 1: Scalar non-polymorphic derived type ---
+// Both branches yield a !fir.ref<!fir.type<T>> variable. A non-move as_expr
+// copies each into a stack temp (mustFree=false); no cast needed.
+func.func @test_scalar_derived(%cond: i1, %a: !fir.ref<!fir.type<_Tt{x:i32}>>, %b: !fir.ref<!fir.type<_Tt{x:i32}>>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.type<_Tt{x:i32}>> {
+ hlfir.yield %a : !fir.ref<!fir.type<_Tt{x:i32}>>
+ } else {
+ hlfir.yield %b : !fir.ref<!fir.type<_Tt{x:i32}>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.type<_Tt{x:i32}>>
+ return
+}
+// CHECK-LABEL: func.func @test_scalar_derived(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.ref<!fir.type<_Tt{x:i32}>>, %[[B:.*]]: !fir.ref<!fir.type<_Tt{x:i32}>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.ref<!fir.type<_Tt{x:i32}>>, i1) {
+// CHECK: %[[T1:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[A]] to %[[T1]]#0 temporary_lhs
+// CHECK: %[[FALSE1:.*]] = arith.constant false
+// CHECK: fir.result %[[T1]]#0, %[[FALSE1]] : !fir.ref<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: } else {
+// CHECK: %[[T2:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[B]] to %[[T2]]#0 temporary_lhs
+// CHECK: %[[FALSE2:.*]] = arith.constant false
+// CHECK: fir.result %[[T2]]#0, %[[FALSE2]] : !fir.ref<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: }
+
+// --- Test 2: Scalar polymorphic (class) ---
+// Both branches yield a !fir.class<!fir.type<T>> variable. A non-move as_expr
+// allocates a polymorphic temp via the runtime, copies into it, and returns
+// mustFree=true.
+func.func @test_scalar_polymorphic(%cond: i1, %a: !fir.class<!fir.type<_Tt{x:i32}>>, %b: !fir.class<!fir.type<_Tt{x:i32}>>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.type<_Tt{x:i32}>?> {
+ hlfir.yield %a : !fir.class<!fir.type<_Tt{x:i32}>>
+ } else {
+ hlfir.yield %b : !fir.class<!fir.type<_Tt{x:i32}>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.type<_Tt{x:i32}>?>
+ return
+}
+// CHECK-LABEL: func.func @test_scalar_polymorphic(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.class<!fir.type<_Tt{x:i32}>>, %[[B:.*]]: !fir.class<!fir.type<_Tt{x:i32}>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.class<!fir.type<_Tt{x:i32}>>, i1) {
+// CHECK: fir.embox %{{.*}} source_box %[[A]]
+// CHECK: fir.call @_FortranAAllocatableAllocate
+// CHECK: %[[T1:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[A]] to %[[T1]]#0 temporary_lhs
+// CHECK: %[[TRUE1:.*]] = arith.constant true
+// CHECK: fir.result %[[T1]]#0, %[[TRUE1]] : !fir.class<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: } else {
+// CHECK: fir.embox %{{.*}} source_box %[[B]]
+// CHECK: fir.call @_FortranAAllocatableAllocate
+// CHECK: %[[T2:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[B]] to %[[T2]]#0 temporary_lhs
+// CHECK: %[[TRUE2:.*]] = arith.constant true
+// CHECK: fir.result %[[T2]]#0, %[[TRUE2]] : !fir.class<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: }
+
+// --- Test 3: Polymorphic function results with cleanup region ---
+// The branch body contains the call + declare; cleanup ops (Destroy + freemem)
+// are in the yield's cleanup region. Because that cleanup would destroy the
+// yielded storage, ConditionalOp must use non-move as_expr semantics here so
+// AssociateOpConversion makes a copy that survives the replayed cleanup.
+func.func @test_poly_func_result_cleanup(%cond: i1) {
+ %alloca = fir.alloca !fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>> {bindc_name = ".result"}
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.type<_Tt{x:i32}>?> {
+ %1 = fir.call @_QPmake_poly() fastmath<contract> : () -> !fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>
+ fir.save_result %1 to %alloca : !fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>, !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %2 = fir.load %alloca : !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %3:2 = hlfir.declare %2 {uniq_name = ".tmp.func_result"} : (!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>) -> (!fir.class<!fir.type<_Tt{x:i32}>>, !fir.class<!fir.type<_Tt{x:i32}>>)
+ hlfir.yield %3#0 : !fir.class<!fir.type<_Tt{x:i32}>> cleanup {
+ %4 = fir.load %alloca : !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %5 = fir.convert %4 : (!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>) -> !fir.box<none>
+ fir.call @_FortranADestroy(%5) fastmath<contract> : (!fir.box<none>) -> ()
+ %6 = fir.load %alloca : !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %7 = fir.box_addr %6 : (!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>) -> !fir.heap<!fir.type<_Tt{x:i32}>>
+ %8 = fir.convert %7 : (!fir.heap<!fir.type<_Tt{x:i32}>>) -> i64
+ %c0 = arith.constant 0 : i64
+ %9 = arith.cmpi ne, %8, %c0 : i64
+ fir.if %9 {
+ fir.freemem %7 : !fir.heap<!fir.type<_Tt{x:i32}>>
+ }
+ }
+ } else {
+ %1 = fir.call @_QPmake_poly() fastmath<contract> : () -> !fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>
+ fir.save_result %1 to %alloca : !fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>, !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %2 = fir.load %alloca : !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %3:2 = hlfir.declare %2 {uniq_name = ".tmp.func_result"} : (!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>) -> (!fir.class<!fir.type<_Tt{x:i32}>>, !fir.class<!fir.type<_Tt{x:i32}>>)
+ hlfir.yield %3#0 : !fir.class<!fir.type<_Tt{x:i32}>> cleanup {
+ %4 = fir.load %alloca : !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %5 = fir.convert %4 : (!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>) -> !fir.box<none>
+ fir.call @_FortranADestroy(%5) fastmath<contract> : (!fir.box<none>) -> ()
+ %6 = fir.load %alloca : !fir.ref<!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>>
+ %7 = fir.box_addr %6 : (!fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>) -> !fir.heap<!fir.type<_Tt{x:i32}>>
+ %8 = fir.convert %7 : (!fir.heap<!fir.type<_Tt{x:i32}>>) -> i64
+ %c0 = arith.constant 0 : i64
+ %9 = arith.cmpi ne, %8, %c0 : i64
+ fir.if %9 {
+ fir.freemem %7 : !fir.heap<!fir.type<_Tt{x:i32}>>
+ }
+ }
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.type<_Tt{x:i32}>?>
+ return
+}
+// CHECK-LABEL: func.func @test_poly_func_result_cleanup(
+// Verify in the then branch: declare the func result, copy into a new temp
+// (because cleanup would free the original buffer), then replay cleanup ops,
+// then return the copy with mustFree=true.
+// CHECK: fir.if %{{.*}} -> (!fir.class<!fir.type<_Tt{x:i32}>>, i1) {
+// CHECK: %[[DECL:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp.func_result"}
+// CHECK: %[[COPY:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[DECL]]#0 to %[[COPY]]#0 temporary_lhs
+// CHECK: %[[TRUE:.*]] = arith.constant true
+// CHECK: fir.call @_FortranADestroy
+// CHECK: fir.if
+// CHECK: fir.freemem
+// CHECK: }
+// CHECK: fir.result %[[COPY]]#0, %[[TRUE]] : !fir.class<!fir.type<_Tt{x:i32}>>, i1
+
+// --- Test 4: Array with static extents ---
+// ExprType is !hlfir.expr<?xi32>, mold has static shape (10xi32).
+// A non-move as_expr allocates a heap temp and copies into it (mustFree=true);
+// castTempToResultType then converts the static-extent ref to a dynamic-extent
+// box via fir.convert + fir.embox.
+func.func @test_array_static(%cond: i1, %a: !fir.ref<!fir.array<10xi32>>, %b: !fir.ref<!fir.array<10xi32>>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<?xi32> {
+ hlfir.yield %a : !fir.ref<!fir.array<10xi32>>
+ } else {
+ hlfir.yield %b : !fir.ref<!fir.array<10xi32>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<?xi32>
+ return
+}
+// CHECK-LABEL: func.func @test_array_static(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.ref<!fir.array<10xi32>>, %[[B:.*]]: !fir.ref<!fir.array<10xi32>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.box<!fir.array<?xi32>>, i1) {
+// Copy into a heap temp, then embox to match the box result type:
+// CHECK: %[[MEM1:.*]] = fir.allocmem !fir.array<10xi32>
+// CHECK: %[[DECL1:.*]]:2 = hlfir.declare %[[MEM1]]
+// CHECK: hlfir.assign %[[A]] to %[[DECL1]]#0 temporary_lhs
+// CHECK: %[[TRUE1:.*]] = arith.constant true
+// CHECK: %[[CVT1:.*]] = fir.convert %{{.*}} : (!fir.ref<!fir.array<10xi32>>) -> !fir.ref<!fir.array<?xi32>>
+// CHECK: %[[BOX1:.*]] = fir.embox %[[CVT1]](%{{.*}}) : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<?xi32>>
+// CHECK: fir.result %[[BOX1]], %[[TRUE1]] : !fir.box<!fir.array<?xi32>>, i1
+// CHECK: } else {
+// CHECK: %[[MEM2:.*]] = fir.allocmem !fir.array<10xi32>
+// CHECK: %[[DECL2:.*]]:2 = hlfir.declare %[[MEM2]]
+// CHECK: hlfir.assign %[[B]] to %[[DECL2]]#0 temporary_lhs
+// CHECK: %[[TRUE2:.*]] = arith.constant true
+// CHECK: %[[CVT2:.*]] = fir.convert %{{.*}} : (!fir.ref<!fir.array<10xi32>>) -> !fir.ref<!fir.array<?xi32>>
+// CHECK: %[[BOX2:.*]] = fir.embox %[[CVT2]](%{{.*}}) : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<?xi32>>
+// CHECK: fir.result %[[BOX2]], %[[TRUE2]] : !fir.box<!fir.array<?xi32>>, i1
+// CHECK: }
+
+// --- Test 5: Scalar character (static length) ---
+// For static-length characters, tempBaseType is !fir.ref<!fir.char<1,N>>.
+// A non-move as_expr copies the yielded variable into a stack temp
+// (mustFree=false).
+func.func @test_scalar_char(%cond: i1, %a: !fir.ref<!fir.char<1,20>>, %b: !fir.ref<!fir.char<1,20>>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.char<1,20>> {
+ hlfir.yield %a : !fir.ref<!fir.char<1,20>>
+ } else {
+ hlfir.yield %b : !fir.ref<!fir.char<1,20>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.char<1,20>>
+ return
+}
+// CHECK-LABEL: func.func @test_scalar_char(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.ref<!fir.char<1,20>>, %[[B:.*]]: !fir.ref<!fir.char<1,20>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.ref<!fir.char<1,20>>, i1) {
+// CHECK: %[[T1:.*]]:2 = hlfir.declare %{{.*}} typeparams %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[A]] to %[[T1]]#0 temporary_lhs
+// CHECK: %[[FALSE1:.*]] = arith.constant false
+// CHECK: fir.result %[[T1]]#0, %[[FALSE1]] : !fir.ref<!fir.char<1,20>>, i1
+// CHECK: } else {
+// CHECK: %[[T2:.*]]:2 = hlfir.declare %{{.*}} typeparams %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[B]] to %[[T2]]#0 temporary_lhs
+// CHECK: %[[FALSE2:.*]] = arith.constant false
+// CHECK: fir.result %[[T2]]#0, %[[FALSE2]] : !fir.ref<!fir.char<1,20>>, i1
+// CHECK: }
+
+// --- Test 6: Forwarding (as_expr with move, type matches, inside region) ---
+// When hlfir.as_expr has move=true and var type == tempBaseType,
+// forward directly without creating an extra temp.
+func.func @test_forwarding(%cond: i1) {
+ %c10 = arith.constant 10 : index
+ %shape = fir.shape %c10 : (index) -> !fir.shape<1>
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<?xi32> {
+ %mem1 = fir.allocmem !fir.array<?xi32>, %c10 {uniq_name = ".tmp"}
+ %decl1:2 = hlfir.declare %mem1(%shape) {uniq_name = ".tmp"} : (!fir.heap<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.heap<!fir.array<?xi32>>)
+ %true = arith.constant true
+ %expr1 = hlfir.as_expr %decl1#0 move %true : (!fir.box<!fir.array<?xi32>>, i1) -> !hlfir.expr<?xi32>
+ hlfir.yield %expr1 : !hlfir.expr<?xi32>
+ } else {
+ %mem2 = fir.allocmem !fir.array<?xi32>, %c10 {uniq_name = ".tmp"}
+ %decl2:2 = hlfir.declare %mem2(%shape) {uniq_name = ".tmp"} : (!fir.heap<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.heap<!fir.array<?xi32>>)
+ %true = arith.constant true
+ %expr2 = hlfir.as_expr %decl2#0 move %true : (!fir.box<!fir.array<?xi32>>, i1) -> !hlfir.expr<?xi32>
+ hlfir.yield %expr2 : !hlfir.expr<?xi32>
+ }
+ hlfir.destroy %0 : !hlfir.expr<?xi32>
+ return
+}
+// CHECK-LABEL: func.func @test_forwarding(
+// CHECK: %[[IF:.*]]:2 = fir.if %{{.*}} -> (!fir.box<!fir.array<?xi32>>, i1) {
+// No temp allocation or assign — directly forward the var and mustFree:
+// CHECK: %[[MEM1:.*]] = fir.allocmem !fir.array<?xi32>
+// CHECK: %[[DECL1:.*]]:2 = hlfir.declare %[[MEM1]]
+// CHECK-NOT: hlfir.assign
+// CHECK: %[[TRUE1:.*]] = arith.constant true
+// CHECK: fir.result %[[DECL1]]#0, %[[TRUE1]] : !fir.box<!fir.array<?xi32>>, i1
+// CHECK: } else {
+// CHECK: %[[MEM2:.*]] = fir.allocmem !fir.array<?xi32>
+// CHECK: %[[DECL2:.*]]:2 = hlfir.declare %[[MEM2]]
+// CHECK-NOT: hlfir.assign
+// CHECK: %[[TRUE2:.*]] = arith.constant true
+// CHECK: fir.result %[[DECL2]]#0, %[[TRUE2]] : !fir.box<!fir.array<?xi32>>, i1
+// CHECK: }
+
+// --- Test 7: Character array with static length ---
+// tempBaseType is !fir.box<!fir.array<?x!fir.char<1,15>>>. A non-move as_expr
+// allocates a heap temp and copies into it (mustFree=true); castTempToResultType
+// then converts + emboxes.
+func.func @test_char_array_static_len(%cond: i1, %a: !fir.ref<!fir.array<3x!fir.char<1,15>>>, %b: !fir.ref<!fir.array<3x!fir.char<1,15>>>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<?x!fir.char<1,15>> {
+ hlfir.yield %a : !fir.ref<!fir.array<3x!fir.char<1,15>>>
+ } else {
+ hlfir.yield %b : !fir.ref<!fir.array<3x!fir.char<1,15>>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<?x!fir.char<1,15>>
+ return
+}
+// CHECK-LABEL: func.func @test_char_array_static_len(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.ref<!fir.array<3x!fir.char<1,15>>>, %[[B:.*]]: !fir.ref<!fir.array<3x!fir.char<1,15>>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.box<!fir.array<?x!fir.char<1,15>>>, i1) {
+// Copy into a heap temp, then convert + embox:
+// CHECK: %[[MEM1:.*]] = fir.allocmem !fir.array<3x!fir.char<1,15>>
+// CHECK: %[[DECL1:.*]]:2 = hlfir.declare %[[MEM1]]
+// CHECK: hlfir.assign %[[A]] to %[[DECL1]]#0 temporary_lhs
+// CHECK: %[[TRUE1:.*]] = arith.constant true
+// CHECK: %[[CVT1:.*]] = fir.convert %{{.*}} : (!fir.ref<!fir.array<3x!fir.char<1,15>>>) -> !fir.ref<!fir.array<?x!fir.char<1,15>>>
+// CHECK: %[[BOX1:.*]] = fir.embox %[[CVT1]](%{{.*}}) : (!fir.ref<!fir.array<?x!fir.char<1,15>>>, !fir.shape<1>) -> !fir.box<!fir.array<?x!fir.char<1,15>>>
+// CHECK: fir.result %[[BOX1]], %[[TRUE1]] : !fir.box<!fir.array<?x!fir.char<1,15>>>, i1
+// CHECK: } else {
+// CHECK: %[[MEM2:.*]] = fir.allocmem !fir.array<3x!fir.char<1,15>>
+// CHECK: %[[DECL2:.*]]:2 = hlfir.declare %[[MEM2]]
+// CHECK: hlfir.assign %[[B]] to %[[DECL2]]#0 temporary_lhs
+// CHECK: %[[TRUE2:.*]] = arith.constant true
+// CHECK: %[[CVT2:.*]] = fir.convert %{{.*}} : (!fir.ref<!fir.array<3x!fir.char<1,15>>>) -> !fir.ref<!fir.array<?x!fir.char<1,15>>>
+// CHECK: %[[BOX2:.*]] = fir.embox %[[CVT2]](%{{.*}}) : (!fir.ref<!fir.array<?x!fir.char<1,15>>>, !fir.shape<1>) -> !fir.box<!fir.array<?x!fir.char<1,15>>>
+// CHECK: fir.result %[[BOX2]], %[[TRUE2]] : !fir.box<!fir.array<?x!fir.char<1,15>>>, i1
+// CHECK: }
+
+// --- Test 8: Scalar character (dynamic length) ---
+// tempBaseType is !fir.boxchar<1>. A non-move as_expr copies the yielded
+// variable into a stack temp (mustFree=false).
+func.func @test_scalar_char_dynamic(%cond: i1, %a: !fir.boxchar<1>, %b: !fir.boxchar<1>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ hlfir.yield %a : !fir.boxchar<1>
+ } else {
+ hlfir.yield %b : !fir.boxchar<1>
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.char<1,?>>
+ return
+}
+// CHECK-LABEL: func.func @test_scalar_char_dynamic(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.boxchar<1>, %[[B:.*]]: !fir.boxchar<1>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.boxchar<1>, i1) {
+// CHECK: %[[T1:.*]]:2 = hlfir.declare %{{.*}} typeparams %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[A]] to %[[T1]]#0 temporary_lhs
+// CHECK: %[[FALSE1:.*]] = arith.constant false
+// CHECK: fir.result %[[T1]]#0, %[[FALSE1]] : !fir.boxchar<1>, i1
+// CHECK: } else {
+// CHECK: %[[T2:.*]]:2 = hlfir.declare %{{.*}} typeparams %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[B]] to %[[T2]]#0 temporary_lhs
+// CHECK: %[[FALSE2:.*]] = arith.constant false
+// CHECK: fir.result %[[T2]]#0, %[[FALSE2]] : !fir.boxchar<1>, i1
+// CHECK: }
+
+// --- Test 9: Asymmetric branches (non-polymorphic + polymorphic) ---
+// One branch yields !fir.ref (non-polymorphic), the other !fir.class
+// (polymorphic). ExprType is polymorphic, so tempBaseType is !fir.class.
+// Each branch copies its variable into a temp; the non-poly branch then emboxes
+// its ref temp to class.
+func.func @test_asymmetric_poly(%cond: i1, %a: !fir.ref<!fir.type<_Tt{x:i32}>>, %b: !fir.class<!fir.type<_Tt{x:i32}>>) {
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.type<_Tt{x:i32}>?> {
+ hlfir.yield %a : !fir.ref<!fir.type<_Tt{x:i32}>>
+ } else {
+ hlfir.yield %b : !fir.class<!fir.type<_Tt{x:i32}>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.type<_Tt{x:i32}>?>
+ return
+}
+// CHECK-LABEL: func.func @test_asymmetric_poly(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[A:.*]]: !fir.ref<!fir.type<_Tt{x:i32}>>, %[[B:.*]]: !fir.class<!fir.type<_Tt{x:i32}>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.class<!fir.type<_Tt{x:i32}>>, i1) {
+// Non-poly branch: copy into a stack temp, then embox ref → class:
+// CHECK: %[[T1:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[A]] to %[[T1]]#0 temporary_lhs
+// CHECK: %[[FALSE:.*]] = arith.constant false
+// CHECK: %[[BOX:.*]] = fir.embox %[[T1]]#0 : (!fir.ref<!fir.type<_Tt{x:i32}>>) -> !fir.class<!fir.type<_Tt{x:i32}>>
+// CHECK: fir.result %[[BOX]], %[[FALSE]] : !fir.class<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: } else {
+// Poly branch: runtime-allocate a polymorphic temp and copy into it:
+// CHECK: fir.call @_FortranAAllocatableAllocate
+// CHECK: %[[T2:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[B]] to %[[T2]]#0 temporary_lhs
+// CHECK: %[[TRUE:.*]] = arith.constant true
+// CHECK: fir.result %[[T2]]#0, %[[TRUE]] : !fir.class<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: }
+
+// Helper declarations for test 3.
+func.func private @_QPmake_poly() -> !fir.class<!fir.heap<!fir.type<_Tt{x:i32}>>>
+func.func private @_FortranADestroy(!fir.box<none>) -> ()
+
+// --- Test 10: Forwarding with non-empty cleanup region ---
+// When forwarding (as_expr move, type matches), the cleanup region may
+// contain ops beyond the destroy of the forwarded entity — e.g.,
+// freeing argument temporaries after a function call.
+// The forwarding path must replay those cleanups while skipping only
+// the destroy whose operand is the forwarded entity.
+// Uses FIR-level cleanup ops (fir.freemem) so they survive lowering and
+// are visible in the CHECK output.
+func.func @test_forwarding_with_cleanup(%cond: i1, %arg: !fir.boxchar<1>) {
+ %result_alloca = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>> {bindc_name = ".result"}
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ // Simulate a heap-allocated argument temporary.
+ %c10 = arith.constant 10 : index
+ %heap = fir.allocmem !fir.char<1,?>(%c10 : index) {uniq_name = ".tmp.arg"}
+ // Simulate function call returning allocatable character result.
+ %call = fir.call @_QPfunc_alloc_char(%arg) : (!fir.boxchar<1>) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+ fir.save_result %call to %result_alloca : !fir.box<!fir.heap<!fir.char<1,?>>>, !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %loaded = fir.load %result_alloca : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
+ %addr = fir.box_addr %loaded : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> !fir.heap<!fir.char<1,?>>
+ %len = fir.box_elesize %loaded : (!fir.box<!fir.heap<!fir.char<1,?>>>) -> index
+ %boxchar = fir.emboxchar %addr, %len : (!fir.heap<!fir.char<1,?>>, index) -> !fir.boxchar<1>
+ %true = arith.constant true
+ %expr = hlfir.as_expr %boxchar move %true : (!fir.boxchar<1>, i1) -> !hlfir.expr<!fir.char<1,?>>
+ hlfir.yield %expr : !hlfir.expr<!fir.char<1,?>> cleanup {
+ hlfir.destroy %expr : !hlfir.expr<!fir.char<1,?>>
+ fir.freemem %heap : !fir.heap<!fir.char<1,?>>
+ }
+ } else {
+ hlfir.yield %arg : !fir.boxchar<1>
+ }
+ hlfir.assign %0 to %arg : !hlfir.expr<!fir.char<1,?>>, !fir.boxchar<1>
+ hlfir.destroy %0 : !hlfir.expr<!fir.char<1,?>>
+ return
+}
+func.func private @_QPfunc_alloc_char(!fir.boxchar<1>) -> !fir.box<!fir.heap<!fir.char<1,?>>>
+// CHECK-LABEL: func.func @test_forwarding_with_cleanup(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[ARG:.*]]: !fir.boxchar<1>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.boxchar<1>, i1) {
+// Forwarding: var and mustFree forwarded directly, no temp copy:
+// CHECK: %[[HEAP:.*]] = fir.allocmem !fir.char<1,?>
+// CHECK: fir.call @_QPfunc_alloc_char
+// CHECK: %[[BOXCHAR:.*]] = fir.emboxchar
+// CHECK: %[[TRUE:.*]] = arith.constant true
+// Cleanup replayed — arg temp freed:
+// CHECK: fir.freemem %[[HEAP]]
+// CHECK: fir.result %[[BOXCHAR]], %[[TRUE]] : !fir.boxchar<1>, i1
+// CHECK: } else {
+// Non-forwarding: variable copied into a stack temp (mustFree=false):
+// CHECK: %[[T:.*]]:2 = hlfir.declare %{{.*}} typeparams %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[ARG]] to %[[T]]#0 temporary_lhs
+// CHECK: %[[FALSE:.*]] = arith.constant false
+// CHECK: fir.result %[[T]]#0, %[[FALSE]] : !fir.boxchar<1>, i1
+// CHECK: }
+
+// --- Test 11: Array expr from hlfir.elemental (buffer forwarding) ---
+// When a branch yields an hlfir.expr produced by hlfir.elemental, the
+// elemental's heap buffer is forwarded directly via hlfir.associate without
+// an extra copy. The other branch yields a variable (exercises the
+// convert + embox path for castTempToResultType).
+func.func @test_elemental_forwarding(%cond: i1, %n: index, %a: !fir.ref<!fir.array<10xi32>>) {
+ %shape = fir.shape %n : (index) -> !fir.shape<1>
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<?xi32> {
+ %elem = hlfir.elemental %shape unordered : (!fir.shape<1>) -> !hlfir.expr<?xi32> {
+ ^bb0(%i: index):
+ %c42 = arith.constant 42 : i32
+ hlfir.yield_element %c42 : i32
+ }
+ hlfir.yield %elem : !hlfir.expr<?xi32>
+ } else {
+ hlfir.yield %a : !fir.ref<!fir.array<10xi32>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<?xi32>
+ return
+}
+// CHECK-LABEL: func.func @test_elemental_forwarding(
+// CHECK-SAME: %[[COND:.*]]: i1, %[[N:.*]]: index, %[[A:.*]]: !fir.ref<!fir.array<10xi32>>)
+// CHECK: %[[IF:.*]]:2 = fir.if %[[COND]] -> (!fir.box<!fir.array<?xi32>>, i1) {
+// Elemental bufferized: allocmem + loop fills the temp:
+// CHECK: %[[ELEM_BUF:.*]] = fir.allocmem !fir.array<?xi32>, %[[N]]
+// CHECK: %[[ELEM_DECL:.*]]:2 = hlfir.declare %[[ELEM_BUF]](%{{.*}})
+// CHECK: %[[TRUE1:.*]] = arith.constant true
+// CHECK: fir.do_loop
+// Buffer forwarded directly (no extra copy after loop):
+// CHECK: fir.result %[[ELEM_DECL]]#0, %[[TRUE1]] : !fir.box<!fir.array<?xi32>>, i1
+// CHECK: } else {
+// Variable yield: copied into a heap temp (mustFree=true), then converted + emboxed:
+// CHECK: %[[MEM:.*]] = fir.allocmem !fir.array<10xi32>
+// CHECK: %[[DECL:.*]]:2 = hlfir.declare %[[MEM]]
+// CHECK: hlfir.assign %[[A]] to %[[DECL]]#0 temporary_lhs
+// CHECK: %[[TRUE2:.*]] = arith.constant true
+// CHECK: %[[CVT:.*]] = fir.convert %{{.*}} : (!fir.ref<!fir.array<10xi32>>) -> !fir.ref<!fir.array<?xi32>>
+// CHECK: %[[BOX:.*]] = fir.embox %[[CVT]](%{{.*}}) : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> !fir.box<!fir.array<?xi32>>
+// CHECK: fir.result %[[BOX]], %[[TRUE2]] : !fir.box<!fir.array<?xi32>>, i1
+// CHECK: }
+
+// --- Test 12: Nested conditional (exercises setHasBoundedRewriteRecursion) ---
+// An inner hlfir.conditional inside an outer branch verifies that the dialect
+// conversion framework correctly re-applies ConditionalOpConversion to the
+// cloned inner op via bounded rewrite recursion.
+func.func @test_nested(%cond1: i1, %cond2: i1, %a: !fir.ref<!fir.type<_Tt{x:i32}>>, %b: !fir.ref<!fir.type<_Tt{x:i32}>>, %c: !fir.ref<!fir.type<_Tt{x:i32}>>) {
+ %0 = hlfir.conditional %cond1 : (i1) -> !hlfir.expr<!fir.type<_Tt{x:i32}>> {
+ %1 = hlfir.conditional %cond2 : (i1) -> !hlfir.expr<!fir.type<_Tt{x:i32}>> {
+ hlfir.yield %a : !fir.ref<!fir.type<_Tt{x:i32}>>
+ } else {
+ hlfir.yield %b : !fir.ref<!fir.type<_Tt{x:i32}>>
+ }
+ hlfir.yield %1 : !hlfir.expr<!fir.type<_Tt{x:i32}>>
+ } else {
+ hlfir.yield %c : !fir.ref<!fir.type<_Tt{x:i32}>>
+ }
+ hlfir.destroy %0 : !hlfir.expr<!fir.type<_Tt{x:i32}>>
+ return
+}
+// CHECK-LABEL: func.func @test_nested(
+// CHECK-SAME: %[[COND1:.*]]: i1, %[[COND2:.*]]: i1, %[[A:.*]]: !fir.ref<!fir.type<_Tt{x:i32}>>, %[[B:.*]]: !fir.ref<!fir.type<_Tt{x:i32}>>, %[[C:.*]]: !fir.ref<!fir.type<_Tt{x:i32}>>)
+// Outer fir.if:
+// CHECK: %[[OUTER:.*]]:2 = fir.if %[[COND1]] -> (!fir.ref<!fir.type<_Tt{x:i32}>>, i1) {
+// Inner fir.if (nested — proves bounded rewrite recursion works):
+// CHECK: %[[INNER:.*]]:2 = fir.if %[[COND2]] -> (!fir.ref<!fir.type<_Tt{x:i32}>>, i1) {
+// CHECK: %[[IT:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[A]] to %[[IT]]#0 temporary_lhs
+// CHECK: fir.result %[[IT]]#0, %{{.*}} : !fir.ref<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: } else {
+// CHECK: %[[IE:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[B]] to %[[IE]]#0 temporary_lhs
+// CHECK: fir.result %[[IE]]#0, %{{.*}} : !fir.ref<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: }
+// Inner results forwarded to outer fir.result:
+// CHECK: fir.result %[[INNER]]#0, %[[INNER]]#1 : !fir.ref<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: } else {
+// CHECK: %[[OE:.*]]:2 = hlfir.declare %{{.*}} {uniq_name = ".tmp"}
+// CHECK: hlfir.assign %[[C]] to %[[OE]]#0 temporary_lhs
+// CHECK: fir.result %[[OE]]#0, %{{.*}} : !fir.ref<!fir.type<_Tt{x:i32}>>, i1
+// CHECK: }
diff --git a/flang/test/HLFIR/invalid.fir b/flang/test/HLFIR/invalid.fir
index 91e9ed6c2e4c4..5b58f3272957e 100644
--- a/flang/test/HLFIR/invalid.fir
+++ b/flang/test/HLFIR/invalid.fir
@@ -1114,6 +1114,29 @@ func.func @bad_element_addr_4(%x: !fir.ref<!fir.array<20xf32>>, %y: !fir.ref<!fi
return
}
+// -----
+func.func @bad_conditional_empty_then_region(%cond: i1, %v0: f32) {
+ // expected-error at +1 {{'hlfir.conditional' op region #0 ('then_region') failed to verify constraint: region with 1 blocks}}
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<f32> {
+ } else {
+ hlfir.yield %v0 : f32
+ }
+ return
+}
+
+// -----
+func.func @bad_conditional_region_size(%cond: i1, %v0: f32, %v1: f32, %v2: f32) {
+ // expected-error at +1 {{'hlfir.conditional' op region #0 ('then_region') failed to verify constraint: region with 1 blocks}}
+ %0 = hlfir.conditional %cond : (i1) -> !hlfir.expr<f32> {
+ hlfir.yield %v0 : f32
+ ^bb1:
+ hlfir.yield %v1 : f32
+ } else {
+ hlfir.yield %v2 : f32
+ }
+ return
+}
+
// -----
func.func @bad_forall(%x : !fir.box<!fir.array<10xf32>>, %y: f32, %bad : !fir.ref<!fir.array<10xindex>>) {
// expected-error at +1 {{'hlfir.forall' op region #0 ('lb_region') failed to verify constraint: single block region that yields an integer scalar value}}
diff --git a/flang/test/Lower/HLFIR/conditional-expr.f90 b/flang/test/Lower/HLFIR/conditional-expr.f90
index 56d9bc4c45369..0c58cc1b67e7b 100644
--- a/flang/test/Lower/HLFIR/conditional-expr.f90
+++ b/flang/test/Lower/HLFIR/conditional-expr.f90
@@ -115,13 +115,10 @@ subroutine test_char_constant_len(flag)
str1 = "HELLO"
str2 = "WORLD"
result = (flag ? str1 : str2)
- ! Constant length: use scalar temp path.
- ! CHECK: %[[TEMP:.*]] = fir.alloca !fir.char<1,5> {bindc_name = ".cond.scalar"
- ! CHECK: %[[TEMP_DECL:.*]]:2 = hlfir.declare %[[TEMP]] typeparams {{.*}} {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[TEMP_DECL]]#0
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.char<1,5>> {
+ ! CHECK: hlfir.yield %{{.*}} : !fir.ref<!fir.char<1,5>>
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[TEMP_DECL]]#0
+ ! CHECK: hlfir.yield %{{.*}} : !fir.ref<!fir.char<1,5>>
! CHECK: }
end subroutine
@@ -133,16 +130,10 @@ subroutine test_char_deferred_len(flag)
str2 = "A MUCH LONGER STRING"
! Result length comes from selected branch
result = (flag ? str1 : str2)
- ! CHECK-DAG: %[[BOX_ALLOC:.*]] = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>> {bindc_name = ".cond.char"
- ! CHECK-DAG: %[[UNALLOC:.*]] = fir.zero_bits !fir.heap<!fir.char<1,?>>
- ! CHECK-DAG: %[[C0:.*]] = arith.constant 0 : index
- ! CHECK: %[[BOX:.*]] = fir.embox %[[UNALLOC]] typeparams %[[C0]]
- ! CHECK: fir.store %[[BOX]] to %{{.*}} : !fir.ref<!fir.box<!fir.heap<!fir.char<1,?>>>>
- ! CHECK: %[[BOX_DECL:.*]]:2 = hlfir.declare %[[BOX_ALLOC]] {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[BOX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[BOX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -153,16 +144,10 @@ subroutine test_array(flag)
arr1 = 1
arr2 = 2
result = (flag ? arr1 : arr2)
- ! CHECK: %[[BOX_ALLOC:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<{{.*}}xi32>>> {bindc_name = ".cond.array"
- ! CHECK: %[[UNALLOC:.*]] = fir.zero_bits !fir.heap<!fir.array<{{.*}}xi32>>
- ! CHECK: %[[SHAPE:.*]] = fir.shape
- ! CHECK: %[[BOX:.*]] = fir.embox %[[UNALLOC]](%[[SHAPE]])
- ! CHECK: fir.store %[[BOX]] to %[[BOX_ALLOC]]
- ! CHECK: %[[BOX_DECL:.*]]:2 = hlfir.declare %[[BOX_ALLOC]] {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[BOX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<?xi32> {
+ ! CHECK: hlfir.yield %{{.*}} : !fir.ref<!fir.array<10xi32>>
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[BOX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}} : !fir.ref<!fir.array<10xi32>>
! CHECK: }
end subroutine
@@ -176,12 +161,10 @@ subroutine test_derived_type(flag)
p1 = point(1.0, 2.0)
p2 = point(3.0, 4.0)
result = (flag ? p1 : p2)
- ! CHECK: %[[TEMP:.*]] = fir.alloca !fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}> {bindc_name = ".cond.scalar"
- ! CHECK: %[[TEMP_DECL:.*]]:2 = hlfir.declare %[[TEMP]] {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[TEMP_DECL]]#0 : !fir.ref<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>>, !fir.ref<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>>
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>> {
+ ! CHECK: hlfir.yield %{{.*}} : !fir.ref<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>>
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[TEMP_DECL]]#0 : !fir.ref<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>>, !fir.ref<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>>
+ ! CHECK: hlfir.yield %{{.*}} : !fir.ref<!fir.type<_QFtest_derived_typeTpoint{x:f32,y:f32}>>
! CHECK: }
end subroutine
@@ -227,12 +210,10 @@ subroutine test_assumed_length_char(flag, str1, str2)
character(len=*) :: str1, str2
character(len=100) :: result
result = (flag ? str1 : str2)
- ! Deferred length path since len=* is not constant
- ! CHECK: %[[BOX_ALLOC:.*]] = fir.alloca !fir.box<!fir.heap<!fir.char<1,?>>> {bindc_name = ".cond.char"
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to {{.*}} realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to {{.*}} realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -266,11 +247,10 @@ subroutine test_array_section(flag)
logical :: flag
integer :: arr1(20), arr2(20), result(10)
result = (flag ? arr1(1:10) : arr2(11:20))
- ! CHECK: %[[BOX_ALLOC:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<{{.*}}xi32>>> {bindc_name = ".cond.array"
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to {{.*}} realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<?xi32> {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to {{.*}} realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -280,11 +260,10 @@ subroutine test_noncontiguous_section(flag)
integer :: arr1(20), arr2(20), result(5)
! Non-contiguous stride-2 sections: result must be contiguous.
result = (flag ? arr1(1:10:2) : arr2(2:10:2))
- ! CHECK: %[[BOX_ALLOC:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<{{.*}}xi32>>> {bindc_name = ".cond.array"
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to {{.*}} realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<?xi32> {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to {{.*}} realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -296,17 +275,12 @@ subroutine test_polymorphic(flag, x, y)
logical :: flag
class(base_type), intent(in) :: x, y
type(base_type) :: result
- ! Polymorphic conditional: uses fir.class (not fir.box) to carry dynamic type.
+ ! Polymorphic conditional: uses hlfir.conditional with polymorphic expr type.
result = (flag ? x : y)
- ! CHECK: %[[BOX_ALLOC:.*]] = fir.alloca !fir.class<!fir.heap<!fir.type<_QFtest_polymorphicTbase_type{val:i32}>>> {bindc_name = ".cond.polymorphic"
- ! CHECK: %[[UNALLOC:.*]] = fir.zero_bits !fir.heap<!fir.type<_QFtest_polymorphicTbase_type{val:i32}>>
- ! CHECK: %[[BOX:.*]] = fir.embox %[[UNALLOC]] : (!fir.heap<!fir.type<_QFtest_polymorphicTbase_type{val:i32}>>) -> !fir.class<!fir.heap<!fir.type<_QFtest_polymorphicTbase_type{val:i32}>>>
- ! CHECK: fir.store %[[BOX]] to %[[BOX_ALLOC]]
- ! CHECK: %[[BOX_DECL:.*]]:2 = hlfir.declare %[[BOX_ALLOC]] {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[BOX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} {{.*}} {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[BOX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -318,14 +292,12 @@ subroutine test_polymorphic_array(flag, x, y)
logical :: flag
class(base_type), intent(in) :: x(:), y(:)
type(base_type), allocatable :: result(:)
- ! Polymorphic array: uses fir.class (not fir.box) for the allocatable temp.
+ ! Polymorphic array: hlfir.conditional with polymorphic array expr type.
result = (flag ? x : y)
- ! CHECK: fir.alloca !fir.class<!fir.heap<!fir.array<?x!fir.type<_QFtest_polymorphic_arrayTbase_type{val:i32}>>>> {bindc_name = ".cond.array"
- ! CHECK: %[[PA_DECL:.*]]:2 = hlfir.declare {{.*}} {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[PA_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} {{.*}} {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[PA_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -339,13 +311,10 @@ subroutine test_polymorphic_char_component(flag, x, y)
class(named_type), intent(in) :: x, y
type(named_type), allocatable :: result
result = (flag ? x : y)
- ! The alloca type proves fir.class is used for the polymorphic temp.
- ! CHECK: fir.alloca !fir.class<!fir.heap<!fir.type<_QFtest_polymorphic_char_componentTnamed_type{name:!fir.char<1,20>,id:i32}>>> {bindc_name = ".cond.polymorphic"
- ! CHECK: %[[PC_DECL:.*]]:2 = hlfir.declare {{.*}} {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[PC_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} {{.*}} {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[PC_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -358,15 +327,12 @@ subroutine test_mixed_type_class(flag, x, y)
type(base_type), intent(in) :: x
class(base_type), intent(in) :: y
type(base_type) :: result
- ! Mixed TYPE(t)/CLASS(t): GetType() returns polymorphic when either branch
- ! is polymorphic, so the result must use fir.class (not fir.box).
+ ! Mixed TYPE(t)/CLASS(t): result is polymorphic when either branch is.
result = (flag ? x : y)
- ! CHECK: fir.alloca !fir.class<!fir.heap<!fir.type<_QFtest_mixed_type_classTbase_type{val:i32}>>> {bindc_name = ".cond.polymorphic"
- ! CHECK: %[[MX_DECL:.*]]:2 = hlfir.declare {{.*}} {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[MX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} {{.*}} {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[MX_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: }
end subroutine
@@ -382,13 +348,37 @@ subroutine test_polymorphic_extends(flag, x, y)
class(base_type), intent(in) :: x, y
type(base_type) :: result
! Polymorphic with type hierarchy: x or y may hold extended_type at runtime.
- ! The lowering must use fir.class to preserve dynamic type info.
result = (flag ? x : y)
- ! CHECK: fir.alloca !fir.class<!fir.heap<!fir.type<_QFtest_polymorphic_extendsTbase_type{val:i32}>>> {bindc_name = ".cond.polymorphic"
- ! CHECK: %[[PE_DECL:.*]]:2 = hlfir.declare {{.*}} {uniq_name = ".cond.result"}
- ! CHECK: fir.if
- ! CHECK: hlfir.assign {{.*}} to %[[PE_DECL]]#0 realloc temporary_lhs
+ ! CHECK: %[[RESULT:.*]] = hlfir.conditional %{{.*}} {{.*}} {
+ ! CHECK: hlfir.yield %{{.*}}
! CHECK: } else {
- ! CHECK: hlfir.assign {{.*}} to %[[PE_DECL]]#0 realloc temporary_lhs
+ ! CHECK: hlfir.yield %{{.*}}
+ ! CHECK: }
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_chained_char_conditional(
+subroutine test_chained_char_conditional(flag1, flag2, flag3)
+ logical :: flag1, flag2, flag3
+ character(len=:), allocatable :: s1, s2, s3, s4, result
+ s1 = "A"
+ s2 = "BB"
+ s3 = "CCC"
+ s4 = "DDDD"
+ ! Chained conditional with
diff erent-length character branches.
+ result = (flag1 ? s1 : flag2 ? s2 : flag3 ? s3 : s4)
+ ! CHECK: %[[OUTER:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ ! CHECK: hlfir.yield %{{.*}}
+ ! CHECK: } else {
+ ! CHECK: %[[MID:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ ! CHECK: hlfir.yield %{{.*}}
+ ! CHECK: } else {
+ ! CHECK: %[[INNER:.*]] = hlfir.conditional %{{.*}} : (i1) -> !hlfir.expr<!fir.char<1,?>> {
+ ! CHECK: hlfir.yield %{{.*}}
+ ! CHECK: } else {
+ ! CHECK: hlfir.yield %{{.*}}
+ ! CHECK: }
+ ! CHECK: hlfir.yield %[[INNER]] : !hlfir.expr<!fir.char<1,?>>
+ ! CHECK: }
+ ! CHECK: hlfir.yield %[[MID]] : !hlfir.expr<!fir.char<1,?>>
! CHECK: }
end subroutine
More information about the flang-commits
mailing list