[flang-commits] [flang] 75db341 - [flang][codegen] Add a conversion for `!fir.coordinate_of` - part 1
Andrzej Warzynski via flang-commits
flang-commits at lists.llvm.org
Fri Dec 10 00:35:24 PST 2021
Author: Andrzej Warzynski
Date: 2021-12-10T08:34:26Z
New Revision: 75db341d5a12783f8048ef9968896871c580e6b7
URL: https://github.com/llvm/llvm-project/commit/75db341d5a12783f8048ef9968896871c580e6b7
DIFF: https://github.com/llvm/llvm-project/commit/75db341d5a12783f8048ef9968896871c580e6b7.diff
LOG: [flang][codegen] Add a conversion for `!fir.coordinate_of` - part 1
This patch extends the `FIRToLLVMLowering` pass in Flang by adding a
hook to transform `!fir.coordinate_of` into a sequence of LLVM MLIR
instructions.
The following cases are currently supported:
1. the input object is a `!fir.complex` (wrapped in e.g. `!fir.ref` or
`!fir.box`)
2. the input object is wrapped in a `!fir.box` (including e.g.
`!fir.array`).
Note that `!fir.complex` inside a `!fir.box` falls under case 1. above
(i.e. it's a special case regardless of the wrapping type).
This is part of the upstreaming effort from the `!fir-dev` branch in [1].
Differential Revision: https://reviews.llvm.org/D114159
Co-authored-by: Jean Perier <jperier at nvidia.com>
Co-authored-by: Eric Schweitz <eschweitz at nvidia.com>
Co-authored-by: V Donaldson <vdonaldson at nvidia.com>
Added:
flang/test/Fir/Todo/cordinate_of_1.fir
flang/test/Fir/Todo/cordinate_of_2.fir
flang/test/Fir/Todo/cordinate_of_3.fir
flang/test/Fir/Todo/cordinate_of_4.fir
Modified:
flang/lib/Optimizer/CodeGen/CodeGen.cpp
flang/lib/Optimizer/CodeGen/TypeConverter.h
flang/test/Fir/convert-to-llvm.fir
Removed:
################################################################################
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index 5f21f4a2c78a0..e1907f6fa77dd 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -2910,6 +2910,168 @@ struct FieldIndexOpConversion : public FIROpConversion<fir::FieldIndexOp> {
}
};
+/// Convert to (memory) reference to a reference to a subobject.
+/// The coordinate_of op is a Swiss army knife operation that can be used on
+/// (memory) references to records, arrays, complex, etc. as well as boxes.
+/// With unboxed arrays, there is the restriction that the array have a static
+/// shape in all but the last column.
+struct CoordinateOpConversion
+ : public FIROpAndTypeConversion<fir::CoordinateOp> {
+ using FIROpAndTypeConversion::FIROpAndTypeConversion;
+
+ mlir::LogicalResult
+ doRewrite(fir::CoordinateOp coor, mlir::Type ty, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const override {
+ mlir::ValueRange operands = adaptor.getOperands();
+
+ mlir::Location loc = coor.getLoc();
+ mlir::Value base = operands[0];
+ mlir::Type baseObjectTy = coor.getBaseType();
+ mlir::Type objectTy = fir::dyn_cast_ptrOrBoxEleTy(baseObjectTy);
+ assert(objectTy && "fir.coordinate_of expects a reference type");
+
+ // Complex type - basically, extract the real or imaginary part
+ if (fir::isa_complex(objectTy)) {
+ mlir::LLVM::ConstantOp c0 =
+ genConstantIndex(loc, lowerTy().indexType(), rewriter, 0);
+ SmallVector<mlir::Value> offs = {c0, operands[1]};
+ mlir::Value gep = genGEP(loc, ty, rewriter, base, offs);
+ rewriter.replaceOp(coor, gep);
+ return success();
+ }
+
+ // Box type - get the base pointer from the box
+ if (auto boxTy = baseObjectTy.dyn_cast<fir::BoxType>()) {
+ doRewriteBox(coor, ty, operands, loc, rewriter);
+ return success();
+ }
+
+ // Sequence type (e.g. fir.array)
+ if (auto arrTy = objectTy.dyn_cast<fir::SequenceType>()) {
+ doRewriteSequence(loc);
+ return success();
+ }
+
+ return rewriter.notifyMatchFailure(
+ coor, "fir.coordinate_of base operand has unsupported type");
+ }
+
+ unsigned getFieldNumber(fir::RecordType ty, mlir::Value op) const {
+ return fir::hasDynamicSize(ty)
+ ? op.getDefiningOp()
+ ->getAttrOfType<mlir::IntegerAttr>("field")
+ .getInt()
+ : getIntValue(op);
+ }
+
+ int64_t getIntValue(mlir::Value val) const {
+ assert(val && val.dyn_cast<mlir::OpResult>() && "must not be null value");
+ mlir::Operation *defop = val.getDefiningOp();
+
+ if (auto constOp = dyn_cast<mlir::arith::ConstantIntOp>(defop))
+ return constOp.value();
+ if (auto llConstOp = dyn_cast<mlir::LLVM::ConstantOp>(defop))
+ if (auto attr = llConstOp.value().dyn_cast<mlir::IntegerAttr>())
+ return attr.getValue().getSExtValue();
+ fir::emitFatalError(val.getLoc(), "must be a constant");
+ }
+
+private:
+ void doRewriteBox(fir::CoordinateOp coor, mlir::Type ty,
+ mlir::ValueRange operands, mlir::Location loc,
+ mlir::ConversionPatternRewriter &rewriter) const {
+ mlir::Type boxObjTy = coor.getBaseType();
+ assert(boxObjTy.dyn_cast<fir::BoxType>() && "This is not a `fir.box`");
+
+ mlir::Value boxBaseAddr = operands[0];
+
+ // 1. SPECIAL CASE (uses `fir.len_param_index`):
+ // %box = ... : !fir.box<!fir.type<derived{len1:i32}>>
+ // %lenp = fir.len_param_index len1, !fir.type<derived{len1:i32}>
+ // %addr = coordinate_of %box, %lenp
+ if (coor.getNumOperands() == 2) {
+ mlir::Operation *coordinateDef = (*coor.coor().begin()).getDefiningOp();
+ if (isa_and_nonnull<fir::LenParamIndexOp>(coordinateDef)) {
+ TODO(loc,
+ "fir.coordinate_of - fir.len_param_index is not supported yet");
+ }
+ }
+
+ // 2. GENERAL CASE:
+ // 2.1. (`fir.array`)
+ // %box = ... : !fix.box<!fir.array<?xU>>
+ // %idx = ... : index
+ // %resultAddr = coordinate_of %box, %idx : !fir.ref<U>
+ // 2.2 (`fir.derived`)
+ // %box = ... : !fix.box<!fir.type<derived_type{field_1:i32}>>
+ // %idx = ... : i32
+ // %resultAddr = coordinate_of %box, %idx : !fir.ref<i32>
+ // 2.3 (`fir.derived` inside `fir.array`)
+ // %box = ... : !fir.box<!fir.array<10 x !fir.type<derived_1{field_1:f32, field_2:f32}>>>
+ // %idx1 = ... : index
+ // %idx2 = ... : i32
+ // %resultAddr = coordinate_of %box, %idx1, %idx2 : !fir.ref<f32>
+ // 2.4. TODO: Either document or disable any other case that the following
+ // implementation might convert.
+ mlir::LLVM::ConstantOp c0 =
+ genConstantIndex(loc, lowerTy().indexType(), rewriter, 0);
+ mlir::Value resultAddr =
+ loadBaseAddrFromBox(loc, getBaseAddrTypeFromBox(boxBaseAddr.getType()),
+ boxBaseAddr, rewriter);
+ auto currentObjTy = fir::dyn_cast_ptrOrBoxEleTy(boxObjTy);
+ mlir::Type voidPtrTy = ::getVoidPtrType(coor.getContext());
+
+ for (unsigned i = 1, last = operands.size(); i < last; ++i) {
+ if (auto arrTy = currentObjTy.dyn_cast<fir::SequenceType>()) {
+ if (i != 1)
+ TODO(loc, "fir.array nested inside other array and/or derived type");
+ // Applies byte strides from the box. Ignore lower bound from box
+ // since fir.coordinate_of indexes are zero based. Lowering takes care
+ // of lower bound aspects. This both accounts for dynamically sized
+ // types and non contiguous arrays.
+ auto idxTy = lowerTy().indexType();
+ mlir::Value off = genConstantIndex(loc, idxTy, rewriter, 0);
+ for (unsigned index = i, lastIndex = i + arrTy.getDimension();
+ index < lastIndex; ++index) {
+ mlir::Value stride =
+ loadStrideFromBox(loc, operands[0], index - i, rewriter);
+ auto sc = rewriter.create<mlir::LLVM::MulOp>(loc, idxTy,
+ operands[index], stride);
+ off = rewriter.create<mlir::LLVM::AddOp>(loc, idxTy, sc, off);
+ }
+ auto voidPtrBase =
+ rewriter.create<mlir::LLVM::BitcastOp>(loc, voidPtrTy, resultAddr);
+ SmallVector<mlir::Value> args{voidPtrBase, off};
+ resultAddr = rewriter.create<mlir::LLVM::GEPOp>(loc, voidPtrTy, args);
+ i += arrTy.getDimension() - 1;
+ currentObjTy = arrTy.getEleTy();
+ } else if (auto recTy = currentObjTy.dyn_cast<fir::RecordType>()) {
+ auto recRefTy =
+ mlir::LLVM::LLVMPointerType::get(lowerTy().convertType(recTy));
+ mlir::Value nxtOpnd = operands[i];
+ auto memObj =
+ rewriter.create<mlir::LLVM::BitcastOp>(loc, recRefTy, resultAddr);
+ llvm::SmallVector<mlir::Value> args = {memObj, c0, nxtOpnd};
+ currentObjTy = recTy.getType(getFieldNumber(recTy, nxtOpnd));
+ auto llvmCurrentObjTy = lowerTy().convertType(currentObjTy);
+ auto gep = rewriter.create<mlir::LLVM::GEPOp>(
+ loc, mlir::LLVM::LLVMPointerType::get(llvmCurrentObjTy), args);
+ resultAddr =
+ rewriter.create<mlir::LLVM::BitcastOp>(loc, voidPtrTy, gep);
+ } else {
+ fir::emitFatalError(loc, "unexpected type in coordinate_of");
+ }
+ }
+
+ rewriter.replaceOpWithNewOp<mlir::LLVM::BitcastOp>(coor, ty, resultAddr);
+ return;
+ }
+
+ void doRewriteSequence(mlir::Location loc) const {
+ TODO(loc, "fir.coordinate_of codegen for sequence types");
+ }
+};
+
} // namespace
namespace {
@@ -2939,7 +3101,7 @@ class FIRToLLVMLowering : public fir::FIRToLLVMLoweringBase<FIRToLLVMLowering> {
BoxIsAllocOpConversion, BoxIsArrayOpConversion, BoxIsPtrOpConversion,
BoxProcHostOpConversion, BoxRankOpConversion, BoxTypeDescOpConversion,
CallOpConversion, CmpcOpConversion, ConstcOpConversion,
- ConvertOpConversion, DispatchOpConversion, DispatchTableOpConversion,
+ ConvertOpConversion, CoordinateOpConversion, DispatchOpConversion, DispatchTableOpConversion,
DTEntryOpConversion, DivcOpConversion, EmboxOpConversion,
EmboxCharOpConversion, EmboxProcOpConversion, ExtractValueOpConversion,
FieldIndexOpConversion, FirEndOpConversion, FreeMemOpConversion,
diff --git a/flang/lib/Optimizer/CodeGen/TypeConverter.h b/flang/lib/Optimizer/CodeGen/TypeConverter.h
index 29a19b6e27270..91e8ebfea7d72 100644
--- a/flang/lib/Optimizer/CodeGen/TypeConverter.h
+++ b/flang/lib/Optimizer/CodeGen/TypeConverter.h
@@ -31,6 +31,7 @@ static constexpr unsigned kTypePosInBox = 4;
static constexpr unsigned kAttributePosInBox = 5;
static constexpr unsigned kF18AddendumPosInBox = 6;
static constexpr unsigned kDimsPosInBox = 7;
+static constexpr unsigned kStridePosInDim = 2;
static constexpr unsigned kOptTypePtrPosInBox = 8;
static constexpr unsigned kOptRowTypePosInBox = 9;
// Position of the
diff erent values in [dims]
diff --git a/flang/test/Fir/Todo/cordinate_of_1.fir b/flang/test/Fir/Todo/cordinate_of_1.fir
new file mode 100644
index 0000000000000..376f508cb75c9
--- /dev/null
+++ b/flang/test/Fir/Todo/cordinate_of_1.fir
@@ -0,0 +1,12 @@
+// RUN: %not_todo_cmd fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s 2>&1 | FileCheck %s
+
+// `fir.coordinate_of` - derived type with `fir.len_param_index`. As
+// `fir.len_param_index` is not implemented yet, that's the error that's
+// currently being generated (this error is generated before trying to convert
+// `fir.coordinate_of`)
+func @coordinate_box_derived_with_fir_len(%arg0: !fir.box<!fir.type<derived_2{len1:i32}>>) {
+// CHECK: not yet implemented fir.len_param_index codegen
+ %e = fir.len_param_index len1, !fir.type<derived_2{len1:i32}>
+ %q = fir.coordinate_of %arg0, %e : (!fir.box<!fir.type<derived_2{len1:i32}>>, !fir.len) -> !fir.ref<i32>
+ return
+}
diff --git a/flang/test/Fir/Todo/cordinate_of_2.fir b/flang/test/Fir/Todo/cordinate_of_2.fir
new file mode 100644
index 0000000000000..4ff935883b0e3
--- /dev/null
+++ b/flang/test/Fir/Todo/cordinate_of_2.fir
@@ -0,0 +1,10 @@
+// RUN: %not_todo_cmd fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s 2>&1 | FileCheck %s
+
+// CHECK: not yet implemented fir.array nested inside other array and/or derived type
+
+// `!fir.coordinate_of` - `!fir.array` inside "boxed" `!fir.type`
+func @coordinate_box_array_inside_derived(%arg0: !fir.box<!fir.type<derived_2{field_1:!fir.array<10 x i32>, field_2:i32}>>, %arg1 : index) {
+ %idx0 = arith.constant 0 : i32
+ %q = fir.coordinate_of %arg0, %idx0, %arg1 : (!fir.box<!fir.type<derived_2{field_1:!fir.array<10 x i32>, field_2:i32}>>, i32, index) -> !fir.ref<f32>
+ return
+}
diff --git a/flang/test/Fir/Todo/cordinate_of_3.fir b/flang/test/Fir/Todo/cordinate_of_3.fir
new file mode 100644
index 0000000000000..00d6ab4717f6f
--- /dev/null
+++ b/flang/test/Fir/Todo/cordinate_of_3.fir
@@ -0,0 +1,10 @@
+// RUN: %not_todo_cmd fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s 2>&1 | FileCheck %s
+
+// CHECK: not yet implemented fir.array nested inside other array and/or derived type
+
+// `fir.coordinate_of` - `fir.array` inside "boxed" `!fir.type<derived_1{!fir.type<derived_2{}>}` (i.e. nested `!fir.type`)
+func @coordinate_box_array_inside_derived(%arg0: !fir.box<!fir.type<derived_1{field_1:!fir.type<derived_2{field_2:!fir.array<10 x i32>}>}>>, %arg1 : index) {
+ %idx0 = arith.constant 0 : i32
+ %q = fir.coordinate_of %arg0, %idx0, %idx0, %arg1 : (!fir.box<!fir.type<derived_1{field_1:!fir.type<derived_2{field_2:!fir.array<10 x i32>}>}>>, i32, i32, index) -> !fir.ref<f32>
+ return
+}
diff --git a/flang/test/Fir/Todo/cordinate_of_4.fir b/flang/test/Fir/Todo/cordinate_of_4.fir
new file mode 100644
index 0000000000000..95f2a8451e614
--- /dev/null
+++ b/flang/test/Fir/Todo/cordinate_of_4.fir
@@ -0,0 +1,11 @@
+// RUN: %not_todo_cmd fir-opt --fir-to-llvm-ir="target=x86_64-unknown-linux-gnu" %s 2>&1 | FileCheck %s
+
+// `!fir.coordinate_of` - derived type with `!fir.len_param_index`. As
+// `!fir.len_param_index` is not implemented yet, the error that we hit is
+// related to `!fir.len_param_index` rather than `!fir.coordinate_of`.
+func @coordinate_box_derived_with_fir_len(%arg0: !fir.box<!fir.type<derived_2{len1:i32}>>) {
+// CHECK: not yet implemented fir.len_param_index codegen
+ %e = fir.len_param_index len1, !fir.type<derived_2{len1:i32}>
+ %q = fir.coordinate_of %arg0, %e : (!fir.box<!fir.type<derived_2{len1:i32}>>, !fir.len) -> !fir.ref<i32>
+ return
+}
diff --git a/flang/test/Fir/convert-to-llvm.fir b/flang/test/Fir/convert-to-llvm.fir
index 86fd9d366b533..48ac9c44d114e 100644
--- a/flang/test/Fir/convert-to-llvm.fir
+++ b/flang/test/Fir/convert-to-llvm.fir
@@ -2156,3 +2156,234 @@ func @foo(%arg0: !fir.box<!fir.array<?x!fir.type<t{i:i32,c:!fir.char<1,10>}>>>)
//CHECK: llvm.call @bar(%[[RESULT_BOX_REF]]) : (!llvm.ptr<struct<(ptr<i8>, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>)>>) -> ()
//CHECK: llvm.return
//CHECK: }
+
+// -----
+
+// Test `fir.coordinate_of` conversion
+
+// 1. COMPLEX TYPE (`fir.complex` is a special case)
+// Complex type wrapped in `fir.ref`
+func @coordinate_ref_complex(%arg0: !fir.ref<!fir.complex<16>>, %arg1: index) {
+ %p = fir.coordinate_of %arg0, %arg1 : (!fir.ref<!fir.complex<16>>, index) -> !fir.ref<f32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_ref_complex
+// CHECK-SAME: %[[ARG0:.*]]: !llvm.ptr<struct<(f128, f128)>>
+// CHECK-SAME: %[[COORDINATE:.*]]: i64) {
+// CHECK: %[[C0:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %{{.*}} = llvm.getelementptr %[[ARG0]][%[[C0]], %[[COORDINATE]]] : (!llvm.ptr<struct<(f128, f128)>>, i64, i64) -> !llvm.ptr<f32>
+// CHECK-NEXT: llvm.return
+
+// Complex type wrapped in `fir.box`
+func @coordinate_box_complex(%arg0: !fir.box<!fir.complex<16>>, %arg1: index) {
+ %p = fir.coordinate_of %arg0, %arg1 : (!fir.box<!fir.complex<16>>, index) -> !fir.ref<f32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_box_complex
+// CHECK-SAME: %[[BOX:.*]]: !llvm.ptr<struct<(ptr<struct<(f128, f128)>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}})>>
+// CHECK-SAME: %[[COORDINATE:.*]]: i64) {
+// CHECK: %[[C0:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %{{.*}} = llvm.getelementptr %[[BOX]][%[[C0]], %[[COORDINATE]]] : (!llvm.ptr<struct<(ptr<struct<(f128, f128)>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}})>>, i64, i64) -> !llvm.ptr<f32>
+// CHECK-NEXT: llvm.return
+
+// -----
+
+// Test `fir.coordinate_of` conversion
+
+// 2. BOX TYPE (objects wrapped in `fir.box`)
+// Derived type - basic case (1 index)
+func @coordinate_box_derived_1(%arg0: !fir.box<!fir.type<derived_1{field_1:i32, field_2:i32}>>) {
+ %idx = fir.field_index field_2, !fir.type<derived_1{field_1:i32, field_2:i32}>
+ %q = fir.coordinate_of %arg0, %idx : (!fir.box<!fir.type<derived_1{field_1:i32, field_2:i32}>>, !fir.field) -> !fir.ref<i32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_box_derived_1
+// CHECK-SAME: %[[BOX:.*]]: !llvm.ptr<struct<(ptr<struct<"derived_1", (i32, i32)>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr<i8>, array<1 x i64>)>>)
+// CHECK: %[[COORDINATE:.*]] = llvm.mlir.constant(1 : i32) : i32
+// CHECK: %[[C0_3:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[C0_1:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK: %[[C0_2:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK: %[[DERIVED_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[C0_1]], %[[C0_2]]] : (!llvm.ptr<struct<(ptr<struct<"derived_1", (i32, i32)>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr<i8>, array<1 x i64>)>>, i32, i32) -> !llvm.ptr<ptr<struct<"derived_1", (i32, i32)>>>
+// CHECK: %[[DERIVED_VAL:.*]] = llvm.load %[[DERIVED_ADDR]] : !llvm.ptr<ptr<struct<"derived_1", (i32, i32)>>>
+// CHECK: %[[DERIVED_CAST:.*]] = llvm.bitcast %[[DERIVED_VAL]] : !llvm.ptr<struct<"derived_1", (i32, i32)>> to !llvm.ptr<struct<"derived_1", (i32, i32)>>
+// CHECK: %[[SUBOBJECT_ADDR:.*]] = llvm.getelementptr %[[DERIVED_CAST]][%[[C0_3]], %[[COORDINATE]]] : (!llvm.ptr<struct<"derived_1", (i32, i32)>>, i64, i32) -> !llvm.ptr<i32>
+// CHECK: %[[CAST_TO_I8_PTR:.*]] = llvm.bitcast %7 : !llvm.ptr<i32> to !llvm.ptr<i8>
+// CHECK: %{{.*}} = llvm.bitcast %[[CAST_TO_I8_PTR]] : !llvm.ptr<i8> to !llvm.ptr<i32>
+// CHECK-NEXT: llvm.return
+
+// Derived type - basic case (2 indices)
+func @coordinate_box_derived_2(%arg0: !fir.box<!fir.type<derived_2{field_1:!fir.type<another_derived{inner1:i32, inner2:f32}>, field_2:i32}>>) {
+ %idx0 = fir.field_index field_1, !fir.type<derived_2{field_1:!fir.type<another_derived{inner1:i32, inner2:f32}>, field_2:i32}>
+ %idx1 = fir.field_index inner2, !fir.type<another_derived{inner1:i32, inner2:f32}>
+ %q = fir.coordinate_of %arg0, %idx0, %idx1 : (!fir.box<!fir.type<derived_2{field_1:!fir.type<another_derived{inner1:i32, inner2:f32}>, field_2:i32}>>, !fir.field, !fir.field) -> !fir.ref<i32>
+ return
+}
+
+// CHECK-LABEL: llvm.func @coordinate_box_derived_2
+// CHECK-SAME: (%[[BOX:.*]]: !llvm.ptr<struct<(ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr<i8>, array<1 x i64>)>>)
+// CHECK-NEXT: %[[C0_0:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(1 : i32) : i32
+// CHECK-NEXT: %[[C0_3:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK-NEXT: %[[C0_1:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[C0_2:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[DERIVED_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[C0_1]], %[[C0_2]]] : (!llvm.ptr<struct<(ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>>, i{{.*}}, i{{.*}}32, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, ptr<i8>, array<1 x i64>)>>, i32, i32) -> !llvm.ptr<ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>>>
+// CHECK-NEXT: %[[DERIVED_VAL:.*]] = llvm.load %[[DERIVED_ADDR]] : !llvm.ptr<ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>>>
+// CHECK-NEXT: %[[DERIVED_CAST_I8_PTR:.*]] = llvm.bitcast %[[DERIVED_VAL]] : !llvm.ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>> to !llvm.ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>>
+// CHECK-NEXT: %[[ANOTHER_DERIVED_ADDR:.*]] = llvm.getelementptr %[[DERIVED_CAST_I8_PTR]][%[[C0_3]], %[[C0_0]]] : (!llvm.ptr<struct<"derived_2", (struct<"another_derived", (i32, f32)>, i32)>>, i64, i32) -> !llvm.ptr<struct<"another_derived", (i32, f32)>>
+// CHECK-NEXT: %[[ANOTHER_DERIVED_ADDR_AS_VOID_PTR:.*]] = llvm.bitcast %[[ANOTHER_DERIVED_ADDR]] : !llvm.ptr<struct<"another_derived", (i32, f32)>> to !llvm.ptr<i8>
+// CHECK-NEXT: %[[ANOTHER_DERIVED_RECAST:.*]] = llvm.bitcast %[[ANOTHER_DERIVED_ADDR_AS_VOID_PTR]] : !llvm.ptr<i8> to !llvm.ptr<struct<"another_derived", (i32, f32)>>
+// CHECK-NEXT: %[[SUBOBJECT_ADDR:.*]] = llvm.getelementptr %[[ANOTHER_DERIVED_RECAST]][%[[C0_3]], %[[C1]]] : (!llvm.ptr<struct<"another_derived", (i32, f32)>>, i64, i32) -> !llvm.ptr<f32>
+// CHECK-NEXT: %[[SUBOBJECT_AS_VOID_PTR:.*]] = llvm.bitcast %[[SUBOBJECT_ADDR]] : !llvm.ptr<f32> to !llvm.ptr<i8>
+// CHECK-NEXT: %{{.*}} = llvm.bitcast %[[SUBOBJECT_AS_VOID_PTR]] : !llvm.ptr<i8> to !llvm.ptr<i32>
+// CHECK-NEXT: llvm.return
+
+// TODO: Derived type - special case with `fir.len_param_index`
+
+// -----
+
+// Test `fir.coordinate_of` conversion
+
+// 3. BOX TYPE - `fir.array` wrapped in `fir.box`
+// `fir.array` inside a `fir.box` (1d)
+func @coordinate_box_array_1d(%arg0: !fir.box<!fir.array<10 x f32>>, %arg1: index) {
+ %p = fir.coordinate_of %arg0, %arg1 : (!fir.box<!fir.array<10 x f32>>, index) -> !fir.ref<f32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_box_array_1d
+// CHECK-SAME: %[[BOX:.*]]: !llvm.ptr<struct<(ptr<array<10 x f32>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i64>>)>>
+// CHECK-SAME: %[[COORDINATE:.*]]: i64
+// CHECK-NEXT: %{{.*}} = llvm.mlir.constant(0 : i64) : i64
+// There's only one box here. Its index is `0`. Generate it.
+// CHECK-NEXT: %[[BOX_IDX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[BOX_1ST_ELEM_IDX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[ARRAY_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX]], %[[BOX_1ST_ELEM_IDX]]] : (!llvm.ptr<struct<(ptr<array<10 x f32>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i64>>)>>, i32, i32) -> !llvm.ptr<ptr<array<10 x f32>>>
+// CHECK-NEXT: %[[ARRAY_OBJECT:.*]] = llvm.load %[[ARRAY_ADDR]] : !llvm.ptr<ptr<array<10 x f32>>>
+// CHECK-NEXT: %[[OFFSET_INIT:.*]] = llvm.mlir.constant(0 : i64) : i64
+// Same as [[BOX_IDX]], just recreated.
+// CHECK-NEXT: %[[BOX_IDX_1:.*]] = llvm.mlir.constant(0 : i32) : i32
+// Index of the array that contains the CFI_dim_t objects
+// CHECK-NEXT: %[[CFI_DIM_IDX:.*]] = llvm.mlir.constant(7 : i32) : i32
+// Index of the 1st CFI_dim_t object (corresonds the the 1st dimension)
+// CHECK-NEXT: %[[DIM_1_IDX:.*]] = llvm.mlir.constant(0 : i64) : i64
+// Index of the memory stride within a CFI_dim_t object
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE:.*]] = llvm.mlir.constant(2 : i32) : i32
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX_1]], %[[CFI_DIM_IDX]], %[[DIM_1_IDX]], %[[DIM_1_MEM_STRIDE]]] : (!llvm.ptr<struct<(ptr<array<10 x f32>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i64>>)>>, i32, i32, i64, i32) -> !llvm.ptr<i64>
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE_VAL:.*]] = llvm.load %[[DIM_1_MEM_STRIDE_ADDR]] : !llvm.ptr<i64>
+// CHECK-NEXT: %[[BYTE_OFFSET:.*]] = llvm.mul %[[COORDINATE]], %[[DIM_1_MEM_STRIDE_VAL]] : i64
+// CHECK-NEXT: %[[SUBOJECT_OFFSET:.*]] = llvm.add %[[BYTE_OFFSET]], %[[OFFSET_INIT]] : i64
+// CHECK-NEXT: %[[ARRAY_OBJECT_AS_VOID_PTR:.*]] = llvm.bitcast %[[ARRAY_OBJECT]] : !llvm.ptr<array<10 x f32>> to !llvm.ptr<i8>
+// CHECK-NEXT: %[[SUBOBJECT_ADDR:.*]] = llvm.getelementptr %[[ARRAY_OBJECT_AS_VOID_PTR]][%[[SUBOJECT_OFFSET]]] : (!llvm.ptr<i8>, i64) -> !llvm.ptr<i8>
+// CHECK-NEXT: %[[RETURN_VAL:.*]] = llvm.bitcast %[[SUBOBJECT_ADDR]] : !llvm.ptr<i8> to !llvm.ptr<f32>
+// CHECK-NEXT: llvm.return
+
+// `fir.array` inside a `fir.box` (1d) - dynamic size
+func @coordinate_of_box_dynamic_array_1d(%arg0: !fir.box<!fir.array<? x f32>>, %arg1: index) {
+ %p = fir.coordinate_of %arg0, %arg1 : (!fir.box<!fir.array<? x f32>>, index) -> !fir.ref<f32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_of_box_dynamic_array_1d
+// CHECK-SAME: %[[BOX:.*]]: !llvm.ptr<struct<(ptr<f32>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i64>>)>>
+// CHECK-SAME: %[[COORDINATE:.*]]: i64
+// CHECK-NEXT: %{{.*}} = llvm.mlir.constant(0 : i64) : i64
+// There's only one box here. Its index is `0`. Generate it.
+// CHECK-NEXT: %[[BOX_IDX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[BOX_1ST_ELEM_IDX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[ARRAY_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX]], %[[BOX_1ST_ELEM_IDX]]] : (!llvm.ptr<struct<(ptr<f32>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i64>>)>>, i32, i32) -> !llvm.ptr<ptr<f32>>
+// CHECK-NEXT: %[[ARRAY_OBJECT:.*]] = llvm.load %[[ARRAY_ADDR]] : !llvm.ptr<ptr<f32>>
+// CHECK-NEXT: %[[OFFSET_INIT:.*]] = llvm.mlir.constant(0 : i64) : i64
+// Same as [[BOX_IDX]], just recreated.
+// CHECK-NEXT: %[[BOX_IDX_1:.*]] = llvm.mlir.constant(0 : i32) : i32
+// Index of the array that contains the CFI_dim_t objects
+// CHECK-NEXT: %[[CFI_DIM_IDX:.*]] = llvm.mlir.constant(7 : i32) : i32
+// Index of the 1st CFI_dim_t object (corresonds the the 1st dimension)
+// CHECK-NEXT: %[[DIM_1_IDX:.*]] = llvm.mlir.constant(0 : i64) : i64
+// Index of the memory stride within a CFI_dim_t object
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE:.*]] = llvm.mlir.constant(2 : i32) : i32
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX_1]], %[[CFI_DIM_IDX]], %[[DIM_1_IDX]], %[[DIM_1_MEM_STRIDE]]] : (!llvm.ptr<struct<(ptr<f32>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<1 x array<3 x i64>>)>>, i32, i32, i64, i32) -> !llvm.ptr<i64>
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE_VAL:.*]] = llvm.load %[[DIM_1_MEM_STRIDE_ADDR]] : !llvm.ptr<i64>
+// CHECK-NEXT: %[[BYTE_OFFSET:.*]] = llvm.mul %[[COORDINATE]], %[[DIM_1_MEM_STRIDE_VAL]] : i64
+// CHECK-NEXT: %[[SUBOJECT_OFFSET:.*]] = llvm.add %[[BYTE_OFFSET]], %[[OFFSET_INIT]] : i64
+// CHECK-NEXT: %[[ARRAY_OBJECT_AS_VOID_PTR:.*]] = llvm.bitcast %[[ARRAY_OBJECT]] : !llvm.ptr<f32> to !llvm.ptr<i8>
+// CHECK-NEXT: %[[SUBOBJECT_ADDR:.*]] = llvm.getelementptr %[[ARRAY_OBJECT_AS_VOID_PTR]][%[[SUBOJECT_OFFSET]]] : (!llvm.ptr<i8>, i64) -> !llvm.ptr<i8>
+// CHECK-NEXT: %[[RETURN_VAL:.*]] = llvm.bitcast %[[SUBOBJECT_ADDR]] : !llvm.ptr<i8> to !llvm.ptr<f32>
+// CHECK-NEXT: llvm.return
+
+// `fir.array` inside a `fir.box` (2d)
+func @coordinate_box_array_2d(%arg0: !fir.box<!fir.array<10 x 10 x f32>>, %arg1: index, %arg2: index) {
+ %p = fir.coordinate_of %arg0, %arg1, %arg2 : (!fir.box<!fir.array<10 x 10 x f32>>, index, index) -> !fir.ref<f32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_box_array_2d
+// CHECK-SAME: %[[BOX:.*]]: !llvm.ptr<struct<(ptr<array<10 x array<10 x f32>>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<2 x array<3 x i64>>)>>
+// CHECK-SAME: %[[COORDINATE_1:.*]]: i64, %[[COORDINATE_2:.*]]: i64)
+// CHECK-NEXT: %{{.*}} = llvm.mlir.constant(0 : i64) : i64
+// There's only one box here. Its index is `0`. Generate it.
+// CHECK-NEXT: %[[BOX_IDX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[BOX_1ST_ELEM_IDX:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK-NEXT: %[[ARRAY_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX]], %[[BOX_1ST_ELEM_IDX]]] : (!llvm.ptr<struct<(ptr<array<10 x array<10 x f32>>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<2 x array<3 x i64>>)>>, i32, i32) -> !llvm.ptr<ptr<array<10 x array<10 x f32>>>>
+// CHECK-NEXT: %[[ARRAY_OBJECT:.*]] = llvm.load %[[ARRAY_ADDR]] : !llvm.ptr<ptr<array<10 x array<10 x f32>>>>
+// CHECK-NEXT: %[[OFFSET_INIT:.*]] = llvm.mlir.constant(0 : i64) : i64
+// Same as [[BOX_IDX]], just recreated.
+// CHECK-NEXT: %[[BOX_IDX_1:.*]] = llvm.mlir.constant(0 : i32) : i32
+// Index of the array that contains the CFI_dim_t objects
+// CHECK-NEXT: %[[CFI_DIM_IDX:.*]] = llvm.mlir.constant(7 : i32) : i32
+// Index of the 1st CFI_dim_t object (corresonds the the 1st dimension)
+// CHECK-NEXT: %[[DIM_1_IDX:.*]] = llvm.mlir.constant(0 : i64) : i64
+// Index of the memory stride within a CFI_dim_t object
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE:.*]] = llvm.mlir.constant(2 : i32) : i32
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX_1]], %[[CFI_DIM_IDX]], %[[DIM_1_IDX]], %[[DIM_1_MEM_STRIDE]]] : (!llvm.ptr<struct<(ptr<array<10 x array<10 x f32>>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<2 x array<3 x i64>>)>>, i32, i32, i64, i32) -> !llvm.ptr<i64>
+// CHECK-NEXT: %[[DIM_1_MEM_STRIDE_VAL:.*]] = llvm.load %[[DIM_1_MEM_STRIDE_ADDR]] : !llvm.ptr<i64>
+// CHECK-NEXT: %[[BYTE_OFFSET_1:.*]] = llvm.mul %[[COORDINATE_1]], %[[DIM_1_MEM_STRIDE_VAL]] : i64
+// CHECK-NEXT: %[[SUBOBJECT_OFFSET_1:.*]] = llvm.add %[[BYTE_OFFSET]], %[[OFFSET_INIT]] : i64
+// Same as [[BOX_IDX]], just recreated.
+// CHECK-NEXT: %[[BOX_IDX_2:.*]] = llvm.mlir.constant(0 : i32) : i32
+// Index of the array that contains the CFI_dim_t objects (same as CFI_DIM_IDX, just recreated)
+// CHECK-NEXT: %[[CFI_DIM_IDX_1:.*]] = llvm.mlir.constant(7 : i32) : i32
+// Index of the 1st CFI_dim_t object (corresonds the the 2nd dimension)
+// CHECK-NEXT: %[[DIM_2_IDX:.*]] = llvm.mlir.constant(1 : i64) : i64
+// Index of the memory stride within a CFI_dim_t object
+// CHECK-NEXT: %[[DIM_2_MEM_STRIDE:.*]] = llvm.mlir.constant(2 : i32) : i32
+// CHECK-NEXT: %[[DIM_2_MEM_STRIDE_ADDR:.*]] = llvm.getelementptr %[[BOX]][%[[BOX_IDX_2]], %[[CFI_DIM_IDX_1]], %[[DIM_2_IDX]], %[[DIM_2_MEM_STRIDE]]] : (!llvm.ptr<struct<(ptr<array<10 x array<10 x f32>>>, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, i{{.*}}, array<2 x array<3 x i64>>)>>, i32, i32, i64, i32) -> !llvm.ptr<i64>
+// CHECK-NEXT: %[[DIM_2_MEM_STRIDE_VAL:.*]] = llvm.load %[[DIM_2_MEM_STRIDE_ADDR]] : !llvm.ptr<i64>
+// CHECK-NEXT: %[[BYTE_OFFSET_2:.*]] = llvm.mul %[[COORDINATE_2]], %[[DIM_2_MEM_STRIDE_VAL]] : i64
+// CHECK-NEXT: %[[SUBOBJECT_OFFSET_2:.*]] = llvm.add %[[BYTE_OFFSET_2]], %[[SUBOBJECT_OFFSET_1]] : i64
+// CHECK-NEXT: %[[ARRAY_OBJECT_AS_VOID_PTR:.*]] = llvm.bitcast %[[ARRAY_OBJECT]] : !llvm.ptr<array<10 x array<10 x f32>>> to !llvm.ptr<i8>
+// CHECK-NEXT: %[[SUBOBJECT_ADDR:.*]] = llvm.getelementptr %[[ARRAY_OBJECT_AS_VOID_PTR]][%[[SUBOBJECT_OFFSET_2]]] : (!llvm.ptr<i8>, i64) -> !llvm.ptr<i8>
+// CHECK-NEXT: %[[RETURN_VAL:.*]] = llvm.bitcast %[[SUBOBJECT_ADDR]] : !llvm.ptr<i8> to !llvm.ptr<f32>
+// CHECK-NEXT: llvm.return
+
+// -----
+
+// Test `fir.coordinate_of` conversion
+
+// 4. BOX TYPE - `fir.derived` inside `fir.array`
+func @coordinate_box_derived_inside_array(%arg0: !fir.box<!fir.array<10 x !fir.type<derived_3{field_1:f32, field_2:f32}>>>, %arg1 : index) {
+ %idx0 = fir.field_index field_2, !fir.type<derived_3{field_1:f32, field_2:f32}>
+ %q = fir.coordinate_of %arg0, %arg1, %idx0 : (!fir.box<!fir.array<10 x !fir.type<derived_3{field_1:f32, field_2:f32}>>>, index, !fir.field) -> !fir.ref<f32>
+ return
+}
+// CHECK-LABEL: llvm.func @coordinate_box_derived_inside_array(
+// CHECK-SAME: %[[BOX:.*]]: !llvm.ptr<struct<(ptr<array<10 x struct<"derived_3", (f32, f32)>>>, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>, ptr<i8>, array<1 x i64>)>>,
+// CHECK-SAME: %[[COORDINATE_1:.*]]: i64) {
+// CHECK: %[[COORDINATE_2:.*]] = llvm.mlir.constant(1 : i32) : i32
+// CHECK: %[[VAL_3:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[VAL_4:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK: %[[VAL_5:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK: %[[VAL_6:.*]] = llvm.getelementptr %[[BOX]]{{\[}}%[[VAL_4]], %[[VAL_5]]] : (!llvm.ptr<struct<(ptr<array<10 x struct<"derived_3", (f32, f32)>>>, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>, ptr<i8>, array<1 x i64>)>>, i32, i32) -> !llvm.ptr<ptr<array<10 x struct<"derived_3", (f32, f32)>>>>
+// CHECK: %[[ARRAY:.*]] = llvm.load %[[VAL_6]] : !llvm.ptr<ptr<array<10 x struct<"derived_3", (f32, f32)>>>>
+// CHECK: %[[VAL_8:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[VAL_9:.*]] = llvm.mlir.constant(0 : i32) : i32
+// CHECK: %[[CFI_DIM_IDX:.*]] = llvm.mlir.constant(7 : i32) : i32
+// CHECK: %[[DIM_IDX:.*]] = llvm.mlir.constant(0 : i64) : i64
+// CHECK: %[[DIM_MEM_STRIDE:.*]] = llvm.mlir.constant(2 : i32) : i32
+// CHECK: %[[VAL_13:.*]] = llvm.getelementptr %[[BOX]][%[[VAL_9]], %[[CFI_DIM_IDX]], %[[DIM_IDX]], %[[DIM_MEM_STRIDE]]] : (!llvm.ptr<struct<(ptr<array<10 x struct<"derived_3", (f32, f32)>>>, i64, i32, i8, i8, i8, i8, array<1 x array<3 x i64>>, ptr<i8>, array<1 x i64>)>>, i32, i32, i64, i32) -> !llvm.ptr<i64>
+// CHECK: %[[VAL_14:.*]] = llvm.load %[[VAL_13]] : !llvm.ptr<i64>
+// CHECK: %[[VAL_15:.*]] = llvm.mul %[[COORDINATE_1]], %[[VAL_14]] : i64
+// CHECK: %[[OFFSET:.*]] = llvm.add %[[VAL_15]], %[[VAL_8]] : i64
+// CHECK: %[[VAL_17:.*]] = llvm.bitcast %[[ARRAY]] : !llvm.ptr<array<10 x struct<"derived_3", (f32, f32)>>> to !llvm.ptr<i8>
+// CHECK: %[[VAL_18:.*]] = llvm.getelementptr %[[VAL_17]][%[[OFFSET]]] : (!llvm.ptr<i8>, i64) -> !llvm.ptr<i8>
+// CHECK: %[[DERIVED:.*]] = llvm.bitcast %[[VAL_18]] : !llvm.ptr<i8> to !llvm.ptr<struct<"derived_3", (f32, f32)>>
+// CHECK: %[[VAL_20:.*]] = llvm.getelementptr %[[DERIVED]][%[[VAL_3]], %[[COORDINATE_2]]] : (!llvm.ptr<struct<"derived_3", (f32, f32)>>, i64, i32) -> !llvm.ptr<f32>
+// CHECK: %[[VAL_21:.*]] = llvm.bitcast %[[VAL_20]] : !llvm.ptr<f32> to !llvm.ptr<i8>
+// CHECK: %[[VAL_22:.*]] = llvm.bitcast %[[VAL_21]] : !llvm.ptr<i8> to !llvm.ptr<f32>
+// CHECK: llvm.return
More information about the flang-commits
mailing list