//===-- runtime/array-constructor.cpp -------------------------------------===// // // 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 "flang/Runtime/array-constructor.h" #include "derived.h" #include "terminator.h" #include "tools.h" #include "type-info.h" #include "flang/Runtime/allocatable.h" #include "flang/Runtime/assign.h" #include "flang/Runtime/descriptor.h" namespace Fortran::runtime { // Initial allocation size for an array constructor temporary whose extent // cannot be pre-computed. This could be fined tuned if needed based on actual // program performance. // REAL(4), INTEGER(4), COMPLEX(2), ... -> 32 elements. // REAL(8), INTEGER(8), COMPLEX(4), ... -> 16 elements. // REAL(16), INTEGER(16), COMPLEX(8), ... -> 8 elements. // Bigger types -> 4 elements. static RT_API_ATTRS SubscriptValue initialAllocationSize( SubscriptValue initialNumberOfElements, SubscriptValue elementBytes) { // Try to guess an optimal initial allocation size in number of elements to // avoid doing too many reallocation. static constexpr SubscriptValue minNumberOfBytes{128}; static constexpr SubscriptValue minNumberOfElements{4}; SubscriptValue numberOfElements{initialNumberOfElements > minNumberOfElements ? initialNumberOfElements : minNumberOfElements}; SubscriptValue elementsForMinBytes{minNumberOfBytes / elementBytes}; return std::max(numberOfElements, elementsForMinBytes); } static RT_API_ATTRS void AllocateOrReallocateVectorIfNeeded( ArrayConstructorVector &vector, Terminator &terminator, SubscriptValue previousToElements, SubscriptValue fromElements) { Descriptor &to{vector.to}; if (to.IsAllocatable() && !to.IsAllocated()) { // The descriptor bounds may already be set here if the array constructor // extent could be pre-computed, but information about length parameters // was missing and required evaluating the first array constructor value. if (previousToElements == 0) { SubscriptValue allocationSize{ initialAllocationSize(fromElements, to.ElementBytes())}; to.GetDimension(0).SetBounds(1, allocationSize); RTNAME(AllocatableAllocate) (to, /*hasStat=*/false, /*errMsg=*/nullptr, vector.sourceFile, vector.sourceLine); to.GetDimension(0).SetBounds(1, fromElements); vector.actualAllocationSize = allocationSize; } else { // Do not over-allocate if the final extent was known before pushing the // first value: there should be no reallocation. RUNTIME_CHECK(terminator, previousToElements >= fromElements); RTNAME(AllocatableAllocate) (to, /*hasStat=*/false, /*errMsg=*/nullptr, vector.sourceFile, vector.sourceLine); vector.actualAllocationSize = previousToElements; } } else { SubscriptValue newToElements{vector.nextValuePosition + fromElements}; if (to.IsAllocatable() && vector.actualAllocationSize < newToElements) { // Reallocate. Ensure the current storage is at least doubled to avoid // doing too many reallocations. SubscriptValue requestedAllocationSize{ std::max(newToElements, vector.actualAllocationSize * 2)}; std::size_t newByteSize{requestedAllocationSize * to.ElementBytes()}; // realloc is undefined with zero new size and ElementBytes() may be null // if the character length is null, or if "from" is a zero sized array. if (newByteSize > 0) { void *p{ReallocateMemoryOrCrash( terminator, to.raw().base_addr, newByteSize)}; to.set_base_addr(p); } vector.actualAllocationSize = requestedAllocationSize; to.GetDimension(0).SetBounds(1, newToElements); } else if (previousToElements < newToElements) { // Storage is big enough, but descriptor extent must be increased because // the final extent was not known before pushing array constructor values. to.GetDimension(0).SetBounds(1, newToElements); } } } extern "C" { RT_EXT_API_GROUP_BEGIN void RTDEF(InitArrayConstructorVector)(ArrayConstructorVector &vector, Descriptor &to, bool useValueLengthParameters, int vectorClassSize, const char *sourceFile, int sourceLine) { Terminator terminator{vector.sourceFile, vector.sourceLine}; RUNTIME_CHECK(terminator, to.rank() == 1 && sizeof(ArrayConstructorVector) <= static_cast(vectorClassSize)); SubscriptValue actualAllocationSize{ to.IsAllocated() ? static_cast(to.Elements()) : 0}; (void)new (&vector) ArrayConstructorVector{to, /*nextValuePosition=*/0, actualAllocationSize, sourceFile, sourceLine, useValueLengthParameters}; } void RTDEF(PushArrayConstructorValue)( ArrayConstructorVector &vector, const Descriptor &from) { Terminator terminator{vector.sourceFile, vector.sourceLine}; Descriptor &to{vector.to}; SubscriptValue fromElements{static_cast(from.Elements())}; SubscriptValue previousToElements{static_cast(to.Elements())}; if (vector.useValueLengthParameters()) { // Array constructor with no type spec. if (to.IsAllocatable() && !to.IsAllocated()) { // Takes length parameters, if any, from the first value. // Note that "to" type must already be set by the caller of this API since // it cannot be taken from "from" here: "from" may be polymorphic (have a // dynamic type that differs from its declared type) and Fortran 2018 7.8 // point 4. says that the dynamic type of an array constructor is its // declared type: it does not inherit the dynamic type of its ac-value // even if if there is no type-spec. if (to.type().IsCharacter()) { to.raw().elem_len = from.ElementBytes(); } else if (auto *toAddendum{to.Addendum()}) { if (const auto *fromAddendum{from.Addendum()}) { if (const auto *toDerived{toAddendum->derivedType()}) { std::size_t lenParms{toDerived->LenParameters()}; for (std::size_t j{0}; j < lenParms; ++j) { toAddendum->SetLenParameterValue( j, fromAddendum->LenParameterValue(j)); } } } } } else if (to.type().IsCharacter()) { // Fortran 2018 7.8 point 2. if (to.ElementBytes() != from.ElementBytes()) { terminator.Crash("Array constructor: mismatched character lengths (%d " "!= %d) between " "values of an array constructor without type-spec", to.ElementBytes() / to.type().GetCategoryAndKind()->second, from.ElementBytes() / from.type().GetCategoryAndKind()->second); } } } // Otherwise, the array constructor had a type-spec and the length // parameters are already in the "to" descriptor. AllocateOrReallocateVectorIfNeeded( vector, terminator, previousToElements, fromElements); // Create descriptor for "to" element or section being copied to. SubscriptValue lower[1]{ to.GetDimension(0).LowerBound() + vector.nextValuePosition}; SubscriptValue upper[1]{lower[0] + fromElements - 1}; SubscriptValue stride[1]{from.rank() == 0 ? 0 : 1}; StaticDescriptor staticDesc; Descriptor &toCurrentElement{staticDesc.descriptor()}; toCurrentElement.EstablishPointerSection(to, lower, upper, stride); // Note: toCurrentElement and from have the same number of elements // and "toCurrentElement" is not an allocatable so AssignTemporary // below works even if "from" rank is bigger than one (and differs // from "toCurrentElement") and not time is wasted reshaping // "toCurrentElement" to "from" shape. RTNAME(AssignTemporary) (toCurrentElement, from, vector.sourceFile, vector.sourceLine); vector.nextValuePosition += fromElements; } void RTDEF(PushArrayConstructorSimpleScalar)( ArrayConstructorVector &vector, void *from) { Terminator terminator{vector.sourceFile, vector.sourceLine}; Descriptor &to{vector.to}; AllocateOrReallocateVectorIfNeeded(vector, terminator, to.Elements(), 1); SubscriptValue subscript[1]{ to.GetDimension(0).LowerBound() + vector.nextValuePosition}; std::memcpy(to.Element(subscript), from, to.ElementBytes()); ++vector.nextValuePosition; } RT_EXT_API_GROUP_END } // extern "C" } // namespace Fortran::runtime