[flang] hlfir.elemental codegen
authorJean Perier <jperier@nvidia.com>
Fri, 16 Dec 2022 08:19:07 +0000 (09:19 +0100)
committerJean Perier <jperier@nvidia.com>
Fri, 16 Dec 2022 08:19:49 +0000 (09:19 +0100)
Without any optimization or when it cannot be optimized before
bufferization, an hlfir.elemental lowers to an array temporary.
Its codegen consists in:
- allocating a temp given the type, shape, and length parameter arguments.
- generating a loop nest given the elemental shape
- inlining the body of the elemental inside the loops, and replacing the
  yield_element by an assignment to an element of the temp.

Differential Revision: https://reviews.llvm.org/D140093

flang/include/flang/Optimizer/Builder/FIRBuilder.h
flang/include/flang/Optimizer/Builder/HLFIRTools.h
flang/lib/Optimizer/Builder/FIRBuilder.cpp
flang/lib/Optimizer/Builder/HLFIRTools.cpp
flang/lib/Optimizer/HLFIR/Transforms/BufferizeHLFIR.cpp
flang/test/HLFIR/elemental-codegen.fir [new file with mode: 0644]

index 148f218..81784f9 100644 (file)
@@ -182,6 +182,13 @@ public:
     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,
@@ -425,16 +432,16 @@ public:
   /// 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
index f1492b2..08cd7d1 100644 (file)
@@ -14,6 +14,7 @@
 #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"
 
@@ -25,6 +26,7 @@ namespace hlfir {
 
 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) {
@@ -253,6 +255,22 @@ hlfir::ElementalOp genElementalOp(mlir::Location loc,
                                   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
index 0c66ed7..e7b8762 100644 (file)
@@ -232,6 +232,20 @@ fir::FirOpBuilder::createTemporary(mlir::Location loc, mlir::Type type,
   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,
index 28791b5..faba6e1 100644 (file)
@@ -15,6 +15,7 @@
 #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.
@@ -484,3 +485,42 @@ hlfir::genElementalOp(mlir::Location loc, fir::FirOpBuilder &builder,
   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};
+}
index 5fcd086..728c1dd 100644 (file)
@@ -95,6 +95,26 @@ static mlir::Value getBufferizedExprMustFreeFlag(mlir::Value bufferizedExpr) {
   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) {
@@ -113,6 +133,21 @@ createTempFromMold(mlir::Location loc, fir::FirOpBuilder &builder,
   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)
@@ -236,11 +271,20 @@ struct EndAssociateOpConversion
   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");
   }
 };
@@ -259,6 +303,79 @@ struct NoReassocOpConversion
   }
 };
 
+/// 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 {
@@ -272,11 +389,13 @@ public:
     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(),
diff --git a/flang/test/HLFIR/elemental-codegen.fir b/flang/test/HLFIR/elemental-codegen.fir
new file mode 100644 (file)
index 0000000..ecdb377
--- /dev/null
@@ -0,0 +1,109 @@
+// 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>