[flang-commits] [flang] [flang] allow rebox/embox of OPTIONAL (PR #194319)
via flang-commits
flang-commits at lists.llvm.org
Mon Apr 27 02:10:41 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-fir-hlfir
@llvm/pr-subscribers-flang-codegen
Author: jeanPerier
<details>
<summary>Changes</summary>
Delay materialization of branches when building local temporary descriptor for OPTIONAL from hlfir-to-fir until pre-cg-rewrite.
This makes the IR easier to analyze with OPTIONAL (for instance alias analysis does not need to handle the branches to find the source).
This is done by adding an "optional" attribute to fir.embox, fir.rebox, and fir.rebox_assumed_rank to indicate that their cogeneration must be conditional.
The conditional aspect is implemented in pre-cg-rewrite to avoid complexifying codegen and the fir.cg dialect.
Assisted by: claude
---
Patch is 38.17 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/194319.diff
10 Files Affected:
- (modified) flang/include/flang/Optimizer/CodeGen/CGPasses.td (+2-1)
- (modified) flang/include/flang/Optimizer/Dialect/FIROps.td (+24-8)
- (modified) flang/lib/Optimizer/CodeGen/PreCGRewrite.cpp (+114-31)
- (modified) flang/lib/Optimizer/Dialect/FIROps.cpp (+20-2)
- (modified) flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp (+21-32)
- (modified) flang/lib/Optimizer/Transforms/AssumedRankOpConversion.cpp (+31-6)
- (modified) flang/test/Fir/fir-ops.fir (+24)
- (added) flang/test/Fir/rebox-embox-optional-codegen.fir (+89)
- (modified) flang/test/Fir/rebox_assumed_rank_codegen.fir (+20)
- (modified) flang/test/HLFIR/declare-codegen.fir (+29-8)
``````````diff
diff --git a/flang/include/flang/Optimizer/CodeGen/CGPasses.td b/flang/include/flang/Optimizer/CodeGen/CGPasses.td
index 22025c19121f3..5c39de9041bc3 100644
--- a/flang/include/flang/Optimizer/CodeGen/CGPasses.td
+++ b/flang/include/flang/Optimizer/CodeGen/CGPasses.td
@@ -50,7 +50,8 @@ def CodeGenRewrite : Pass<"cg-rewrite", "mlir::ModuleOp"> {
Fuse specific subgraphs into single Ops for code generation.
}];
let dependentDialects = [
- "fir::FIROpsDialect", "fir::FIRCodeGenDialect"
+ "fir::FIROpsDialect", "fir::FIRCodeGenDialect",
+ "mlir::cf::ControlFlowDialect"
];
let options = [
Option<"preserveDeclare", "preserve-declare", "bool", /*default=*/"false",
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 1af30684db4f6..453379b010075 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -894,13 +894,16 @@ def fir_EmboxOp : fir_Op<"embox", [NoMemoryEffect, AttrSizedOperandSegments,
type descriptor or element size to populate the new descriptor.
- accessMap: unused/experimental.
- allocator_idx: specify special allocator to use.
+ - optional: indicates that a null pointer `memref` must produce a absent
+ fir.box as opposed to a fir.box containing a nullptr.
}];
let arguments = (ins AnyReferenceLike:$memref, Optional<AnyShapeType>:$shape,
Optional<fir_SliceType>:$slice, Variadic<AnyIntegerType>:$typeparams,
Optional<BoxOrClassType>:$sourceBox,
OptionalAttr<AffineMapAttr>:$accessMap,
- OptionalAttr<I32Attr>:$allocator_idx);
+ OptionalAttr<I32Attr>:$allocator_idx,
+ UnitAttr:$optional);
let results = (outs BoxOrClassType);
@@ -913,12 +916,13 @@ def fir_EmboxOp : fir_Op<"embox", [NoMemoryEffect, AttrSizedOperandSegments,
CArg<"mlir::IntegerAttr", "{}">:$allocator_idx),
[{ return build($_builder, $_state, resultTypes, memref, shape, slice,
typeparams, sourceBox, mlir::AffineMapAttr{},
- allocator_idx); }]>
+ allocator_idx, /*optional=*/mlir::UnitAttr{}); }]>
];
let assemblyFormat = [{
$memref (`(` $shape^ `)`)? (`[` $slice^ `]`)? (`typeparams` $typeparams^)?
- (`source_box` $sourceBox^)? (`map` $accessMap^)? attr-dict `:`
+ (`source_box` $sourceBox^)? (`map` $accessMap^)?
+ (`optional` $optional^)? attr-dict `:`
functional-type(operands, results)
}];
@@ -976,18 +980,24 @@ def fir_ReboxOp : fir_Op<"rebox", [NoMemoryEffect, AttrSizedOperandSegments,
%3 = fir.shape %c3, %c4 : (index, index) -> !fir.shape<2>
%4 = fir.rebox %2(%3) : (!fir.box<!fir.array<?xf32>>, !fir.shape<2>) -> !fir.box<!fir.array<?x?xf32>>
```
+
+ When the `optional` unit attribute is set, `$box` may be an absent
+ (null-descriptor) OPTIONAL. In such case, the produced fir.box will be
+ absent when the input one is absent. Without this flag, it is illegal
+ to execute a fir.rebox where the input fir.box is absent.
}];
let arguments = (ins
BoxOrClassType:$box,
Optional<AnyShapeOrShiftType>:$shape,
- Optional<fir_SliceType>:$slice
+ Optional<fir_SliceType>:$slice,
+ UnitAttr:$optional
);
let results = (outs BoxOrClassType);
let assemblyFormat = [{
- $box (`(` $shape^ `)`)? (`[` $slice^ `]`)?
+ $box (`(` $shape^ `)`)? (`[` $slice^ `]`)? (`optional` $optional^)?
attr-dict `:` functional-type(operands, results)
}];
@@ -1022,18 +1032,24 @@ def fir_ReboxAssumedRankOp : fir_Op<"rebox_assumed_rank",
Example:
```
fir.rebox_assumed_rank %1 lbs zeroes : (!fir.box<!fir.array<*:f32>>) -> !fir.box<!fir.array<*:f32>>
- ```
+ ```
+
+ When the `optional` unit attribute is set, `$box` may be an absent
+ (null-descriptor) OPTIONAL. In such case, the produced fir.box will be
+ absent when the input one is absent. Without this flag, it is illegal
+ to execute a fir.rebox where the input fir.box is absent.
}];
let arguments = (ins
AnyRefOrBoxType:$box,
- fir_LowerBoundModifierAttribute:$lbs_modifier
+ fir_LowerBoundModifierAttribute:$lbs_modifier,
+ UnitAttr:$optional
);
let results = (outs BoxOrClassType);
let assemblyFormat = [{
- $box `lbs` $lbs_modifier
+ $box `lbs` $lbs_modifier (`optional` $optional^)?
attr-dict `:` functional-type(operands, results)
}];
diff --git a/flang/lib/Optimizer/CodeGen/PreCGRewrite.cpp b/flang/lib/Optimizer/CodeGen/PreCGRewrite.cpp
index 3b137d1e54234..d0b9093eaae65 100644
--- a/flang/lib/Optimizer/CodeGen/PreCGRewrite.cpp
+++ b/flang/lib/Optimizer/CodeGen/PreCGRewrite.cpp
@@ -18,6 +18,8 @@
#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/FIRType.h"
#include "flang/Optimizer/Dialect/Support/FIRContext.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlow.h"
+#include "mlir/Dialect/ControlFlow/IR/ControlFlowOps.h"
#include "mlir/IR/Iterators.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/ADT/STLExtras.h"
@@ -55,6 +57,40 @@ static void populateShift(llvm::SmallVectorImpl<mlir::Value> &vec,
vec.append(shift.getOrigins().begin(), shift.getOrigins().end());
}
+// Helper to emit embox/rebox for OPTIONAL input inside a block
+// guarded by a runtime presence check and to return an absent
+// box when the input is not present.
+template <typename OP>
+static mlir::Value
+emitOptionalBoxGuard(mlir::PatternRewriter &rewriter, OP op,
+ llvm::function_ref<mlir::Value()> buildPresent) {
+ mlir::Location loc = op.getLoc();
+ mlir::Type boxType = op.getResult().getType();
+ mlir::Value isPresent = fir::IsPresentOp::create(
+ rewriter, loc, rewriter.getI1Type(), op->getOperand(0));
+ mlir::Block *condBlock = op->getBlock();
+ mlir::Block *mergeBlock = rewriter.splitBlock(condBlock, op->getIterator());
+ mergeBlock->addArgument(boxType, loc);
+
+ mlir::Block *thenBlock = rewriter.createBlock(mergeBlock);
+ rewriter.setInsertionPointToStart(thenBlock);
+ mlir::Value present = buildPresent();
+ mlir::cf::BranchOp::create(rewriter, loc, mergeBlock,
+ mlir::ValueRange{present});
+
+ mlir::Block *elseBlock = rewriter.createBlock(mergeBlock);
+ rewriter.setInsertionPointToStart(elseBlock);
+ mlir::Value absent = fir::AbsentOp::create(rewriter, loc, boxType);
+ mlir::cf::BranchOp::create(rewriter, loc, mergeBlock,
+ mlir::ValueRange{absent});
+
+ rewriter.setInsertionPointToEnd(condBlock);
+ mlir::cf::CondBranchOp::create(rewriter, loc, isPresent, thenBlock,
+ elseBlock);
+ rewriter.setInsertionPointToStart(mergeBlock);
+ return mergeBlock->getArgument(0);
+}
+
namespace {
/// Convert fir.embox to the extended form where necessary.
@@ -80,24 +116,60 @@ class EmboxConversion : public mlir::OpRewritePattern<fir::EmboxOp> {
public:
using OpRewritePattern::OpRewritePattern;
- llvm::LogicalResult
- matchAndRewrite(fir::EmboxOp embox,
- mlir::PatternRewriter &rewriter) const override {
- // If the embox does not include a shape, then do not convert it
+ enum class RewriteKind { Dynamic, Static, DropOptional };
+
+ static llvm::FailureOr<RewriteKind> getRewriteKind(fir::EmboxOp embox) {
if (auto shapeVal = embox.getShape())
- return rewriteDynamicShape(embox, rewriter, shapeVal);
- if (mlir::isa<fir::ClassType>(embox.getType()))
- TODO(embox.getLoc(), "embox conversion for fir.class type");
+ return RewriteKind::Dynamic;
if (auto boxTy = mlir::dyn_cast<fir::BoxType>(embox.getType()))
if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(boxTy.getEleTy()))
if (!seqTy.hasDynamicExtents())
- return rewriteStaticShape(embox, rewriter, seqTy);
- return mlir::failure();
+ return RewriteKind::Static;
+ if (embox.getOptional())
+ return RewriteKind::DropOptional;
+ return llvm::failure();
}
- llvm::LogicalResult rewriteStaticShape(fir::EmboxOp embox,
- mlir::PatternRewriter &rewriter,
- fir::SequenceType seqTy) const {
+ llvm::LogicalResult
+ matchAndRewrite(fir::EmboxOp embox,
+ mlir::PatternRewriter &rewriter) const override {
+ llvm::FailureOr<RewriteKind> rewriteKind = getRewriteKind(embox);
+ if (llvm::failed(rewriteKind))
+ return llvm::failure();
+ if (embox.getOptional()) {
+ mlir::Value newBox = emitOptionalBoxGuard(rewriter, embox, [&] {
+ return matchAndRewriteImpl(embox, rewriter, *rewriteKind)->getResult(0);
+ });
+ rewriter.replaceOp(embox, newBox);
+ return mlir::success();
+ }
+ mlir::Operation *newOp = matchAndRewriteImpl(embox, rewriter, *rewriteKind);
+ rewriter.replaceOp(embox, newOp);
+ return mlir::success();
+ }
+ mlir::Operation *matchAndRewriteImpl(fir::EmboxOp embox,
+ mlir::PatternRewriter &rewriter,
+ RewriteKind rewriteKind) const {
+ switch (rewriteKind) {
+ case RewriteKind::Dynamic:
+ return rewriteDynamicShape(embox, rewriter, embox.getShape());
+ case RewriteKind::Static:
+ return rewriteStaticShape(embox, rewriter,
+ fir::unwrapUntilSeqType(embox.getType()));
+ case RewriteKind::DropOptional: {
+ auto newEmbox =
+ llvm::cast<fir::EmboxOp>(rewriter.clone(*embox.getOperation()));
+ newEmbox.setOptional(false);
+ return newEmbox.getOperation();
+ }
+ }
+ llvm_unreachable("all cases covered");
+ return nullptr;
+ }
+
+ mlir::Operation *rewriteStaticShape(fir::EmboxOp embox,
+ mlir::PatternRewriter &rewriter,
+ fir::SequenceType seqTy) const {
auto loc = embox.getLoc();
llvm::SmallVector<mlir::Value> shapeOpers;
auto idxTy = rewriter.getIndexType();
@@ -113,13 +185,12 @@ class EmboxConversion : public mlir::OpRewritePattern<fir::EmboxOp> {
mlir::ValueRange{}, embox.getTypeparams(), embox.getSourceBox(),
embox.getAllocatorIdxAttr());
LLVM_DEBUG(llvm::dbgs() << "rewriting " << embox << " to " << xbox << '\n');
- rewriter.replaceOp(embox, xbox.getOperation()->getResults());
- return mlir::success();
+ return xbox.getOperation();
}
- llvm::LogicalResult rewriteDynamicShape(fir::EmboxOp embox,
- mlir::PatternRewriter &rewriter,
- mlir::Value shapeVal) const {
+ mlir::Operation *rewriteDynamicShape(fir::EmboxOp embox,
+ mlir::PatternRewriter &rewriter,
+ mlir::Value shapeVal) const {
auto loc = embox.getLoc();
llvm::SmallVector<mlir::Value> shapeOpers;
llvm::SmallVector<mlir::Value> shiftOpers;
@@ -150,8 +221,7 @@ class EmboxConversion : public mlir::OpRewritePattern<fir::EmboxOp> {
embox.getTypeparams(), embox.getSourceBox(),
embox.getAllocatorIdxAttr());
LLVM_DEBUG(llvm::dbgs() << "rewriting " << embox << " to " << xbox << '\n');
- rewriter.replaceOp(embox, xbox.getOperation()->getResults());
- return mlir::success();
+ return xbox.getOperation();
}
};
@@ -174,20 +244,35 @@ class ReboxConversion : public mlir::OpRewritePattern<fir::ReboxOp> {
llvm::LogicalResult
matchAndRewrite(fir::ReboxOp rebox,
mlir::PatternRewriter &rewriter) const override {
+ if (rebox.getOptional()) {
+ mlir::Value newBox = emitOptionalBoxGuard(rewriter, rebox, [&] {
+ return matchAndRewriteImpl(rebox, rewriter)->getResult(0);
+ });
+ llvm::errs() << newBox;
+ rewriter.replaceOp(rebox, newBox);
+ return mlir::success();
+ }
+ mlir::Operation *newOp = matchAndRewriteImpl(rebox, rewriter);
+ rewriter.replaceOp(rebox, newOp);
+ return mlir::success();
+ }
+
+ mlir::Operation *matchAndRewriteImpl(fir::ReboxOp rebox,
+ mlir::PatternRewriter &rewriter) const {
auto loc = rebox.getLoc();
llvm::SmallVector<mlir::Value> shapeOpers;
llvm::SmallVector<mlir::Value> shiftOpers;
if (auto shapeVal = rebox.getShape()) {
if (auto shapeOp = mlir::dyn_cast<fir::ShapeOp>(shapeVal.getDefiningOp()))
populateShape(shapeOpers, shapeOp);
- else if (auto shiftOp =
+ else if (auto shapeShiftOp =
mlir::dyn_cast<fir::ShapeShiftOp>(shapeVal.getDefiningOp()))
- populateShapeAndShift(shapeOpers, shiftOpers, shiftOp);
- else if (auto shiftOp =
- mlir::dyn_cast<fir::ShiftOp>(shapeVal.getDefiningOp()))
+ populateShapeAndShift(shapeOpers, shiftOpers, shapeShiftOp);
+ else {
+ auto shiftOp = mlir::dyn_cast<fir::ShiftOp>(shapeVal.getDefiningOp());
+ assert(shiftOp && "unexpected shape operand type");
populateShift(shiftOpers, shiftOp);
- else
- return mlir::failure();
+ }
}
llvm::SmallVector<mlir::Value> sliceOpers;
llvm::SmallVector<mlir::Value> subcompOpers;
@@ -208,8 +293,7 @@ class ReboxConversion : public mlir::OpRewritePattern<fir::ReboxOp> {
sliceOpers, subcompOpers, substrOpers);
LLVM_DEBUG(llvm::dbgs()
<< "rewriting " << rebox << " to " << xRebox << '\n');
- rewriter.replaceOp(rebox, xRebox.getOperation()->getResults());
- return mlir::success();
+ return xRebox.getOperation();
}
};
@@ -362,15 +446,14 @@ class CodeGenRewrite : public fir::impl::CodeGenRewriteBase<CodeGenRewrite> {
auto &context = getContext();
mlir::ConversionTarget target(context);
target.addLegalDialect<mlir::arith::ArithDialect, fir::FIROpsDialect,
- fir::FIRCodeGenDialect, mlir::func::FuncDialect>();
+ fir::FIRCodeGenDialect, mlir::func::FuncDialect,
+ mlir::cf::ControlFlowDialect>();
target.addIllegalOp<fir::ArrayCoorOp>();
target.addIllegalOp<fir::ReboxOp>();
target.addIllegalOp<fir::DeclareOp>();
target.addIllegalOp<fir::DummyScopeOp>();
target.addDynamicallyLegalOp<fir::EmboxOp>([](fir::EmboxOp embox) {
- return !(embox.getShape() ||
- mlir::isa<fir::SequenceType>(
- mlir::cast<fir::BaseBoxType>(embox.getType()).getEleTy()));
+ return llvm::failed(EmboxConversion::getRewriteKind(embox));
});
mlir::RewritePatternSet patterns(&context);
fir::populatePreCGRewritePatterns(patterns, preserveDeclare);
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 4705033945611..307511988eb8d 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -203,8 +203,16 @@ bool fir::mayBeAbsentBox(mlir::Value val) {
// Check for fir.embox and fir.rebox before checking for
// FortranObjectViewOpInterface, which they support.
- // A box created by fir.embox/rebox cannot be absent.
- if (mlir::isa<fir::ReboxOp, fir::EmboxOp, fir::LoadOp>(defOp))
+ // A box created by fir.embox/fir.rebox/fir.rebox_assumed_rank is only
+ // potentially absent when the operation was explicitly tagged with the
+ // `optional` attribute.
+ if (auto reboxOp = mlir::dyn_cast<fir::ReboxOp>(defOp))
+ return reboxOp.getOptional();
+ if (auto emboxOp = mlir::dyn_cast<fir::EmboxOp>(defOp))
+ return emboxOp.getOptional();
+ if (auto reboxAROp = mlir::dyn_cast<fir::ReboxAssumedRankOp>(defOp))
+ return reboxAROp.getOptional();
+ if (mlir::isa<fir::LoadOp>(defOp))
return false;
if (auto viewIface =
@@ -2490,6 +2498,11 @@ std::optional<std::int64_t> fir::EmboxOp::getViewOffset(mlir::OpResult) {
}
mlir::Speculation::Speculatability fir::EmboxOp::getSpeculatability() {
+ // The operation is always safe to evaluate if it has the "optional"
+ // attribute, otherwise it is not safe to evaluate if the input may
+ // be absent.
+ if (getOptional())
+ return mlir::Speculation::NotSpeculatable;
return (getSourceBox() && mayBeAbsentBox(getSourceBox()))
? mlir::Speculation::NotSpeculatable
: mlir::Speculation::Speculatable;
@@ -3801,6 +3814,11 @@ std::optional<std::int64_t> fir::ReboxOp::getViewOffset(mlir::OpResult) {
}
mlir::Speculation::Speculatability fir::ReboxOp::getSpeculatability() {
+ // The operation is always safe to evaluate if it has the "optional"
+ // attribute, otherwise it is not safe to evaluate if the input may
+ // be absent.
+ if (getOptional())
+ return mlir::Speculation::NotSpeculatable;
return mayBeAbsentBox(getBox()) ? mlir::Speculation::NotSpeculatable
: mlir::Speculation::Speculatable;
}
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
index 25ffb8fe6768b..84c6999ec26f5 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
@@ -365,23 +365,30 @@ class DeclareOpConversion : public mlir::OpRewritePattern<hlfir::DeclareOp> {
if (mlir::isa<fir::BaseBoxType>(hlfirBaseType)) {
fir::FirOpBuilder builder(rewriter, declareOp.getOperation());
// Helper to generate the hlfir fir.box with the local lower bounds and
- // type parameters.
+ // type parameters and OPTIONAL aspect.
+ const bool isOptional =
+ mlir::cast<fir::FortranVariableOpInterface>(declareOp.getOperation())
+ .isOptional();
auto genHlfirBox = [&]() -> mlir::Value {
if (auto baseBoxType =
mlir::dyn_cast<fir::BaseBoxType>(firBase.getType())) {
if (declareOp.getSkipRebox())
return firBase;
// Rebox so that lower bounds and attributes are correct.
- if (baseBoxType.isAssumedRank())
+ if (baseBoxType.isAssumedRank()) {
return fir::ReboxAssumedRankOp::create(
builder, loc, hlfirBaseType, firBase,
- fir::LowerBoundModifierAttribute::SetToOnes);
+ fir::LowerBoundModifierAttribute::SetToOnes, isOptional);
+ }
if (!fir::extractSequenceType(baseBoxType.getEleTy()) &&
baseBoxType == hlfirBaseType)
return firBase;
- return fir::ReboxOp::create(builder, loc, hlfirBaseType, firBase,
- declareOp.getShape(),
- /*slice=*/mlir::Value{});
+ auto rebox = fir::ReboxOp::create(builder, loc, hlfirBaseType,
+ firBase, declareOp.getShape(),
+ /*slice=*/mlir::Value{});
+ if (isOptional)
+ rebox.setOptional(true);
+ return rebox.getResult();
} else {
llvm::SmallVector<mlir::Value> typeParams;
auto maybeCharType = mlir::dyn_cast<fir::CharacterType>(
@@ -389,14 +396,16 @@ class DeclareOpConversion : public mlir::OpRewritePattern<hlfir::DeclareOp> {
if (!maybeCharType || maybeCharType.hasDynamicLen())
typeParams.append(declareOp.getTypeparams().begin(),
declareOp.getTypeparams().end());
- return fir::EmboxOp::create(builder, loc, hlfirBaseType, firBase,
- declareOp.getShape(),
- /*slice=*/mlir::Value{}, typeParams);
+ auto embox = fir::EmboxOp::create(
+ builder, loc, hlfirBaseType, firBase, declareOp.getShape(),
+ /*slice=*/mlir::Value{}, typeParams);
+ if (isOptional)
+ embox.setOptional(true);
+ return embox.getResult();
}
};
- if (!mlir::cast<fir::FortranVariableOpInterface>(declareOp.getOperation())
- .isOptional()) {
- hlfirBase = genHlfirBox();
+ hlfirBase = genHlfirBox();
+ if (!isOptional) {
// If the original base is a box too, we could as well
// use the HLFIR box as the FIR base: otherwise, the two
// box...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/194319
More information about the flang-commits
mailing list