return createTemporary(loc, type, name, {}, {}, attrs);
}
+ /// Create a temporary on the heap.
+ mlir::Value
+ createHeapTemporary(mlir::Location loc, mlir::Type type,
+ llvm::StringRef name = {}, mlir::ValueRange shape = {},
+ mlir::ValueRange lenParams = {},
+ llvm::ArrayRef<mlir::NamedAttribute> attrs = {});
+
/// Create a global value.
fir::GlobalOp createGlobal(mlir::Location loc, mlir::Type type,
llvm::StringRef name,
/// Dump the current function. (debug)
LLVM_DUMP_METHOD void dumpFunc();
-private:
- /// Set attributes (e.g. FastMathAttr) to \p op operation
- /// based on the current attributes setting.
- void setCommonAttributes(mlir::Operation *op) const;
-
/// FirOpBuilder hook for creating new operation.
void notifyOperationInserted(mlir::Operation *op) override {
setCommonAttributes(op);
}
+private:
+ /// Set attributes (e.g. FastMathAttr) to \p op operation
+ /// based on the current attributes setting.
+ void setCommonAttributes(mlir::Operation *op) const;
+
const KindMapping &kindMap;
/// FastMathFlags that need to be set for operations that support
#define FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H
#include "flang/Optimizer/Builder/BoxValue.h"
+#include "flang/Optimizer/Dialect/FIROps.h"
#include "flang/Optimizer/Dialect/FortranVariableInterface.h"
#include "flang/Optimizer/HLFIR/HLFIRDialect.h"
class AssociateOp;
class ElementalOp;
+class YieldElementOp;
/// Is this an SSA value type for the value of a Fortran expression?
inline bool isFortranValueType(mlir::Type type) {
mlir::ValueRange typeParams,
const ElementalKernelGenerator &genKernel);
+/// Generate a fir.do_loop nest looping from 1 to extents[i].
+/// Return the inner fir.do_loop and the indices of the loops.
+std::pair<fir::DoLoopOp, llvm::SmallVector<mlir::Value>>
+genLoopNest(mlir::Location loc, fir::FirOpBuilder &builder,
+ mlir::ValueRange extents);
+
+/// Inline the body of an hlfir.elemental at the current insertion point
+/// given a list of one based indices. This generates the computation
+/// of one element of the elemental expression. Return the YieldElementOp
+/// whose value argument is the element value.
+/// The original hlfir::ElementalOp is left untouched.
+hlfir::YieldElementOp inlineElementalOp(mlir::Location loc,
+ fir::FirOpBuilder &builder,
+ hlfir::ElementalOp elemental,
+ mlir::ValueRange oneBasedIndices);
+
} // namespace hlfir
#endif // FORTRAN_OPTIMIZER_BUILDER_HLFIRTOOLS_H
return ae;
}
+mlir::Value fir::FirOpBuilder::createHeapTemporary(
+ mlir::Location loc, mlir::Type type, llvm::StringRef name,
+ mlir::ValueRange shape, mlir::ValueRange lenParams,
+ llvm::ArrayRef<mlir::NamedAttribute> attrs) {
+ llvm::SmallVector<mlir::Value> dynamicShape =
+ elideExtentsAlreadyInType(type, shape);
+ llvm::SmallVector<mlir::Value> dynamicLength =
+ elideLengthsAlreadyInType(type, lenParams);
+
+ assert(!type.isa<fir::ReferenceType>() && "cannot be a reference");
+ return create<fir::AllocMemOp>(loc, type, /*unique_name=*/llvm::StringRef{},
+ name, dynamicLength, dynamicShape, attrs);
+}
+
/// Create a global variable in the (read-only) data section. A global variable
/// must have a unique name to identify and reference it.
fir::GlobalOp fir::FirOpBuilder::createGlobal(mlir::Location loc,
#include "flang/Optimizer/Builder/MutableBox.h"
#include "flang/Optimizer/Builder/Todo.h"
#include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "mlir/IR/BlockAndValueMapping.h"
// Return explicit extents. If the base is a fir.box, this won't read it to
// return the extents and will instead return an empty vector.
builder.restoreInsertionPoint(insertPt);
return elementalOp;
}
+
+hlfir::YieldElementOp
+hlfir::inlineElementalOp(mlir::Location loc, fir::FirOpBuilder &builder,
+ hlfir::ElementalOp elemental,
+ mlir::ValueRange oneBasedIndices) {
+ // hlfir.elemental region is a SizedRegion<1>.
+ assert(elemental.getRegion().hasOneBlock() &&
+ "expect elemental region to have one block");
+ mlir::BlockAndValueMapping mapper;
+ mapper.map(elemental.getIndices(), oneBasedIndices);
+ mlir::Operation *newOp;
+ for (auto &op : elemental.getRegion().back().getOperations())
+ newOp = builder.clone(op, mapper);
+ auto yield = mlir::dyn_cast_or_null<hlfir::YieldElementOp>(newOp);
+ assert(yield && "last ElementalOp operation must be am hlfir.yield_element");
+ return yield;
+}
+
+std::pair<fir::DoLoopOp, llvm::SmallVector<mlir::Value>>
+hlfir::genLoopNest(mlir::Location loc, fir::FirOpBuilder &builder,
+ mlir::ValueRange extents) {
+ assert(!extents.empty() && "must have at least one extent");
+ auto insPt = builder.saveInsertionPoint();
+ llvm::SmallVector<mlir::Value> indices(extents.size());
+ // Build loop nest from column to row.
+ auto one = builder.create<mlir::arith::ConstantIndexOp>(loc, 1);
+ mlir::Type indexType = builder.getIndexType();
+ unsigned dim = extents.size() - 1;
+ fir::DoLoopOp innerLoop;
+ for (auto extent : llvm::reverse(extents)) {
+ auto ub = builder.createConvert(loc, indexType, extent);
+ innerLoop = builder.create<fir::DoLoopOp>(loc, one, ub, one);
+ builder.setInsertionPointToStart(innerLoop.getBody());
+ // Reverse the indices so they are in column-major order.
+ indices[dim--] = innerLoop.getInductionVar();
+ }
+ builder.restoreInsertionPoint(insPt);
+ return {innerLoop, indices};
+}
TODO(bufferizedExpr.getLoc(), "general extract storage case");
}
+static llvm::SmallVector<mlir::Value>
+getIndexExtents(mlir::Location loc, fir::FirOpBuilder &builder,
+ mlir::Value shape) {
+ llvm::SmallVector<mlir::Value> extents;
+ if (auto s = shape.getDefiningOp<fir::ShapeOp>()) {
+ auto e = s.getExtents();
+ extents.insert(extents.end(), e.begin(), e.end());
+ } else if (auto s = shape.getDefiningOp<fir::ShapeShiftOp>()) {
+ auto e = s.getExtents();
+ extents.insert(extents.end(), e.begin(), e.end());
+ } else {
+ // TODO: add fir.get_extent ops on fir.shape<> ops.
+ TODO(loc, "get extents from fir.shape without fir::ShapeOp parent op");
+ }
+ mlir::Type indexType = builder.getIndexType();
+ for (auto &extent : extents)
+ extent = builder.createConvert(loc, indexType, extent);
+ return extents;
+}
+
static std::pair<hlfir::Entity, mlir::Value>
createTempFromMold(mlir::Location loc, fir::FirOpBuilder &builder,
hlfir::Entity mold) {
return {hlfir::Entity{declareOp.getBase()}, falseVal};
}
+static std::pair<hlfir::Entity, mlir::Value>
+createArrayTemp(mlir::Location loc, fir::FirOpBuilder &builder,
+ mlir::Type exprType, mlir::Value shape,
+ mlir::ValueRange extents, mlir::ValueRange lenParams) {
+ mlir::Type sequenceType = hlfir::getFortranElementOrSequenceType(exprType);
+ llvm::StringRef tmpName{".tmp.array"};
+ mlir::Value allocmem = builder.createHeapTemporary(loc, sequenceType, tmpName,
+ extents, lenParams);
+ auto declareOp =
+ builder.create<hlfir::DeclareOp>(loc, allocmem, tmpName, shape, lenParams,
+ fir::FortranVariableFlagsAttr{});
+ mlir::Value trueVal = builder.createBool(loc, true);
+ return {hlfir::Entity{declareOp.getBase()}, trueVal};
+}
+
struct AsExprOpConversion : public mlir::OpConversionPattern<hlfir::AsExprOp> {
using mlir::OpConversionPattern<hlfir::AsExprOp>::OpConversionPattern;
explicit AsExprOpConversion(mlir::MLIRContext *ctx)
matchAndRewrite(hlfir::EndAssociateOp endAssociate, OpAdaptor adaptor,
mlir::ConversionPatternRewriter &rewriter) const override {
mlir::Value mustFree = adaptor.getMustFree();
- if (auto cstMustFree = fir::factory::getIntIfConstant(mustFree))
- if (*cstMustFree == 0) {
- rewriter.eraseOp(endAssociate);
- return mlir::success(); // nothing to do.
- }
+ mlir::Location loc = endAssociate->getLoc();
+ rewriter.eraseOp(endAssociate);
+ auto genFree = [&]() {
+ mlir::Value var = adaptor.getVar();
+ if (var.getType().isa<fir::BaseBoxType>())
+ TODO(loc, "unbox");
+ rewriter.create<fir::FreeMemOp>(loc, var);
+ };
+ if (auto cstMustFree = fir::factory::getIntIfConstant(mustFree)) {
+ if (*cstMustFree != 0)
+ genFree();
+ // else, nothing to do.
+ return mlir::success();
+ }
TODO(endAssociate.getLoc(), "conditional free");
}
};
}
};
+/// This Listener allows setting both the builder and the rewriter as
+/// listeners. This is required when a pattern uses a firBuilder helper that
+/// may create illegal operations that will need to be translated and requires
+/// notifying the rewriter.
+struct HLFIRListener : public mlir::OpBuilder::Listener {
+ HLFIRListener(fir::FirOpBuilder &builder,
+ mlir::ConversionPatternRewriter &rewriter)
+ : builder{builder}, rewriter{rewriter} {}
+ void notifyOperationInserted(mlir::Operation *op) override {
+ builder.notifyOperationInserted(op);
+ rewriter.notifyOperationInserted(op);
+ }
+ virtual void notifyBlockCreated(mlir::Block *block) override {
+ builder.notifyBlockCreated(block);
+ rewriter.notifyBlockCreated(block);
+ }
+ fir::FirOpBuilder &builder;
+ mlir::ConversionPatternRewriter &rewriter;
+};
+
+struct ElementalOpConversion
+ : public mlir::OpConversionPattern<hlfir::ElementalOp> {
+ using mlir::OpConversionPattern<hlfir::ElementalOp>::OpConversionPattern;
+ explicit ElementalOpConversion(mlir::MLIRContext *ctx)
+ : mlir::OpConversionPattern<hlfir::ElementalOp>{ctx} {}
+ mlir::LogicalResult
+ matchAndRewrite(hlfir::ElementalOp elemental, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const override {
+ mlir::Location loc = elemental->getLoc();
+ auto module = elemental->getParentOfType<mlir::ModuleOp>();
+ fir::FirOpBuilder builder(rewriter, fir::getKindMapping(module));
+ // The body of the elemental op may contain operation that will require
+ // to be translated. Notify the rewriter about the cloned operations.
+ HLFIRListener listener{builder, rewriter};
+ builder.setListener(&listener);
+
+ mlir::Value shape = adaptor.getShape();
+ auto extents = getIndexExtents(loc, builder, shape);
+ auto [temp, cleanup] =
+ createArrayTemp(loc, builder, elemental.getType(), shape, extents,
+ adaptor.getTypeparams());
+ // Generate a loop nest looping around the fir.elemental shape and clone
+ // fir.elemental region inside the inner loop.
+ auto [innerLoop, oneBasedLoopIndices] =
+ hlfir::genLoopNest(loc, builder, extents);
+ auto insPt = builder.saveInsertionPoint();
+ builder.setInsertionPointToStart(innerLoop.getBody());
+ auto yield =
+ hlfir::inlineElementalOp(loc, builder, elemental, oneBasedLoopIndices);
+ hlfir::Entity elementValue(yield.getElementValue());
+ // Skip final AsExpr if any. It would create an element temporary,
+ // which is no needed since the element will be assigned right away in
+ // the array temporary. An hlfir.as_expr may have been added if the
+ // elemental is a "view" over a variable (e.g parentheses or transpose).
+ if (auto asExpr = elementValue.getDefiningOp<hlfir::AsExprOp>()) {
+ elementValue = hlfir::Entity{asExpr.getVar()};
+ if (asExpr->hasOneUse())
+ rewriter.eraseOp(asExpr);
+ }
+ rewriter.eraseOp(yield);
+ // Assign the element value to the temp element for this iteration.
+ auto tempElement =
+ hlfir::getElementAt(loc, builder, temp, oneBasedLoopIndices);
+ builder.create<hlfir::AssignOp>(loc, elementValue, tempElement);
+ builder.restoreInsertionPoint(insPt);
+
+ mlir::Value bufferizedExpr =
+ packageBufferizedExpr(loc, builder, temp, cleanup);
+ rewriter.replaceOp(elemental, bufferizedExpr);
+ return mlir::success();
+ }
+};
+
class BufferizeHLFIR : public hlfir::impl::BufferizeHLFIRBase<BufferizeHLFIR> {
public:
void runOnOperation() override {
auto module = this->getOperation();
auto *context = &getContext();
mlir::RewritePatternSet patterns(context);
- patterns.insert<AsExprOpConversion, AssignOpConversion,
- AssociateOpConversion, ConcatOpConversion,
- EndAssociateOpConversion, NoReassocOpConversion>(context);
+ patterns
+ .insert<AsExprOpConversion, AssignOpConversion, AssociateOpConversion,
+ ConcatOpConversion, ElementalOpConversion,
+ EndAssociateOpConversion, NoReassocOpConversion>(context);
mlir::ConversionTarget target(*context);
- target.addIllegalOp<hlfir::AssociateOp, hlfir::EndAssociateOp>();
+ target.addIllegalOp<hlfir::AssociateOp, hlfir::ElementalOp,
+ hlfir::EndAssociateOp, hlfir::YieldElementOp>();
target.markUnknownOpDynamicallyLegal([](mlir::Operation *op) {
return llvm::all_of(
op->getResultTypes(),
--- /dev/null
+// Test hlfir.elemental code generation
+// RUN: fir-opt %s --bufferize-hlfir | FileCheck %s
+
+func.func @numeric_type(%arg0: !fir.ref<!fir.array<10x20xi32>>, %arg1: !fir.ref<!fir.array<10x20xi32>>) {
+ %c10 = arith.constant 10 : index
+ %c20 = arith.constant 20 : index
+ %0 = fir.shape %c10, %c20 : (index, index) -> !fir.shape<2>
+ %1 = hlfir.elemental %0 : (!fir.shape<2>) -> !hlfir.expr<10x20xi32> {
+ ^bb0(%arg2: index, %arg3: index):
+ %2 = hlfir.designate %arg0 (%arg2, %arg3) : (!fir.ref<!fir.array<10x20xi32>>, index, index) -> !fir.ref<i32>
+ %3 = hlfir.designate %arg1 (%arg2, %arg3) : (!fir.ref<!fir.array<10x20xi32>>, index, index) -> !fir.ref<i32>
+ %4 = fir.load %2 : !fir.ref<i32>
+ %5 = fir.load %3 : !fir.ref<i32>
+ %6 = arith.addi %4, %5 : i32
+ hlfir.yield_element %6 : i32
+ }
+ return
+}
+// CHECK-LABEL: func.func @numeric_type(
+// CHECK-SAME: %[[VAL_0:[^:]*]]: !fir.ref<!fir.array<10x20xi32>>,
+// CHECK-SAME: %[[VAL_1:.*]]: !fir.ref<!fir.array<10x20xi32>>) {
+// CHECK: %[[VAL_2:.*]] = arith.constant 10 : index
+// CHECK: %[[VAL_3:.*]] = arith.constant 20 : index
+// CHECK: %[[VAL_4:.*]] = fir.shape %[[VAL_2]], %[[VAL_3]] : (index, index) -> !fir.shape<2>
+// CHECK: %[[VAL_5:.*]] = fir.allocmem !fir.array<10x20xi32> {bindc_name = ".tmp.array", uniq_name = ""}
+// CHECK: %[[VAL_6:.*]]:2 = hlfir.declare %[[VAL_5]](%[[VAL_4]]) {uniq_name = ".tmp.array"} : (!fir.heap<!fir.array<10x20xi32>>, !fir.shape<2>) -> (!fir.heap<!fir.array<10x20xi32>>, !fir.heap<!fir.array<10x20xi32>>)
+// CHECK: %[[VAL_7:.*]] = arith.constant true
+// CHECK: %[[VAL_8:.*]] = arith.constant 1 : index
+// CHECK: fir.do_loop %[[VAL_9:.*]] = %[[VAL_8]] to %[[VAL_3]] step %[[VAL_8]] {
+// CHECK: fir.do_loop %[[VAL_10:.*]] = %[[VAL_8]] to %[[VAL_2]] step %[[VAL_8]] {
+// CHECK: %[[VAL_11:.*]] = hlfir.designate %[[VAL_0]] (%[[VAL_10]], %[[VAL_9]]) : (!fir.ref<!fir.array<10x20xi32>>, index, index) -> !fir.ref<i32>
+// CHECK: %[[VAL_12:.*]] = hlfir.designate %[[VAL_1]] (%[[VAL_10]], %[[VAL_9]]) : (!fir.ref<!fir.array<10x20xi32>>, index, index) -> !fir.ref<i32>
+// CHECK: %[[VAL_13:.*]] = fir.load %[[VAL_11]] : !fir.ref<i32>
+// CHECK: %[[VAL_14:.*]] = fir.load %[[VAL_12]] : !fir.ref<i32>
+// CHECK: %[[VAL_15:.*]] = arith.addi %[[VAL_13]], %[[VAL_14]] : i32
+// CHECK: %[[VAL_16:.*]] = hlfir.designate %[[VAL_6]]#0 (%[[VAL_10]], %[[VAL_9]]) : (!fir.heap<!fir.array<10x20xi32>>, index, index) -> !fir.ref<i32>
+// CHECK: hlfir.assign %[[VAL_15]] to %[[VAL_16]] : i32, !fir.ref<i32>
+// CHECK: }
+// CHECK: }
+// CHECK: %[[VAL_17:.*]] = fir.undefined tuple<!fir.heap<!fir.array<10x20xi32>>, i1>
+// CHECK: %[[VAL_18:.*]] = fir.insert_value %[[VAL_17]], %[[VAL_7]], [1 : index] : (tuple<!fir.heap<!fir.array<10x20xi32>>, i1>, i1) -> tuple<!fir.heap<!fir.array<10x20xi32>>, i1>
+// CHECK: %[[VAL_19:.*]] = fir.insert_value %[[VAL_18]], %[[VAL_6]]#0, [0 : index] : (tuple<!fir.heap<!fir.array<10x20xi32>>, i1>, !fir.heap<!fir.array<10x20xi32>>) -> tuple<!fir.heap<!fir.array<10x20xi32>>, i1>
+
+
+func.func @char_type(%arg0: !fir.box<!fir.array<?x!fir.char<1,?>>>, %arg1: index, %arg2: index, %arg3: index) {
+ %0 = fir.shape %arg1 : (index) -> !fir.shape<1>
+ %1 = hlfir.elemental %0 typeparams %arg2 : (!fir.shape<1>, index) -> !hlfir.expr<?x!fir.char<1,?>> {
+ ^bb0(%arg4: index):
+ %2 = hlfir.designate %arg0 (%arg4) typeparams %arg3 : (!fir.box<!fir.array<?x!fir.char<1,?>>>, index, index) -> !fir.boxchar<1>
+ %3 = hlfir.concat %2, %2 len %arg2 : (!fir.boxchar<1>, !fir.boxchar<1>, index) -> !hlfir.expr<!fir.char<1,?>>
+ hlfir.yield_element %3 : !hlfir.expr<!fir.char<1,?>>
+ }
+ return
+}
+// CHECK-LABEL: func.func @char_type(
+// CHECK-SAME: %[[VAL_0:.*]]: !fir.box<!fir.array<?x!fir.char<1,?>>>,
+// CHECK-SAME: %[[VAL_1:[^:]*]]: index,
+// CHECK-SAME: %[[VAL_2:[^:]*]]: index,
+// CHECK-SAME: %[[VAL_3:[^:]*]]: index) {
+// CHECK: %[[VAL_4:.*]] = fir.shape %[[VAL_1]] : (index) -> !fir.shape<1>
+// CHECK: %[[VAL_5:.*]] = fir.allocmem !fir.array<?x!fir.char<1,?>>(%[[VAL_2]] : index), %[[VAL_1]] {bindc_name = ".tmp.array", uniq_name = ""}
+// CHECK: %[[VAL_6:.*]]:2 = hlfir.declare %[[VAL_5]](%[[VAL_4]]) typeparams %[[VAL_2]] {uniq_name = ".tmp.array"} : (!fir.heap<!fir.array<?x!fir.char<1,?>>>, !fir.shape<1>, index) -> (!fir.box<!fir.array<?x!fir.char<1,?>>>, !fir.heap<!fir.array<?x!fir.char<1,?>>>)
+// CHECK: %[[VAL_7:.*]] = arith.constant true
+// CHECK: %[[VAL_8:.*]] = arith.constant 1 : index
+// CHECK: fir.do_loop %[[VAL_9:.*]] = %[[VAL_8]] to %[[VAL_1]] step %[[VAL_8]] {
+// CHECK: %[[VAL_10:.*]] = hlfir.designate %[[VAL_0]] (%[[VAL_9]]) typeparams %[[VAL_3]] : (!fir.box<!fir.array<?x!fir.char<1,?>>>, index, index) -> !fir.boxchar<1>
+ // concatenation
+// CHECK: %[[VAL_30:.*]]:2 = hlfir.declare %[[VAL_14:.*]] typeparams %[[VAL_13:.*]] {uniq_name = "tmp"} : (!fir.ref<!fir.char<1,?>>, index) -> (!fir.boxchar<1>, !fir.ref<!fir.char<1,?>>)
+// CHECK: %[[VAL_31:.*]] = arith.constant false
+// CHECK: %[[VAL_32:.*]] = fir.undefined tuple<!fir.boxchar<1>, i1>
+// CHECK: %[[VAL_33:.*]] = fir.insert_value %[[VAL_32]], %[[VAL_31]], [1 : index] : (tuple<!fir.boxchar<1>, i1>, i1) -> tuple<!fir.boxchar<1>, i1>
+// CHECK: %[[VAL_34:.*]] = fir.insert_value %[[VAL_33]], %[[VAL_30]]#0, [0 : index] : (tuple<!fir.boxchar<1>, i1>, !fir.boxchar<1>) -> tuple<!fir.boxchar<1>, i1>
+// CHECK: %[[VAL_35:.*]] = hlfir.designate %[[VAL_6]]#0 (%[[VAL_9]]) typeparams %[[VAL_2]] : (!fir.box<!fir.array<?x!fir.char<1,?>>>, index, index) -> !fir.boxchar<1>
+// CHECK: hlfir.assign %[[VAL_30]]#0 to %[[VAL_35]] : !fir.boxchar<1>, !fir.boxchar<1>
+// CHECK: }
+// CHECK: %[[VAL_36:.*]] = fir.undefined tuple<!fir.box<!fir.array<?x!fir.char<1,?>>>, i1>
+// CHECK: %[[VAL_37:.*]] = fir.insert_value %[[VAL_36]], %[[VAL_7]], [1 : index] : (tuple<!fir.box<!fir.array<?x!fir.char<1,?>>>, i1>, i1) -> tuple<!fir.box<!fir.array<?x!fir.char<1,?>>>, i1>
+// CHECK: %[[VAL_38:.*]] = fir.insert_value %[[VAL_37]], %[[VAL_6]]#0, [0 : index] : (tuple<!fir.box<!fir.array<?x!fir.char<1,?>>>, i1>, !fir.box<!fir.array<?x!fir.char<1,?>>>) -> tuple<!fir.box<!fir.array<?x!fir.char<1,?>>>, i1>
+
+
+func.func @derived_transpose(%arg0: !fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, %arg1: index, %arg2: index) {
+ %0 = fir.shape %arg2, %arg1 : (index, index) -> !fir.shape<2>
+ %1 = hlfir.elemental %0 : (!fir.shape<2>) -> !hlfir.expr<?x?x!fir.type<t{field:f32}>> {
+ ^bb0(%arg4: index, %arg5: index):
+ %2 = hlfir.designate %arg0 (%arg4, %arg5) : (!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, index, index) -> !fir.box<!fir.type<t{field:f32}>>
+ %3 = hlfir.as_expr %2 : (!fir.box<!fir.type<t{field:f32}>>) -> !hlfir.expr<!fir.type<t{field:f32}>>
+ hlfir.yield_element %3 : !hlfir.expr<!fir.type<t{field:f32}>>
+ }
+ return
+}
+// CHECK-LABEL: func.func @derived_transpose(
+// CHECK-SAME: %[[VAL_0:.*]]: !fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>,
+// CHECK-SAME: %[[VAL_1:[^:]*]]: index,
+// CHECK-SAME: %[[VAL_2:.*]]: index) {
+// CHECK: %[[VAL_3:.*]] = fir.shape %[[VAL_2]], %[[VAL_1]] : (index, index) -> !fir.shape<2>
+// CHECK: %[[VAL_4:.*]] = fir.allocmem !fir.array<?x?x!fir.type<t{field:f32}>>, %[[VAL_2]], %[[VAL_1]] {bindc_name = ".tmp.array", uniq_name = ""}
+// CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %[[VAL_4]](%[[VAL_3]]) {uniq_name = ".tmp.array"} : (!fir.heap<!fir.array<?x?x!fir.type<t{field:f32}>>>, !fir.shape<2>) -> (!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, !fir.heap<!fir.array<?x?x!fir.type<t{field:f32}>>>)
+// CHECK: %[[VAL_6:.*]] = arith.constant true
+// CHECK: %[[VAL_7:.*]] = arith.constant 1 : index
+// CHECK: fir.do_loop %[[VAL_8:.*]] = %[[VAL_7]] to %[[VAL_1]] step %[[VAL_7]] {
+// CHECK: fir.do_loop %[[VAL_9:.*]] = %[[VAL_7]] to %[[VAL_2]] step %[[VAL_7]] {
+// CHECK: %[[VAL_10:.*]] = hlfir.designate %[[VAL_0]] (%[[VAL_9]], %[[VAL_8]]) : (!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, index, index) -> !fir.box<!fir.type<t{field:f32}>>
+// CHECK: %[[VAL_11:.*]] = hlfir.designate %[[VAL_5]]#0 (%[[VAL_9]], %[[VAL_8]]) : (!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, index, index) -> !fir.ref<!fir.type<t{field:f32}>>
+// CHECK: hlfir.assign %[[VAL_10]] to %[[VAL_11]] : !fir.box<!fir.type<t{field:f32}>>, !fir.ref<!fir.type<t{field:f32}>>
+// CHECK: }
+// CHECK: }
+// CHECK: %[[VAL_12:.*]] = fir.undefined tuple<!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, i1>
+// CHECK: %[[VAL_13:.*]] = fir.insert_value %[[VAL_12]], %[[VAL_6]], [1 : index] : (tuple<!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, i1>, i1) -> tuple<!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, i1>
+// CHECK: %[[VAL_14:.*]] = fir.insert_value %[[VAL_13]], %[[VAL_5]]#0, [0 : index] : (tuple<!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, i1>, !fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>) -> tuple<!fir.box<!fir.array<?x?x!fir.type<t{field:f32}>>>, i1>