[flang] Update UBOUND runtime API and lowering (#95085)

LBOUND and SHAPE runtime were added with an API that avoids making a
dynamic allocation for the small result storage. Update the UBOUND API
that was already there and used in lowering outside of the assumed-rank
case.
Add tests for the assumed-rank case.
This commit is contained in:
jeanPerier 2024-06-13 10:44:21 +02:00 committed by GitHub
parent 2b66d283bc
commit 65f746e76c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 193 additions and 117 deletions

View File

@ -35,7 +35,7 @@ std::int64_t RTDECL(Size)(
std::int64_t RTDECL(SizeDim)(const Descriptor &array, int dim,
const char *sourceFile = nullptr, int line = 0);
void RTDECL(Ubound)(Descriptor &result, const Descriptor &array, int kind,
void RTDECL(Ubound)(void *result, const Descriptor &array, int kind,
const char *sourceFile = nullptr, int line = 0);
} // extern "C"

View File

@ -6071,33 +6071,80 @@ mlir::Value IntrinsicLibrary::genSetExponent(mlir::Type resultType,
fir::getBase(args[1])));
}
/// Create a fir.box to be passed to the LBOUND/UBOUND runtime.
/// This ensure that local lower bounds of assumed shape are propagated and that
/// a fir.box with equivalent LBOUNDs.
static mlir::Value
createBoxForRuntimeBoundInquiry(mlir::Location loc, fir::FirOpBuilder &builder,
const fir::ExtendedValue &array) {
// Assumed-rank descriptor must always carry accurate lower bound information
// in lowering since they cannot be tracked on the side in a vector at compile
// time.
if (array.hasAssumedRank())
return builder.createBox(loc, array);
return array.match(
[&](const fir::BoxValue &boxValue) -> mlir::Value {
// This entity is mapped to a fir.box that may not contain the local
// lower bound information if it is a dummy. Rebox it with the local
// shape information.
mlir::Value localShape = builder.createShape(loc, array);
mlir::Value oldBox = boxValue.getAddr();
return builder.create<fir::ReboxOp>(loc, oldBox.getType(), oldBox,
localShape,
/*slice=*/mlir::Value{});
},
[&](const auto &) -> mlir::Value {
// This is a pointer/allocatable, or an entity not yet tracked with a
// fir.box. For pointer/allocatable, createBox will forward the
// descriptor that contains the correct lower bound information. For
// other entities, a new fir.box will be made with the local lower
// bounds.
return builder.createBox(loc, array);
});
}
/// Generate runtime call to inquire about all the bounds/extents of an
/// assumed-rank array.
/// array (or an assumed-rank).
template <typename Func>
static fir::ExtendedValue genAssumedRankBoundInquiry(
fir::FirOpBuilder &builder, mlir::Location loc, mlir::Type resultType,
llvm::ArrayRef<fir::ExtendedValue> args, int kindPos, Func genRtCall) {
static fir::ExtendedValue
genBoundInquiry(fir::FirOpBuilder &builder, mlir::Location loc,
mlir::Type resultType, llvm::ArrayRef<fir::ExtendedValue> args,
int kindPos, Func genRtCall, bool needAccurateLowerBound) {
const fir::ExtendedValue &array = args[0];
// Allocate an array with the maximum rank, that is big enough to hold the
// result but still "small" (15 elements). Static size alloca make stack
// analysis/manipulation easier.
const bool hasAssumedRank = array.hasAssumedRank();
mlir::Type resultElementType = fir::unwrapSequenceType(resultType);
mlir::Type allocSeqType =
fir::SequenceType::get({Fortran::common::maxRank}, resultElementType);
// For assumed-rank arrays, allocate an array with the maximum rank, that is
// big enough to hold the result but still "small" (15 elements). Static size
// alloca make stack analysis/manipulation easier.
int rank = hasAssumedRank ? Fortran::common::maxRank : array.rank();
mlir::Type allocSeqType = fir::SequenceType::get(rank, resultElementType);
mlir::Value resultStorage = builder.createTemporary(loc, allocSeqType);
mlir::Value arrayBox = builder.createBox(loc, array);
mlir::Value arrayBox =
needAccurateLowerBound
? createBoxForRuntimeBoundInquiry(loc, builder, array)
: builder.createBox(loc, array);
mlir::Value kind = isStaticallyAbsent(args, kindPos)
? builder.createIntegerConstant(
loc, builder.getI32Type(),
builder.getKindMap().defaultIntegerKind())
: fir::getBase(args[kindPos]);
genRtCall(builder, loc, resultStorage, arrayBox, kind);
mlir::Type baseType =
fir::ReferenceType::get(builder.getVarLenSeqTy(resultElementType));
mlir::Value resultBase = builder.createConvert(loc, baseType, resultStorage);
mlir::Value rank =
builder.create<fir::BoxRankOp>(loc, builder.getIndexType(), arrayBox);
return fir::ArrayBoxValue{resultBase, {rank}};
if (hasAssumedRank) {
// Cast to fir.ref<array<?xik>> since the result extent is not a compile
// time constant.
mlir::Type baseType =
fir::ReferenceType::get(builder.getVarLenSeqTy(resultElementType));
mlir::Value resultBase =
builder.createConvert(loc, baseType, resultStorage);
mlir::Value rankValue =
builder.create<fir::BoxRankOp>(loc, builder.getIndexType(), arrayBox);
return fir::ArrayBoxValue{resultBase, {rankValue}};
}
// Result extent is a compile time constant in the other cases.
mlir::Value rankValue =
builder.createIntegerConstant(loc, builder.getIndexType(), rank);
return fir::ArrayBoxValue{resultStorage, {rankValue}};
}
// SHAPE
@ -6107,8 +6154,9 @@ IntrinsicLibrary::genShape(mlir::Type resultType,
assert(args.size() >= 1);
const fir::ExtendedValue &array = args[0];
if (array.hasAssumedRank())
return genAssumedRankBoundInquiry(builder, loc, resultType, args,
/*kindPos=*/1, fir::runtime::genShape);
return genBoundInquiry(builder, loc, resultType, args,
/*kindPos=*/1, fir::runtime::genShape,
/*needAccurateLowerBound=*/false);
int rank = array.rank();
mlir::Type indexType = builder.getIndexType();
mlir::Type extentType = fir::unwrapSequenceType(resultType);
@ -6344,33 +6392,6 @@ static mlir::Value computeLBOUND(fir::FirOpBuilder &builder, mlir::Location loc,
return builder.create<mlir::arith::SelectOp>(loc, dimIsEmpty, one, lb);
}
/// Create a fir.box to be passed to the LBOUND/UBOUND runtime.
/// This ensure that local lower bounds of assumed shape are propagated and that
/// a fir.box with equivalent LBOUNDs.
static mlir::Value
createBoxForRuntimeBoundInquiry(mlir::Location loc, fir::FirOpBuilder &builder,
const fir::ExtendedValue &array) {
return array.match(
[&](const fir::BoxValue &boxValue) -> mlir::Value {
// This entity is mapped to a fir.box that may not contain the local
// lower bound information if it is a dummy. Rebox it with the local
// shape information.
mlir::Value localShape = builder.createShape(loc, array);
mlir::Value oldBox = boxValue.getAddr();
return builder.create<fir::ReboxOp>(loc, oldBox.getType(), oldBox,
localShape,
/*slice=*/mlir::Value{});
},
[&](const auto &) -> mlir::Value {
// This is a pointer/allocatable, or an entity not yet tracked with a
// fir.box. For pointer/allocatable, createBox will forward the
// descriptor that contains the correct lower bound information. For
// other entities, a new fir.box will be made with the local lower
// bounds.
return builder.createBox(loc, array);
});
}
// LBOUND
fir::ExtendedValue
IntrinsicLibrary::genLbound(mlir::Type resultType,
@ -6380,9 +6401,12 @@ IntrinsicLibrary::genLbound(mlir::Type resultType,
// Semantics builds signatures for LBOUND calls as either
// LBOUND(array, dim, [kind]) or LBOUND(array, [kind]).
const bool dimIsAbsent = args.size() == 2 || isStaticallyAbsent(args, 1);
if (array.hasAssumedRank() && dimIsAbsent)
return genAssumedRankBoundInquiry(builder, loc, resultType, args,
/*kindPos=*/1, fir::runtime::genLbound);
if (array.hasAssumedRank() && dimIsAbsent) {
int kindPos = args.size() == 2 ? 1 : 2;
return genBoundInquiry(builder, loc, resultType, args, kindPos,
fir::runtime::genLbound,
/*needAccurateLowerBound=*/true);
}
mlir::Type indexType = builder.getIndexType();
@ -6434,7 +6458,8 @@ fir::ExtendedValue
IntrinsicLibrary::genUbound(mlir::Type resultType,
llvm::ArrayRef<fir::ExtendedValue> args) {
assert(args.size() == 3 || args.size() == 2);
if (args.size() == 3) {
const bool dimIsAbsent = args.size() == 2 || isStaticallyAbsent(args, 1);
if (!dimIsAbsent) {
// Handle calls to UBOUND with the DIM argument, which return a scalar
mlir::Value extent = fir::getBase(genSize(resultType, args));
mlir::Value lbound = fir::getBase(genLbound(resultType, args));
@ -6442,28 +6467,12 @@ IntrinsicLibrary::genUbound(mlir::Type resultType,
mlir::Value one = builder.createIntegerConstant(loc, resultType, 1);
mlir::Value ubound = builder.create<mlir::arith::SubIOp>(loc, lbound, one);
return builder.create<mlir::arith::AddIOp>(loc, ubound, extent);
} else {
// Handle calls to UBOUND without the DIM argument, which return an array
mlir::Value kind = isStaticallyAbsent(args[1])
? builder.createIntegerConstant(
loc, builder.getIndexType(),
builder.getKindMap().defaultIntegerKind())
: fir::getBase(args[1]);
// Create mutable fir.box to be passed to the runtime for the result.
mlir::Type type = builder.getVarLenSeqTy(resultType, /*rank=*/1);
fir::MutableBoxValue resultMutableBox =
fir::factory::createTempMutableBox(builder, loc, type);
mlir::Value resultIrBox =
fir::factory::getMutableIRBox(builder, loc, resultMutableBox);
fir::ExtendedValue box =
createBoxForRuntimeBoundInquiry(loc, builder, args[0]);
fir::runtime::genUbound(builder, loc, resultIrBox, fir::getBase(box), kind);
return readAndAddCleanUp(resultMutableBox, resultType, "UBOUND");
}
return mlir::Value();
// Handle calls to UBOUND without the DIM argument, which return an array
int kindPos = args.size() == 2 ? 1 : 2;
return genBoundInquiry(builder, loc, resultType, args, kindPos,
fir::runtime::genUbound,
/*needAccurateLowerBound=*/true);
}
// SPACING

View File

@ -39,28 +39,14 @@ std::int64_t RTDEF(LboundDim)(
return static_cast<std::int64_t>(dimension.LowerBound());
}
void RTDEF(Ubound)(Descriptor &result, const Descriptor &array, int kind,
void RTDEF(Ubound)(void *result, const Descriptor &array, int kind,
const char *sourceFile, int line) {
SubscriptValue extent[1]{array.rank()};
result.Establish(TypeCategory::Integer, kind, nullptr, 1, extent,
CFI_attribute_allocatable);
// The array returned by UBOUND has a lower bound of 1 and an extent equal to
// the rank of its input array.
result.GetDimension(0).SetBounds(1, array.rank());
Terminator terminator{sourceFile, line};
if (int stat{result.Allocate()}) {
terminator.Crash(
"UBOUND: could not allocate memory for result; STAT=%d", stat);
}
auto storeIntegerAt = [&](std::size_t atIndex, std::int64_t value) {
Fortran::runtime::ApplyIntegerKind<StoreIntegerAt, void>(
kind, terminator, result, atIndex, value);
};
INTERNAL_CHECK(result.rank() == 1);
INTERNAL_CHECK(array.rank() <= common::maxRank);
for (SubscriptValue i{0}; i < array.rank(); ++i) {
const Dimension &dimension{array.GetDimension(i)};
storeIntegerAt(i, dimension.UpperBound());
Fortran::runtime::ApplyIntegerKind<RawStoreIntegerAt, void>(
kind, terminator, result, i, dimension.UpperBound());
}
}

View File

@ -109,3 +109,86 @@ end subroutine
! CHECK: %[[VAL_13:.*]] = fir.box_rank %[[VAL_4]] : (!fir.box<!fir.ptr<!fir.array<*:f32>>>) -> index
! CHECK: %[[VAL_14:.*]] = fir.shape %[[VAL_13]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_15:.*]]:2 = hlfir.declare %[[VAL_12]](%[[VAL_14]]) {uniq_name = ".tmp.intrinsic_result"} : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.ref<!fir.array<?xi32>>)
subroutine test_ubound(x)
real :: x(..)
call takes_integer_array(ubound(x))
end subroutine
! CHECK-LABEL: func.func @_QPtest_ubound(
! CHECK: %[[VAL_1:.*]] = fir.alloca !fir.array<15xi32>
! CHECK: %[[VAL_4:.*]] = arith.constant 4 : i32
! CHECK: %[[VAL_7:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<!fir.array<15xi32>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_8:.*]] = fir.convert %[[VAL_3:.*]] : (!fir.box<!fir.array<*:f32>>) -> !fir.box<none>
! CHECK: %[[VAL_10:.*]] = fir.call @_FortranAUbound(%[[VAL_7]], %[[VAL_8]], %[[VAL_4]], %{{.*}}, %{{.*}})
! CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<!fir.array<15xi32>>) -> !fir.ref<!fir.array<?xi32>>
! CHECK: %[[VAL_12:.*]] = fir.box_rank %[[VAL_3]] : (!fir.box<!fir.array<*:f32>>) -> index
! CHECK: %[[VAL_13:.*]] = fir.shape %[[VAL_12]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_14:.*]]:2 = hlfir.declare %[[VAL_11]](%[[VAL_13]]) {uniq_name = ".tmp.intrinsic_result"} : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.ref<!fir.array<?xi32>>)
! CHECK: %[[VAL_15:.*]] = arith.constant false
! CHECK: %[[VAL_16:.*]] = hlfir.as_expr %[[VAL_14]]#0 move %[[VAL_15]] : (!fir.box<!fir.array<?xi32>>, i1) -> !hlfir.expr<?xi32>
! CHECK: %[[VAL_17:.*]]:3 = hlfir.associate %[[VAL_16]](%[[VAL_13]]) {adapt.valuebyref} : (!hlfir.expr<?xi32>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.ref<!fir.array<?xi32>>, i1)
! CHECK: fir.call @_QPtakes_integer_array(%[[VAL_17]]#1) fastmath<contract> : (!fir.ref<!fir.array<?xi32>>) -> ()
! CHECK: hlfir.end_associate %[[VAL_17]]#1, %[[VAL_17]]#2 : !fir.ref<!fir.array<?xi32>>, i1
! CHECK: hlfir.destroy %[[VAL_16]] : !hlfir.expr<?xi32>
! CHECK: return
! CHECK: }
subroutine test_ubound_kind(x)
real :: x(..)
call takes_integer8_array(ubound(x, kind=8))
end subroutine
! CHECK-LABEL: func.func @_QPtest_ubound_kind(
! CHECK: %[[VAL_1:.*]] = fir.alloca !fir.array<15xi64>
! CHECK: %[[VAL_4:.*]] = arith.constant 8 : i32
! CHECK: %[[VAL_7:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<!fir.array<15xi64>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_8:.*]] = fir.convert %[[VAL_3:.*]] : (!fir.box<!fir.array<*:f32>>) -> !fir.box<none>
! CHECK: %[[VAL_10:.*]] = fir.call @_FortranAUbound(%[[VAL_7]], %[[VAL_8]], %[[VAL_4]], %{{.*}}, %{{.*}})
! CHECK: %[[VAL_11:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<!fir.array<15xi64>>) -> !fir.ref<!fir.array<?xi64>>
! CHECK: %[[VAL_12:.*]] = fir.box_rank %[[VAL_3]] : (!fir.box<!fir.array<*:f32>>) -> index
! CHECK: %[[VAL_13:.*]] = fir.shape %[[VAL_12]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_14:.*]]:2 = hlfir.declare %[[VAL_11]](%[[VAL_13]]) {uniq_name = ".tmp.intrinsic_result"} : (!fir.ref<!fir.array<?xi64>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi64>>, !fir.ref<!fir.array<?xi64>>)
subroutine test_ubound_2(x)
real, pointer :: x(..)
call takes_integer_array(ubound(x))
end subroutine
! CHECK-LABEL: func.func @_QPtest_ubound_2(
! CHECK: %[[VAL_1:.*]] = fir.alloca !fir.array<15xi32>
! CHECK: %[[VAL_4:.*]] = fir.load %[[VAL_3:.*]] : !fir.ref<!fir.box<!fir.ptr<!fir.array<*:f32>>>>
! CHECK: %[[VAL_5:.*]] = arith.constant 4 : i32
! CHECK: %[[VAL_8:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<!fir.array<15xi32>>) -> !fir.llvm_ptr<i8>
! CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_4]] : (!fir.box<!fir.ptr<!fir.array<*:f32>>>) -> !fir.box<none>
! CHECK: %[[VAL_11:.*]] = fir.call @_FortranAUbound(%[[VAL_8]], %[[VAL_9]], %[[VAL_5]], %{{.*}}, %{{.*}})
! CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_1]] : (!fir.ref<!fir.array<15xi32>>) -> !fir.ref<!fir.array<?xi32>>
! CHECK: %[[VAL_13:.*]] = fir.box_rank %[[VAL_4]] : (!fir.box<!fir.ptr<!fir.array<*:f32>>>) -> index
! CHECK: %[[VAL_14:.*]] = fir.shape %[[VAL_13]] : (index) -> !fir.shape<1>
! CHECK: %[[VAL_15:.*]]:2 = hlfir.declare %[[VAL_12]](%[[VAL_14]]) {uniq_name = ".tmp.intrinsic_result"} : (!fir.ref<!fir.array<?xi32>>, !fir.shape<1>) -> (!fir.box<!fir.array<?xi32>>, !fir.ref<!fir.array<?xi32>>)
subroutine test_lbound_dim(x)
real :: x(..)
call takes_integer(lbound(x, dim=2))
end subroutine
! CHECK-LABEL: func.func @_QPtest_lbound_dim(
! CHECK: %[[VAL_3:.*]] = arith.constant 2 : i32
! CHECK: %[[VAL_6:.*]] = fir.convert %[[VAL_2:.*]]#0 : (!fir.box<!fir.array<*:f32>>) -> !fir.box<none>
! CHECK: %[[VAL_8:.*]] = fir.call @_FortranALboundDim(%[[VAL_6]], %[[VAL_3]],
! CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_8]] : (i64) -> i32
! CHECK: %[[VAL_10:.*]]:3 = hlfir.associate %[[VAL_9]]
subroutine test_ubound_dim(x)
real :: x(..)
call takes_integer(ubound(x, dim=2))
end subroutine
! CHECK-LABEL: func.func @_QPtest_ubound_dim(
! CHECK: %[[VAL_3:.*]] = arith.constant 2 : i32
! CHECK: %[[VAL_6:.*]] = fir.convert %[[VAL_2:.*]]#0 : (!fir.box<!fir.array<*:f32>>) -> !fir.box<none>
! CHECK: %[[VAL_8:.*]] = fir.call @_FortranASizeDim(%[[VAL_6]], %[[VAL_3]],
! CHECK: %[[VAL_9:.*]] = fir.convert %[[VAL_8]] : (i64) -> i32
! CHECK: %[[VAL_12:.*]] = fir.convert %[[VAL_2]]#0 : (!fir.box<!fir.array<*:f32>>) -> !fir.box<none>
! CHECK: %[[VAL_14:.*]] = fir.call @_FortranALboundDim(%[[VAL_12]], %[[VAL_3]],
! CHECK: %[[VAL_15:.*]] = fir.convert %[[VAL_14]] : (i64) -> i32
! CHECK: %[[VAL_16:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_17:.*]] = arith.subi %[[VAL_15]], %[[VAL_16]] : i32
! CHECK: %[[VAL_18:.*]] = arith.addi %[[VAL_17]], %[[VAL_9]] : i32
! CHECK: %[[VAL_19:.*]]:3 = hlfir.associate %[[VAL_18]]

View File

@ -20,4 +20,4 @@ end
! CHECK-SAME: %[[ARG0:.*]]: !fir.box<!fir.array<?x?xf32>>
! CHECK: %[[BOX:.*]] = fir.rebox %[[ARG0]](%{{.*}}) : (!fir.box<!fir.array<?x?xf32>>, !fir.shift<2>) -> !fir.box<!fir.array<?x?xf32>>
! CHECK: %[[BOX_NONE:.*]] = fir.convert %[[BOX]] : (!fir.box<!fir.array<?x?xf32>>) -> !fir.box<none>
! CHECK: %{{.*}} = fir.call @_FortranAUbound(%{{.*}}, %[[BOX_NONE]], %{{.*}}, %{{.*}}, %{{.*}}) {{.*}}: (!fir.ref<!fir.box<none>>, !fir.box<none>, i32, !fir.ref<i8>, i32) -> none
! CHECK: %{{.*}} = fir.call @_FortranAUbound(%{{.*}}, %[[BOX_NONE]], %{{.*}}, %{{.*}}, %{{.*}}) {{.*}}: (!fir.llvm_ptr<i8>, !fir.box<none>, i32, !fir.ref<i8>, i32) -> none

View File

@ -69,35 +69,33 @@ TEST(Inquiry, Ubound) {
std::vector<int>{2, 3}, std::vector<std::int32_t>{1, 2, 3, 4, 5, 6})};
array->GetDimension(0).SetLowerBound(1000);
array->GetDimension(1).SetLowerBound(1);
StaticDescriptor<2, true> statDesc;
int intValue{1};
SubscriptValue extent[]{2};
Descriptor &result{statDesc.descriptor()};
result.Establish(TypeCategory::Integer, /*KIND=*/4,
static_cast<void *>(&intValue), 1, extent, CFI_attribute_pointer);
RTNAME(Ubound)(result, *array, /*KIND=*/4, __FILE__, __LINE__);
EXPECT_EQ(result.rank(), 1);
EXPECT_EQ(result.type().raw(), (TypeCode{TypeCategory::Integer, 4}.raw()));
// The lower bound of UBOUND's result array is always 1
EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
EXPECT_EQ(result.GetDimension(0).Extent(), 2);
EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(0), 1001);
EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int32_t>(1), 3);
result.Destroy();
// UBOUND(ARRAY, KIND=1)
auto int8Result{
MakeArray<TypeCategory::Integer, 1>(std::vector<int>{array->rank()},
std::vector<std::int8_t>(array->rank(), 0))};
RTNAME(Ubound)
(int8Result->raw().base_addr, *array, /*KIND=*/1, __FILE__, __LINE__);
EXPECT_EQ(*int8Result->ZeroBasedIndexedElement<std::int8_t>(0), -23);
EXPECT_EQ(*int8Result->ZeroBasedIndexedElement<std::int8_t>(1), 3);
result = statDesc.descriptor();
result.Establish(TypeCategory::Integer, /*KIND=*/1,
static_cast<void *>(&intValue), 1, extent, CFI_attribute_pointer);
RTNAME(Ubound)(result, *array, /*KIND=*/1, __FILE__, __LINE__);
EXPECT_EQ(result.rank(), 1);
EXPECT_EQ(result.type().raw(), (TypeCode{TypeCategory::Integer, 1}.raw()));
// The lower bound of UBOUND's result array is always 1
EXPECT_EQ(result.GetDimension(0).LowerBound(), 1);
EXPECT_EQ(result.GetDimension(0).Extent(), 2);
EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int8_t>(0), -23);
EXPECT_EQ(*result.ZeroBasedIndexedElement<std::int8_t>(1), 3);
result.Destroy();
// UBOUND(ARRAY, KIND=4)
auto int32Result{
MakeArray<TypeCategory::Integer, 4>(std::vector<int>{array->rank()},
std::vector<std::int32_t>(array->rank(), 0))};
RTNAME(Ubound)
(int32Result->raw().base_addr, *array, /*KIND=*/4, __FILE__, __LINE__);
EXPECT_EQ(*int32Result->ZeroBasedIndexedElement<std::int32_t>(0), 1001);
EXPECT_EQ(*int32Result->ZeroBasedIndexedElement<std::int32_t>(1), 3);
// UBOUND(ARRAY, KIND=8)
auto int64Result{
MakeArray<TypeCategory::Integer, 8>(std::vector<int>{array->rank()},
std::vector<std::int64_t>(array->rank(), 0))};
RTNAME(Ubound)
(int64Result->raw().base_addr, *array, /*KIND=*/8, __FILE__, __LINE__);
EXPECT_EQ(*int64Result->ZeroBasedIndexedElement<std::int64_t>(0), 1001);
EXPECT_EQ(*int64Result->ZeroBasedIndexedElement<std::int64_t>(1), 3);
}
TEST(Inquiry, Size) {