-
Notifications
You must be signed in to change notification settings - Fork 10.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[flang][runtime] Add API to help with the difficult array constructor…
… cases This runtime API can be used to lower any flavor of array constructors, but is mainly intended to be used with: - array constructors for which the extent or length parameters cannot be computed without lowering some ac-value or ac-implied-do-control that cannot be pre-evaluated. - array constructors of a derived type with allocatable component where copy is not trivial or PDTS. Example of use cases: - `[((i+j,i=1, ifoo()), j=1,n)]` where ifoo() is not pure. - `[return_allocatable_array(), return_allocatable_array()]` Differential Revision: https://reviews.llvm.org/D144411
- Loading branch information
1 parent
02eda22
commit 5226f8a
Showing
7 changed files
with
464 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
//===-- include/flang/Runtime/array-constructor.h ---------------*- 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
// External APIs to create temporary storage for array constructors when their | ||
// final extents or length parameters cannot be pre-computed. | ||
|
||
#ifndef FORTRAN_RUNTIME_ARRAYCONSTRUCTOR_H_ | ||
#define FORTRAN_RUNTIME_ARRAYCONSTRUCTOR_H_ | ||
|
||
#include "flang/Runtime/descriptor.h" | ||
#include "flang/Runtime/entry-names.h" | ||
#include <cstdint> | ||
|
||
namespace Fortran::runtime { | ||
|
||
// Runtime data structure to hold information about the storage of | ||
// an array constructor being constructed. | ||
struct ArrayConstructorVector { | ||
ArrayConstructorVector(class Descriptor &to, SubscriptValue nextValuePosition, | ||
SubscriptValue actualAllocationSize, const char *sourceFile, | ||
int sourceLine, bool useValueLengthParameters) | ||
: to{to}, nextValuePosition{nextValuePosition}, | ||
actualAllocationSize{actualAllocationSize}, sourceFile{sourceFile}, | ||
sourceLine{sourceLine}, useValueLengthParameters_{ | ||
useValueLengthParameters} {} | ||
|
||
bool useValueLengthParameters() const { return useValueLengthParameters_; } | ||
|
||
class Descriptor &to; | ||
SubscriptValue nextValuePosition; | ||
SubscriptValue actualAllocationSize; | ||
const char *sourceFile; | ||
int sourceLine; | ||
|
||
private: | ||
unsigned char useValueLengthParameters_ : 1; | ||
}; | ||
|
||
// This file defines an API to "push" an evaluated array constructor value | ||
// "from" into some storage "to" of an array constructor. It can be seen as a | ||
// form of std::vector::push_back() implementation for Fortran array | ||
// constructors. In the APIs and ArrayConstructorVector struct above: | ||
// | ||
// - "to" is a ranked-1 descriptor whose declared type is already set to the | ||
// array constructor derived type. It may be already allocated, even before the | ||
// first call to this API, or it may be unallocated. "to" extent is increased | ||
// every time a "from" is pushed past its current extent. At this end of the | ||
// API calls, its extent is the extent of the array constructor. If "to" is | ||
// unallocated and its extent is not null, it is assumed this is the final array | ||
// constructor extent value, and the first allocation already "reserves" storage | ||
// space accordingly to avoid reallocations. | ||
// - "from" is a scalar or array descriptor for the evaluated array | ||
// constructor value that must be copied into the storage of "to" at | ||
// "nextValuePosition". | ||
// - "useValueLengthParameters" must be set to true if the array constructor | ||
// has length parameters and no type spec. If it is true and "to" is | ||
// unallocated, "to" will take the length parameters of "from". If it is true | ||
// and "to" is an allocated character array constructor, it will be checked | ||
// that "from" length matches the one from "to". When it is false, the | ||
// character length must already be set in "to" before the first call to this | ||
// API and "from" character lengths are allowed to mismatch from "to". | ||
// - "nextValuePosition" is the zero based sequence position of "from" in the | ||
// array constructor. It is updated after this call by the number of "from" | ||
// elements. It should be set to zero by the caller of this API before the first | ||
// call. | ||
// - "actualAllocationSize" is the current allocation size of "to" storage. It | ||
// may be bigger than "to" extent for reallocation optimization purposes, but | ||
// should never be smaller, unless this is the first call and "to" is | ||
// unallocated. It is updated by the runtime after each successful allocation or | ||
// reallocation. It should be set to "to" extent if "to" is allocated before the | ||
// first call of this API, and can be left undefined otherwise. | ||
// | ||
// Note that this API can be used with "to" being a variable (that can be | ||
// discontiguous). This can be done when the variable is the left hand side of | ||
// an assignment from an array constructor as long as: | ||
// - none of the ac-value overlaps with the variable, | ||
// - this is an intrinsic assignment that is not a whole allocatable | ||
// assignment, *and* for a type that has no components requiring user defined | ||
// assignments, | ||
// - the variable is properly finalized before using this API if its need to, | ||
// - "useValueLengthParameters" should be set to false in this case, even if | ||
// the array constructor has no type-spec, since the variable may have a | ||
// different character length than the array constructor values. | ||
|
||
extern "C" { | ||
// API to initialize an ArrayConstructorVector before any values are pushed to | ||
// it. Inlined code is only expected to allocate the "ArrayConstructorVector" | ||
// class instance storage with sufficient size (using | ||
// "2*sizeof(ArrayConstructorVector)" on the host should be safe regardless of | ||
// the target the runtime is compiled for). This avoids the need for the runtime | ||
// to maintain a state, or to use dynamic allocation for it. "vectorClassSize" | ||
// is used to validate that lowering allocated enough space for it. | ||
void RTNAME(InitArrayConstructorVector)(ArrayConstructorVector &vector, | ||
Descriptor &to, bool useValueLengthParameters, int vectorClassSize, | ||
const char *sourceFile = nullptr, int sourceLine = 0); | ||
|
||
// Generic API to push any kind of entity into the array constructor (any | ||
// Fortran type and any rank). | ||
void RTNAME(PushArrayConstructorValue)( | ||
ArrayConstructorVector &vector, const Descriptor &from); | ||
|
||
// API to push scalar array constructor value of: | ||
// - a numerical or logical type, | ||
// - or a derived type that has no length parameters, and no allocatable | ||
// component (that would require deep copies). | ||
// It requires no descriptor for the value that is passed via its base address. | ||
void RTNAME(PushArrayConstructorSimpleScalar)( | ||
ArrayConstructorVector &vector, void *from); | ||
} // extern "C" | ||
} // namespace Fortran::runtime | ||
#endif // FORTRAN_RUNTIME_ARRAYCONSTRUCTOR_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
//===-- 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 "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 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 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{std::realloc(to.raw().base_addr, newByteSize)}; | ||
RUNTIME_CHECK(terminator, p); | ||
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" { | ||
void RTNAME(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<std::size_t>(vectorClassSize)); | ||
SubscriptValue actualAllocationSize{ | ||
to.IsAllocated() ? static_cast<SubscriptValue>(to.Elements()) : 0}; | ||
(void)new (&vector) ArrayConstructorVector{to, /*nextValuePosition=*/0, | ||
actualAllocationSize, sourceFile, sourceLine, useValueLengthParameters}; | ||
} | ||
|
||
void RTNAME(PushArrayConstructorValue)( | ||
ArrayConstructorVector &vector, const Descriptor &from) { | ||
Terminator terminator{vector.sourceFile, vector.sourceLine}; | ||
Descriptor &to{vector.to}; | ||
SubscriptValue fromElements{static_cast<SubscriptValue>(from.Elements())}; | ||
SubscriptValue previousToElements{static_cast<SubscriptValue>(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<maxRank, true, 1> 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 RTNAME(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<char>(subscript), from, to.ElementBytes()); | ||
++vector.nextValuePosition; | ||
} | ||
} // extern "C" | ||
} // namespace Fortran::runtime |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.