[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 &region, const auto &value) {
+      builder.setInsertionPointToStart(&region.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 &region,
+                               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 &region) {
+      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