//===--- flang/unittests/Runtime/TemporaryStack.cpp -------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "gtest/gtest.h"
#include "tools.h"
#include "flang/ISO_Fortran_binding_wrapper.h"
#include "flang/Runtime/allocatable.h"
#include "flang/Runtime/cpp-type.h"
#include "flang/Runtime/descriptor.h"
#include "flang/Runtime/temporary-stack.h"
#include "flang/Runtime/type-code.h"
#include <vector>
using namespace Fortran::runtime;
// true if two descriptors are otherwise identical, except for different data
// pointers. The pointed-to elements are bit for bit identical.
static void descriptorAlmostEqual(
const Descriptor &lhs, const Descriptor &rhs) {
const Fortran::ISO::CFI_cdesc_t &lhsRaw = lhs.raw();
const Fortran::ISO::CFI_cdesc_t &rhsRaw = rhs.raw();
ASSERT_EQ(lhs.ElementBytes() == rhs.ElementBytes(), true);
ASSERT_EQ(lhsRaw.version == rhsRaw.version, true);
ASSERT_EQ(lhs.rank() == rhs.rank(), true);
ASSERT_EQ(lhs.type() == rhs.type(), true);
ASSERT_EQ(lhsRaw.attribute == rhsRaw.attribute, true);
ASSERT_EQ(memcmp(lhsRaw.dim, rhsRaw.dim, lhs.rank()) == 0, true);
const std::size_t bytes = lhs.Elements() * lhs.ElementBytes();
ASSERT_EQ(memcmp(lhsRaw.base_addr, rhsRaw.base_addr, bytes) == 0, true);
const DescriptorAddendum *lhsAdd = lhs.Addendum();
const DescriptorAddendum *rhsAdd = rhs.Addendum();
if (lhsAdd) {
ASSERT_NE(rhsAdd, nullptr);
ASSERT_EQ(lhsAdd->SizeInBytes() == rhsAdd->SizeInBytes(), true);
ASSERT_EQ(memcmp(lhsAdd, rhsAdd, lhsAdd->SizeInBytes()) == 0, true);
} else {
ASSERT_EQ(rhsAdd, nullptr);
}
}
TEST(TemporaryStack, ValueStackBasic) {
const TypeCode code{CFI_type_int32_t};
constexpr size_t elementBytes = 4;
constexpr size_t rank = 2;
void *const descriptorPtr = reinterpret_cast<void *>(0xdeadbeef);
const SubscriptValue extent[rank]{42, 24};
StaticDescriptor<rank> testDescriptorStorage[3];
Descriptor &inputDesc{testDescriptorStorage[0].descriptor()};
Descriptor &outputDesc{testDescriptorStorage[1].descriptor()};
Descriptor &outputDesc2{testDescriptorStorage[2].descriptor()};
inputDesc.Establish(code, elementBytes, descriptorPtr, rank, extent);
inputDesc.Allocate();
ASSERT_EQ(inputDesc.IsAllocated(), true);
uint32_t *inputData = static_cast<uint32_t *>(inputDesc.raw().base_addr);
for (std::size_t i = 0; i < inputDesc.Elements(); ++i) {
inputData[i] = i;
}
void *storage = RTNAME(CreateValueStack)(__FILE__, __LINE__);
ASSERT_NE(storage, nullptr);
RTNAME(PushValue)(storage, inputDesc);
RTNAME(ValueAt)(storage, 0, outputDesc);
descriptorAlmostEqual(inputDesc, outputDesc);
RTNAME(PopValue)(storage, outputDesc2);
descriptorAlmostEqual(inputDesc, outputDesc2);
RTNAME(DestroyValueStack)(storage);
}
static unsigned max(unsigned x, unsigned y) {
if (x > y) {
return x;
}
return y;
}
TEST(TemporaryStack, ValueStackMultiSize) {
constexpr unsigned numToTest = 42;
const TypeCode code{CFI_type_int32_t};
constexpr size_t elementBytes = 4;
SubscriptValue extent[CFI_MAX_RANK];
std::vector<OwningPtr<Descriptor>> inputDescriptors;
inputDescriptors.reserve(numToTest);
void *storage = RTNAME(CreateValueStack)(__FILE__, __LINE__);
ASSERT_NE(storage, nullptr);
// create descriptors with and without adendums
auto getAdendum = [](unsigned i) { return i % 2; };
// create descriptors with varying ranks
auto getRank = [](unsigned i) { return max(i % 8, 1); };
// push descriptors of varying sizes and contents
for (unsigned i = 0; i < numToTest; ++i) {
const bool adendum = getAdendum(i);
const size_t rank = getRank(i);
for (unsigned dim = 0; dim < rank; ++dim) {
extent[dim] = ((i + dim) % 8) + 1;
}
const OwningPtr<Descriptor> &desc =
inputDescriptors.emplace_back(Descriptor::Create(code, elementBytes,
nullptr, rank, extent, CFI_attribute_allocatable, adendum));
// Descriptor::Establish doesn't initialise the extents if baseaddr is null
for (unsigned dim = 0; dim < rank; ++dim) {
Fortran::ISO::CFI_dim_t &boxDims = desc->raw().dim[dim];
boxDims.lower_bound = 1;
boxDims.extent = extent[dim];
boxDims.sm = elementBytes;
}
desc->Allocate();
// fill the array with some data to test
for (uint32_t i = 0; i < desc->Elements(); ++i) {
uint32_t *data = static_cast<uint32_t *>(desc->raw().base_addr);
ASSERT_NE(data, nullptr);
data[i] = i;
}
RTNAME(PushValue)(storage, *desc.get());
}
const TypeCode boolCode{CFI_type_Bool};
// peek and test each descriptor
for (unsigned i = 0; i < numToTest; ++i) {
const OwningPtr<Descriptor> &input = inputDescriptors[i];
const bool adendum = getAdendum(i);
const size_t rank = getRank(i);
// buffer to return the descriptor into
OwningPtr<Descriptor> out = Descriptor::Create(
boolCode, 1, nullptr, rank, extent, CFI_attribute_other, adendum);
(void)input;
RTNAME(ValueAt)(storage, i, *out.get());
descriptorAlmostEqual(*input, *out);
}
// pop and test each descriptor
for (unsigned i = numToTest; i > 0; --i) {
const OwningPtr<Descriptor> &input = inputDescriptors[i - 1];
const bool adendum = getAdendum(i - 1);
const size_t rank = getRank(i - 1);
// buffer to return the descriptor into
OwningPtr<Descriptor> out = Descriptor::Create(
boolCode, 1, nullptr, rank, extent, CFI_attribute_other, adendum);
RTNAME(PopValue)(storage, *out.get());
descriptorAlmostEqual(*input, *out);
}
RTNAME(DestroyValueStack)(storage);
}
TEST(TemporaryStack, DescriptorStackBasic) {
const TypeCode code{CFI_type_Bool};
constexpr size_t elementBytes = 4;
constexpr size_t rank = 2;
void *const descriptorPtr = reinterpret_cast<void *>(0xdeadbeef);
const SubscriptValue extent[rank]{42, 24};
StaticDescriptor<rank> testDescriptorStorage[3];
Descriptor &inputDesc{testDescriptorStorage[0].descriptor()};
Descriptor &outputDesc{testDescriptorStorage[1].descriptor()};
Descriptor &outputDesc2{testDescriptorStorage[2].descriptor()};
inputDesc.Establish(code, elementBytes, descriptorPtr, rank, extent);
void *storage = RTNAME(CreateDescriptorStack)(__FILE__, __LINE__);
ASSERT_NE(storage, nullptr);
RTNAME(PushDescriptor)(storage, inputDesc);
RTNAME(DescriptorAt)(storage, 0, outputDesc);
ASSERT_EQ(
memcmp(&inputDesc, &outputDesc, testDescriptorStorage[0].byteSize), 0);
RTNAME(PopDescriptor)(storage, outputDesc2);
ASSERT_EQ(
memcmp(&inputDesc, &outputDesc2, testDescriptorStorage[0].byteSize), 0);
RTNAME(DestroyDescriptorStack)(storage);
}
TEST(TemporaryStack, DescriptorStackMultiSize) {
constexpr unsigned numToTest = 42;
const TypeCode code{CFI_type_Bool};
constexpr size_t elementBytes = 4;
const uintptr_t ptrBase = 0xdeadbeef;
SubscriptValue extent[CFI_MAX_RANK];
std::vector<OwningPtr<Descriptor>> inputDescriptors;
inputDescriptors.reserve(numToTest);
void *storage = RTNAME(CreateDescriptorStack)(__FILE__, __LINE__);
ASSERT_NE(storage, nullptr);
// create descriptors with and without adendums
auto getAdendum = [](unsigned i) { return i % 2; };
// create descriptors with varying ranks
auto getRank = [](unsigned i) { return max(i % CFI_MAX_RANK, 1); };
// push descriptors of varying sizes and contents
for (unsigned i = 0; i < numToTest; ++i) {
const bool adendum = getAdendum(i);
const size_t rank = getRank(i);
for (unsigned dim = 0; dim < rank; ++dim) {
extent[dim] = max(i - dim, 1);
}
// varying pointers
void *const ptr = reinterpret_cast<void *>(ptrBase + i * elementBytes);
const OwningPtr<Descriptor> &desc =
inputDescriptors.emplace_back(Descriptor::Create(code, elementBytes,
ptr, rank, extent, CFI_attribute_other, adendum));
RTNAME(PushDescriptor)(storage, *desc.get());
}
const TypeCode intCode{CFI_type_int8_t};
// peek and test each descriptor
for (unsigned i = 0; i < numToTest; ++i) {
const OwningPtr<Descriptor> &input = inputDescriptors[i];
const bool adendum = getAdendum(i);
const size_t rank = getRank(i);
// buffer to return the descriptor into
OwningPtr<Descriptor> out = Descriptor::Create(
intCode, 1, nullptr, rank, extent, CFI_attribute_other, adendum);
RTNAME(DescriptorAt)(storage, i, *out.get());
ASSERT_EQ(memcmp(input.get(), out.get(), input->SizeInBytes()), 0);
}
// pop and test each descriptor
for (unsigned i = numToTest; i > 0; --i) {
const OwningPtr<Descriptor> &input = inputDescriptors[i - 1];
const bool adendum = getAdendum(i - 1);
const size_t rank = getRank(i - 1);
// buffer to return the descriptor into
OwningPtr<Descriptor> out = Descriptor::Create(
intCode, 1, nullptr, rank, extent, CFI_attribute_other, adendum);
RTNAME(PopDescriptor)(storage, *out.get());
ASSERT_EQ(memcmp(input.get(), out.get(), input->SizeInBytes()), 0);
}
RTNAME(DestroyDescriptorStack)(storage);
}