[flang-commits] [flang] [flang] Region-based HLFIR operation for conditional expressions lowering (PR #194411)
via flang-commits
flang-commits at lists.llvm.org
Mon Apr 27 09:51:57 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
Author: Caroline Newcombe (cenewcombe)
<details>
<summary>Changes</summary>
Introduces `hlfir.conditional`, a region-based HLFIR operation that represents Fortran 2023 conditional expressions (10.1.2.3) with lazy branch evaluation.
## Motivation
The previous lowering emitted `fir.if` directly in ConvertExprToHLFIR with per-category strategies (scalar temp + assign, allocatable temp + realloc, CHARACTER-specific handling). This leaked bufferization concerns into lowering and required four separate code paths.
A naive approach of lowering directly to `fir.if` with the branch values as results doesn't work, because `fir.if` requires both branches to yield results of identical MLIR types. The two branches can produce different runtime representations — for example, a a constant-length CHARACTER variable (`fir.ref<fir.char<1,10>>`) vs. a dynamic-length CHARACTER expression (`fir.ref<fir.char<1,?>>`), or an array with a different descriptor shape. These type mismatches are only resolvable after we know the canonical type for the temporary that will hold the result.
## Approach
`hlfir.conditional` captures each branch in its own region (terminated by `hlfir.yield`), deferring materialization to the bufferization pass. During bufferization, `ConditionalOpConversion`:
- Computes a single canonical temp type from the hlfir.expr result type (computeTempBaseType), ensuring both branches produce an identical MLIR type for the `fir.if` results.
- Clones each region into a `fir.if` branch, creating a temp of that canonical type and assigning the yielded value into it.
- Defers `hlfir.destroy` cloning until after the assign to prevent use-after-free.
Trivial scalar types (INTEGER, REAL, COMPLEX, LOGICAL, UNSIGNED) bypass hlfir.conditional entirely and use fir.if SSA results directly since no temporary is needed.
**AI Usage Disclosure**: AI tools (Claude Sonnet 4.5) were used to assist with implementation of this feature and test code generation. I have reviewed, modified, and tested all AI-generated code.
---
Patch is 35.14 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/194411.diff
5 Files Affected:
- (modified) flang/include/flang/Optimizer/HLFIR/HLFIROps.td (+52-5)
- (modified) flang/lib/Lower/ConvertExprToHLFIR.cpp (+43-134)
- (modified) flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp (+35)
- (modified) flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp (+141-7)
- (modified) flang/test/Lower/HLFIR/conditional-expr.f90 (+39-76)
``````````diff
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 a57fce53c0ca5..d9b450d65878f 100644
--- a/flang/lib/Lower/ConvertExprToHLFIR.cpp
+++ b/flang/lib/Lower/ConvertExprToHLFIR.cpp
@@ -1847,56 +1847,48 @@ 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);
+ // Populate each region inside a scope so that any cleanups
+ // (hlfir.destroy) emitted by gen() stay inside the region, avoiding
+ // dominance violations. The ConditionalOpConversion in bufferization
+ // defers these destroy ops until after the assign into the temp.
+ builder.setInsertionPointToStart(&condOp.getThenRegion().front());
+ getStmtCtx().pushScope();
+ const hlfir::Entity thenEntity{gen(condExpr.thenValue())};
+ getStmtCtx().finalizeAndPop();
+ hlfir::YieldOp::create(builder, loc, thenEntity);
+ builder.setInsertionPointToStart(&condOp.getElseRegion().front());
+ getStmtCtx().pushScope();
+ const hlfir::Entity elseEntity{gen(condExpr.elseValue())};
+ getStmtCtx().finalizeAndPop();
+ hlfir::YieldOp::create(builder, loc, elseEntity);
+ 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
@@ -1940,110 +1932,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/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
index e42c064794176..3230066f2305f 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
@@ -2461,6 +2461,41 @@ 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()->push_back(new mlir::Block{});
+ odsState.addRegion()->push_back(new mlir::Block{});
+}
+
+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 (region.empty())
+ return emitOpError(name) << " region must not be empty";
+ if (!region.hasOneBlock())
+ return emitOpError(name) << " region must have exactly one block";
+ 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 35cbdd59cf5d8..24e4099ec98b7 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
@@ -29,6 +29,7 @@
#include "flang/Optimizer/OpenMP/Passes.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.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"
@@ -857,6 +858,137 @@ 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();
+ }
+ /// Compute the MLIR type of the temp's DeclareOp base result,
+ /// given the hlfir.expr type of the conditional. This must match
+ /// what createAndDeclareTemp + hlfir::DeclareOp would produce.
+ static mlir::Type computeTempBaseType(fir::FirOpBuilder &builder,
+ hlfir::ExprType exprType) {
+ const mlir::Type eleTy{exprType.getEleTy()};
+ const bool isPolymorphic{exprType.isPolymorphic()};
+ const bool isArray{exprType.isArray()};
+ mlir::Type elemOrSeqType{eleTy};
+ if (isArray)
+ elemOrSeqType = fir::SequenceType::get(exprType.getShape(), eleTy);
+ // Polymorphic: runtime allocate produces fir.class<fir.heap<T>>,
+ // DeclareOp strips the heap attribute → fir.class<T>.
+ if (isPolymorphic)
+ return fir::ClassType::get(elemOrSeqType);
+ // Arrays (non-polymorphic): heap alloc → fir.heap<seqTy>,
+ // DeclareOp wraps in box → fir.box<seqTy>.
+ if (isArray)
+ return fir::BoxType::get(elemOrSeqType);
+ // Scalar, non-polymorphic.
+ if (auto charTy{mlir::dyn_cast<fir::CharacterType>(eleTy)})
+ if (charTy.hasDynamicLen())
+ return fir::BoxCharType::get(builder.getContext(), charTy.getFKind());
+ if (fir::isRecordWithTypeParameters(eleTy))
+ return fir::BoxType::get(eleTy);
+ return fir::ReferenceType::get(eleTy);
+ }
+
+ 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 bool isPolymorphic{exprType.isPolymorphic()};
+ const bool isArray{exprType.isArray()};
+ mlir::Type elemOrSeqType{exprType.getEleTy()};
+ if (isArray)
+ elemOrSeqType =
+ fir::SequenceType::get(exprType.getShape(), elemOrSeqType);
+ const bool useStack{!isArray && !isPolymorphic};
+ const mlir::Type tempBaseType{computeTempBaseType(builder, exprType)};
+ // Callback for hlfir::DeclareOp.
+ auto genTempDeclareOp =
+ [](fir::FirOpBuilder &bldr, mlir::Location l, mlir::Value memref,
+ llvm::StringRef name, mlir::Value shape,
+ llvm::ArrayRef<mlir::Value> typeParams,
+ fir::FortranVariableFlagsAttr attrs) -> mlir::Value {
+ auto declareOp =
+ hlfir::DeclareOp::create(bldr, l, memref, name, shape, typeParams,
+ /*dummy_scope=*/nullptr, /*storage=*/nullptr,
+ /*storage_offset=*/0, attrs);
+ return declareOp.getBase();
+ };
+
+ // Emit one branch: clone ops, create temp, assign, run deferred
+ // destroys, yield (temp, mustFree).
+ auto emitBranch = [&](mlir::Region ®ion) {
+ mlir::IRMapping mapper;
+ // Clone all ops except hlfir.destroy and the terminator.
+ for (auto &op : region.front().without_terminator())
+ if (!mlir::isa<hlfir::DestroyOp>(op))
+ builder.clone(op, mapper);
+ auto yield{mlir::cast<hlfir::YieldOp>(region.front().getTerminator())};
+ // Dereference allocatable/pointer values.
+ const hlfir::Entity val{hlfir::derefPointersAndAllocatables(
+ loc, builder,
+ hlfir::Entity{mapper.lookupOrDefault(yield.get...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/194411
More information about the flang-commits
mailing list