Skip to content

Commit

Permalink
[flang][hlfir] Array constructor lowering [part 4/4]
Browse files Browse the repository at this point in the history
Enable character and derived type array constructor lowering.
Nothing special needs to be done other than lowering the types
before the array constructor lowering.
Derived type are forced to use the runtime for now to avoid
undesired usage of user defined assignment that hlfir.assign
may trigger when using the runtime.

Differential Revision: https://reviews.llvm.org/D144548
  • Loading branch information
jeanPerier committed Feb 24, 2023
1 parent 7f81dd4 commit bc991d9
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 17 deletions.
47 changes: 30 additions & 17 deletions flang/lib/Lower/ConvertArrayConstructor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,20 @@ class ArrayCtorLoweringStrategy {
// and chooses the lowering strategy.
//===----------------------------------------------------------------------===//

/// Helper to lower a scalar extent expression (like implied-do bounds).
static mlir::Value lowerExtentExpr(mlir::Location loc,
Fortran::lower::AbstractConverter &converter,
Fortran::lower::SymMap &symMap,
Fortran::lower::StatementContext &stmtCtx,
const Fortran::evaluate::ExtentExpr &expr) {
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
mlir::IndexType idxTy = builder.getIndexType();
hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
loc, converter, toEvExpr(expr), symMap, stmtCtx);
value = hlfir::loadTrivialScalar(loc, builder, value);
return builder.createConvert(loc, idxTy, value);
}

namespace {
/// Helper class to lower the array constructor type and its length parameters.
/// The length parameters, if any, are only lowered if this does not require
Expand All @@ -503,7 +517,10 @@ struct LengthAndTypeCollector<Fortran::evaluate::SomeDerived> {
&arrayCtorExpr,
Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
mlir::SmallVectorImpl<mlir::Value> &lengths) {
TODO(loc, "collect derived type and length");
// Array constructors cannot be unlimited polymorphic (C7113), so there must
// be a derived type spec available.
return Fortran::lower::translateDerivedTypeToFIRType(
converter, arrayCtorExpr.result().derivedTypeSpec());
}
};

Expand All @@ -517,7 +534,17 @@ struct LengthAndTypeCollector<Character<Kind>> {
const Fortran::evaluate::ArrayConstructor<Character<Kind>> &arrayCtorExpr,
Fortran::lower::SymMap &symMap, Fortran::lower::StatementContext &stmtCtx,
mlir::SmallVectorImpl<mlir::Value> &lengths) {
TODO(loc, "collect character type and length");
llvm::SmallVector<Fortran::lower::LenParameterTy> typeLengths;
if (const Fortran::evaluate::ExtentExpr *lenExpr = arrayCtorExpr.LEN()) {
lengths.push_back(
lowerExtentExpr(loc, converter, symMap, stmtCtx, *lenExpr));
if (std::optional<std::int64_t> cstLen =
Fortran::evaluate::ToInt64(*lenExpr))
typeLengths.push_back(*cstLen);
}
return Fortran::lower::getFIRType(&converter.getMLIRContext(),
Fortran::common::TypeCategory::Character,
Kind, typeLengths);
}
};
} // namespace
Expand Down Expand Up @@ -611,20 +638,6 @@ ArrayCtorAnalysis::ArrayCtorAnalysis(
}
}

/// Helper to lower a scalar extent expression (like implied-do bounds).
static mlir::Value lowerExtentExpr(mlir::Location loc,
Fortran::lower::AbstractConverter &converter,
Fortran::lower::SymMap &symMap,
Fortran::lower::StatementContext &stmtCtx,
const Fortran::evaluate::ExtentExpr &expr) {
fir::FirOpBuilder &builder = converter.getFirOpBuilder();
mlir::IndexType idxTy = builder.getIndexType();
hlfir::Entity value = Fortran::lower::convertExprToHLFIR(
loc, converter, toEvExpr(expr), symMap, stmtCtx);
value = hlfir::loadTrivialScalar(loc, builder, value);
return builder.createConvert(loc, idxTy, value);
}

/// Does \p expr contain no calls to user function?
static bool isCallFreeExpr(const Fortran::evaluate::ExtentExpr &expr) {
for (const Fortran::semantics::Symbol &symbol :
Expand Down Expand Up @@ -679,7 +692,7 @@ static ArrayCtorLoweringStrategy selectArrayCtorLoweringStrategy(
// Based on what was gathered and the result of the analysis, select and
// instantiate the right lowering strategy for the array constructor.
if (!extent || needToEvaluateOneExprToGetLengthParameters ||
analysis.anyArrayExpr)
analysis.anyArrayExpr || declaredType.getEleTy().isa<fir::RecordType>())
return RuntimeTempStrategy(
loc, builder, declaredType,
extent ? std::optional<mlir::Value>(extent) : std::nullopt, lengths,
Expand Down
87 changes: 87 additions & 0 deletions flang/test/Lower/HLFIR/array-ctor-character.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
! Test lowering of character array constructors to HLFIR.
! RUN: bbc -emit-fir -hlfir -o - %s | FileCheck %s

module chararrayctor
character(3), target :: ctarg1 = "abc"
character(3), target :: ctarg2 = "def"
contains

subroutine test_pre_computed_length(c1, c2)
character(*) :: c1, c2
call takes_char([character(3):: c1, c2])
end subroutine
! CHECK-LABEL: func.func @_QMchararrayctorPtest_pre_computed_length(
! CHECK: %[[VAL_9:.*]]:2 = hlfir.declare %{{.*}}Ec1"
! CHECK: %[[VAL_11:.*]]:2 = hlfir.declare %{{.*}}Ec2"
! CHECK: %[[VAL_12:.*]] = arith.constant 2 : index
! CHECK: %[[VAL_13:.*]] = arith.constant 3 : i64
! CHECK: %[[VAL_14:.*]] = fir.convert %[[VAL_13]] : (i64) -> index
! CHECK: %[[VAL_15:.*]] = arith.constant 1 : index
! CHECK: %[[VAL_16:.*]] = fir.allocmem !fir.array<2x!fir.char<1,3>> {bindc_name = ".tmp.arrayctor", uniq_name = ""}
! CHECK: %[[VAL_17:.*]] = fir.shape %[[VAL_12]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_18:.*]]:2 = hlfir.declare %[[VAL_16]](%[[VAL_17]]) typeparams %[[VAL_14]] {uniq_name = ".tmp.arrayctor"} : (!fir.heap<!fir.array<2x!fir.char<1,3>>>, !fir.shape<1>, index) -> (!fir.heap<!fir.array<2x!fir.char<1,3>>>, !fir.heap<!fir.array<2x!fir.char<1,3>>>)
! CHECK: %[[VAL_19:.*]] = arith.constant 3 : i64
! CHECK: %[[VAL_20:.*]] = hlfir.set_length %[[VAL_9]]#0 len %[[VAL_19]] : (!fir.boxchar<1>, i64) -> !hlfir.expr<!fir.char<1,3>>
! CHECK: %[[VAL_21:.*]] = arith.addi %[[VAL_15]], %[[VAL_15]] : index
! CHECK: %[[VAL_22:.*]] = hlfir.designate %[[VAL_18]]#0 (%[[VAL_15]]) typeparams %[[VAL_14]] : (!fir.heap<!fir.array<2x!fir.char<1,3>>>, index, index) -> !fir.ref<!fir.char<1,3>>
! CHECK: hlfir.assign %[[VAL_20]] to %[[VAL_22]] : !hlfir.expr<!fir.char<1,3>>, !fir.ref<!fir.char<1,3>>
! CHECK: %[[VAL_23:.*]] = arith.constant 3 : i64
! CHECK: %[[VAL_24:.*]] = hlfir.set_length %[[VAL_11]]#0 len %[[VAL_23]] : (!fir.boxchar<1>, i64) -> !hlfir.expr<!fir.char<1,3>>
! CHECK: %[[VAL_25:.*]] = hlfir.designate %[[VAL_18]]#0 (%[[VAL_21]]) typeparams %[[VAL_14]] : (!fir.heap<!fir.array<2x!fir.char<1,3>>>, index, index) -> !fir.ref<!fir.char<1,3>>
! CHECK: hlfir.assign %[[VAL_24]] to %[[VAL_25]] : !hlfir.expr<!fir.char<1,3>>, !fir.ref<!fir.char<1,3>>
! CHECK: %[[VAL_26:.*]] = arith.constant true
! CHECK: %[[VAL_27:.*]] = hlfir.as_expr %[[VAL_18]]#0 move %[[VAL_26]] : (!fir.heap<!fir.array<2x!fir.char<1,3>>>, i1) -> !hlfir.expr<2x!fir.char<1,3>>
! CHECK: fir.call @_QMchararrayctorPtakes_char
! CHECK: hlfir.destroy %[[VAL_27]] : !hlfir.expr<2x!fir.char<1,3>>

subroutine test_dynamic_length()
call takes_char([char_pointer(1), char_pointer(2)])
end subroutine
! CHECK-LABEL: func.func @_QMchararrayctorPtest_dynamic_length() {
! CHECK: %[[VAL_0:.*]] = fir.alloca !fir.box<!fir.ptr<!fir.char<1,?>>> {bindc_name = ".result"}
! CHECK: %[[VAL_1:.*]] = fir.alloca !fir.box<!fir.ptr<!fir.char<1,?>>> {bindc_name = ".result"}
! CHECK: %[[VAL_2:.*]] = fir.alloca !fir.array<10xi64> {bindc_name = ".rt.arrayctor.vector"}
! CHECK: %[[VAL_3:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<2x!fir.char<1,?>>>> {bindc_name = ".tmp.arrayctor"}
! CHECK: %[[VAL_10:.*]] = arith.constant 2 : index
! CHECK: %[[VAL_11:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_12:.*]] = fir.zero_bits !fir.heap<!fir.array<2x!fir.char<1,?>>>
! CHECK: %[[VAL_13:.*]] = fir.shape %[[VAL_10]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_14:.*]] = fir.embox %[[VAL_12]](%[[VAL_13]]) typeparams %[[VAL_11]] : (!fir.heap<!fir.array<2x!fir.char<1,?>>>, !fir.shape<1>, index) -> !fir.box<!fir.heap<!fir.array<2x!fir.char<1,?>>>>
! CHECK: fir.store %[[VAL_14]] to %[[VAL_3]] : !fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.char<1,?>>>>>
! CHECK: %[[VAL_15:.*]] = arith.constant true
! CHECK: %[[VAL_16:.*]] = fir.convert %[[VAL_2]] : (!fir.ref<!fir.array<10xi64>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_20:.*]] = fir.convert %[[VAL_3]] : (!fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.char<1,?>>>>>) -> !fir.ref<!fir.box<none>>
! CHECK: %[[VAL_22:.*]] = fir.call @_FortranAInitArrayConstructorVector(%[[VAL_16]], %[[VAL_20]], %[[VAL_15]], %{{.*}}, %{{.*}}, %{{.*}}) {{.*}}: (!fir.llvm_ptr<i8>, !fir.ref<!fir.box<none>>, i1, i32, !fir.ref<i8>, i32) -> none
! CHECK: fir.call @_QMchararrayctorPchar_pointer(
! CHECK: fir.call @_FortranAPushArrayConstructorValue(%[[VAL_16]], %{{.*}}) {{.*}}: (!fir.llvm_ptr<i8>, !fir.box<none>) -> none
! CHECK: fir.call @_QMchararrayctorPchar_pointer(
! CHECK: fir.call @_FortranAPushArrayConstructorValue(%[[VAL_16]], %{{.*}}) {{.*}}: (!fir.llvm_ptr<i8>, !fir.box<none>) -> none
! CHECK: %[[VAL_45:.*]] = arith.constant true
! CHECK: %[[VAL_46:.*]] = fir.load %[[VAL_3]] : !fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.char<1,?>>>>>
! CHECK: %[[VAL_47:.*]] = hlfir.as_expr %[[VAL_46]] move %[[VAL_45]] : (!fir.box<!fir.heap<!fir.array<2x!fir.char<1,?>>>>, i1) -> !hlfir.expr<2x!fir.char<1,?>>
! CHECK: fir.call @_QMchararrayctorPtakes_char(
! CHECK: hlfir.destroy %[[VAL_47]] : !hlfir.expr<2x!fir.char<1,?>>


! Code below is only relevant for end-to-end test validation purpose.
function char_pointer(i)
integer :: i
character(:), pointer :: char_pointer
if (i.eq.1) then
char_pointer => ctarg1
else
char_pointer => ctarg2
end if
end function
subroutine takes_char(c)
character(*) :: c(:)
print *, "got : ", c
end subroutine
end module

use chararrayctor
print *, "expect: ab cde"
call test_pre_computed_length("ab", "cdefg")
print *, "expect: abcdef"
call test_dynamic_length()
end
88 changes: 88 additions & 0 deletions flang/test/Lower/HLFIR/array-ctor-derived.f90
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
! Test lowering of derived type array constructors to HLFIR.
! RUN: bbc -emit-fir -hlfir --polymorphic-type -o - %s | FileCheck %s

module types
type simple
integer :: i
integer :: j
end type
end module
module derivedarrayctor
use types
contains
subroutine test_simple(s1, s2)
type(simple) :: s1, s2
call takes_simple([s1, s2])
end subroutine
! CHECK-LABEL: func.func @_QMderivedarrayctorPtest_simple(
! CHECK: %[[VAL_2:.*]] = fir.alloca !fir.array<10xi64> {bindc_name = ".rt.arrayctor.vector"}
! CHECK: %[[VAL_3:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>> {bindc_name = ".tmp.arrayctor"}
! CHECK: %[[VAL_4:.*]]:2 = hlfir.declare %{{.*}}Es1"
! CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %{{.*}}Es2"
! CHECK: %[[VAL_6:.*]] = arith.constant 2 : index
! CHECK: %[[VAL_7:.*]] = fir.allocmem !fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>> {bindc_name = ".tmp.arrayctor", uniq_name = ""}
! CHECK: %[[VAL_8:.*]] = fir.shape %[[VAL_6]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_9:.*]]:2 = hlfir.declare %[[VAL_7]](%[[VAL_8]]) {uniq_name = ".tmp.arrayctor"} : (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, !fir.shape<1>) -> (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, !fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>)
! CHECK: %[[VAL_10:.*]] = fir.embox %[[VAL_9]]#1(%[[VAL_8]]) : (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, !fir.shape<1>) -> !fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>>
! CHECK: fir.store %[[VAL_10]] to %[[VAL_3]] : !fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>>>
! CHECK: %[[VAL_11:.*]] = arith.constant false
! CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_2]] : (!fir.ref<!fir.array<10xi64>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_16:.*]] = fir.convert %[[VAL_3]] : (!fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>>>) -> !fir.ref<!fir.box<none>>
! CHECK: %[[VAL_18:.*]] = fir.call @_FortranAInitArrayConstructorVector(%[[VAL_12]], %[[VAL_16]], %[[VAL_11]], %{{.*}}, %{{.*}}, %{{.*}}) {{.*}}: (!fir.llvm_ptr<i8>, !fir.ref<!fir.box<none>>, i1, i32, !fir.ref<i8>, i32) -> none
! CHECK: %[[VAL_19:.*]] = fir.convert %[[VAL_4]]#1 : (!fir.ref<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_20:.*]] = fir.call @_FortranAPushArrayConstructorSimpleScalar(%[[VAL_12]], %[[VAL_19]]) {{.*}}: (!fir.llvm_ptr<i8>, !fir.llvm_ptr<i8>) -> none
! CHECK: %[[VAL_21:.*]] = fir.convert %[[VAL_5]]#1 : (!fir.ref<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_22:.*]] = fir.call @_FortranAPushArrayConstructorSimpleScalar(%[[VAL_12]], %[[VAL_21]]) {{.*}}: (!fir.llvm_ptr<i8>, !fir.llvm_ptr<i8>) -> none
! CHECK: %[[VAL_23:.*]] = arith.constant true
! CHECK: %[[VAL_24:.*]] = hlfir.as_expr %[[VAL_9]]#0 move %[[VAL_23]] : (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, i1) -> !hlfir.expr<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>
! CHECK: fir.call @_QMderivedarrayctorPtakes_simple
! CHECK: hlfir.destroy %[[VAL_24]] : !hlfir.expr<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>

subroutine test_with_polymorphic(s1, s2)
class(simple) :: s1, s2
call takes_simple([s1, s2])
end subroutine
! CHECK-LABEL: func.func @_QMderivedarrayctorPtest_with_polymorphic(
! CHECK: %[[VAL_2:.*]] = fir.alloca !fir.array<10xi64> {bindc_name = ".rt.arrayctor.vector"}
! CHECK: %[[VAL_3:.*]] = fir.alloca !fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>> {bindc_name = ".tmp.arrayctor"}
! CHECK: %[[VAL_4:.*]]:2 = hlfir.declare %{{.*}}Es1"
! CHECK: %[[VAL_5:.*]]:2 = hlfir.declare %{{.*}}Es2"
! CHECK: %[[VAL_6:.*]] = arith.constant 2 : index
! CHECK: %[[VAL_7:.*]] = fir.allocmem !fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>> {bindc_name = ".tmp.arrayctor", uniq_name = ""}
! CHECK: %[[VAL_8:.*]] = fir.shape %[[VAL_6]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_9:.*]]:2 = hlfir.declare %[[VAL_7]](%[[VAL_8]]) {uniq_name = ".tmp.arrayctor"} : (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, !fir.shape<1>) -> (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, !fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>)
! CHECK: %[[VAL_10:.*]] = fir.embox %[[VAL_9]]#1(%[[VAL_8]]) : (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, !fir.shape<1>) -> !fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>>
! CHECK: fir.store %[[VAL_10]] to %[[VAL_3]] : !fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>>>
! CHECK: %[[VAL_11:.*]] = arith.constant false
! CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_2]] : (!fir.ref<!fir.array<10xi64>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_16:.*]] = fir.convert %[[VAL_3]] : (!fir.ref<!fir.box<!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>>>) -> !fir.ref<!fir.box<none>>
! CHECK: %[[VAL_18:.*]] = fir.call @_FortranAInitArrayConstructorVector(%[[VAL_12]], %[[VAL_16]], %[[VAL_11]], %{{.*}}, %{{.*}}, %{{.*}}) {{.*}}: (!fir.llvm_ptr<i8>, !fir.ref<!fir.box<none>>, i1, i32, !fir.ref<i8>, i32) -> none
! CHECK: %[[VAL_19A:.*]] = fir.box_addr %[[VAL_4]]#1 : (!fir.class<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>) -> !fir.ref<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>
! CHECK: %[[VAL_19:.*]] = fir.convert %[[VAL_19A]] : (!fir.ref<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_20:.*]] = fir.call @_FortranAPushArrayConstructorSimpleScalar(%[[VAL_12]], %[[VAL_19]]) {{.*}}: (!fir.llvm_ptr<i8>, !fir.llvm_ptr<i8>) -> none
! CHECK: %[[VAL_21A:.*]] = fir.box_addr %[[VAL_5]]#1 : (!fir.class<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>) -> !fir.ref<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>
! CHECK: %[[VAL_21:.*]] = fir.convert %[[VAL_21A]] : (!fir.ref<!fir.type<_QMtypesTsimple{i:i32,j:i32}>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_22:.*]] = fir.call @_FortranAPushArrayConstructorSimpleScalar(%[[VAL_12]], %[[VAL_21]]) {{.*}}: (!fir.llvm_ptr<i8>, !fir.llvm_ptr<i8>) -> none
! CHECK: %[[VAL_23:.*]] = arith.constant true
! CHECK: %[[VAL_24:.*]] = hlfir.as_expr %[[VAL_9]]#0 move %[[VAL_23]] : (!fir.heap<!fir.array<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>>, i1) -> !hlfir.expr<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>
! CHECK: fir.call @_QMderivedarrayctorPtakes_simple
! CHECK: hlfir.destroy %[[VAL_24]] : !hlfir.expr<2x!fir.type<_QMtypesTsimple{i:i32,j:i32}>>

subroutine takes_simple(s)
type(simple) :: s(:)
print *, "got :", s
end subroutine
end module

use derivedarrayctor
type(simple) :: s1, s2
s1%i = 1
s1%j = 2
s2%i = 3
s2%j = 4

print *, "expect: 1 2 3 4"
call test_simple(s1, s2)
print *, "expect: 1 2 3 4"
call test_with_polymorphic(s1, s2)
end

0 comments on commit bc991d9

Please sign in to comment.