Revert runtime work queue patch, it breaks some tests that need investigation (#143713)

Revert "[flang][runtime] Another try to fix build failure"

This reverts commit 13869cac2b5051e453aa96ad71220d9d33404620.

Revert "[flang][runtime] Fix build bot flang-runtime-cuda-gcc errors
(#143650)"

This reverts commit d75e28477a.

Revert "[flang][runtime] Replace recursion with iterative work queue
(#137727)"

This reverts commit 163c67ad3d.
This commit is contained in:
Peter Klausler
2025-06-11 07:55:06 -07:00
committed by GitHub
parent 4e441665cc
commit 10f512f7bb
31 changed files with 1121 additions and 2232 deletions

View File

@@ -64,9 +64,6 @@ struct ExecutionEnvironment {
bool defaultUTF8{false}; // DEFAULT_UTF8
bool checkPointerDeallocation{true}; // FORT_CHECK_POINTER_DEALLOCATION
enum InternalDebugging { WorkQueue = 1 };
int internalDebugging{0}; // FLANG_RT_DEBUG
// CUDA related variables
std::size_t cudaStackLimit{0}; // ACC_OFFLOAD_STACK_SIZE
bool cudaDeviceIsManaged{false}; // NV_CUDAFOR_DEVICE_IS_MANAGED

View File

@@ -24,7 +24,7 @@ class Terminator;
enum Stat {
StatOk = 0, // required to be zero by Fortran
// Interoperable STAT= codes (>= 11)
// Interoperable STAT= codes
StatBaseNull = CFI_ERROR_BASE_ADDR_NULL,
StatBaseNotNull = CFI_ERROR_BASE_ADDR_NOT_NULL,
StatInvalidElemLen = CFI_INVALID_ELEM_LEN,
@@ -36,7 +36,7 @@ enum Stat {
StatMemAllocation = CFI_ERROR_MEM_ALLOCATION,
StatOutOfBounds = CFI_ERROR_OUT_OF_BOUNDS,
// Standard STAT= values (>= 101)
// Standard STAT= values
StatFailedImage = FORTRAN_RUNTIME_STAT_FAILED_IMAGE,
StatLocked = FORTRAN_RUNTIME_STAT_LOCKED,
StatLockedOtherImage = FORTRAN_RUNTIME_STAT_LOCKED_OTHER_IMAGE,
@@ -49,14 +49,10 @@ enum Stat {
// Additional "processor-defined" STAT= values
StatInvalidArgumentNumber = FORTRAN_RUNTIME_STAT_INVALID_ARG_NUMBER,
StatMissingArgument = FORTRAN_RUNTIME_STAT_MISSING_ARG,
StatValueTooShort = FORTRAN_RUNTIME_STAT_VALUE_TOO_SHORT, // -1
StatValueTooShort = FORTRAN_RUNTIME_STAT_VALUE_TOO_SHORT,
StatMoveAllocSameAllocatable =
FORTRAN_RUNTIME_STAT_MOVE_ALLOC_SAME_ALLOCATABLE,
StatBadPointerDeallocation = FORTRAN_RUNTIME_STAT_BAD_POINTER_DEALLOCATION,
// Dummy status for work queue continuation, declared here to perhaps
// avoid collisions
StatContinue = 201
};
RT_API_ATTRS const char *StatErrorString(int);

View File

@@ -240,7 +240,6 @@ public:
RT_API_ATTRS bool noFinalizationNeeded() const {
return noFinalizationNeeded_;
}
RT_API_ATTRS bool noDefinedAssignment() const { return noDefinedAssignment_; }
RT_API_ATTRS std::size_t LenParameters() const {
return lenParameterKind().Elements();
@@ -323,7 +322,6 @@ private:
bool noInitializationNeeded_{false};
bool noDestructionNeeded_{false};
bool noFinalizationNeeded_{false};
bool noDefinedAssignment_{false};
};
} // namespace Fortran::runtime::typeInfo

View File

@@ -1,552 +0,0 @@
//===-- include/flang-rt/runtime/work-queue.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
//
//===----------------------------------------------------------------------===//
// Internal runtime utilities for work queues that replace the use of recursion
// for better GPU device support.
//
// A work queue comprises a list of tickets. Each ticket class has a Begin()
// member function, which is called once, and a Continue() member function
// that can be called zero or more times. A ticket's execution terminates
// when either of these member functions returns a status other than
// StatContinue. When that status is not StatOk, then the whole queue
// is shut down.
//
// By returning StatContinue from its Continue() member function,
// a ticket suspends its execution so that any nested tickets that it
// may have created can be run to completion. It is the reponsibility
// of each ticket class to maintain resumption information in its state
// and manage its own progress. Most ticket classes inherit from
// class ComponentsOverElements, which implements an outer loop over all
// components of a derived type, and an inner loop over all elements
// of a descriptor, possibly with multiple phases of execution per element.
//
// Tickets are created by WorkQueue::Begin...() member functions.
// There is one of these for each "top level" recursive function in the
// Fortran runtime support library that has been restructured into this
// ticket framework.
//
// When the work queue is running tickets, it always selects the last ticket
// on the list for execution -- "work stack" might have been a more accurate
// name for this framework. This ticket may, while doing its job, create
// new tickets, and since those are pushed after the active one, the first
// such nested ticket will be the next one executed to completion -- i.e.,
// the order of nested WorkQueue::Begin...() calls is respected.
// Note that a ticket's Continue() member function won't be called again
// until all nested tickets have run to completion and it is once again
// the last ticket on the queue.
//
// Example for an assignment to a derived type:
// 1. Assign() is called, and its work queue is created. It calls
// WorkQueue::BeginAssign() and then WorkQueue::Run().
// 2. Run calls AssignTicket::Begin(), which pushes a tickets via
// BeginFinalize() and returns StatContinue.
// 3. FinalizeTicket::Begin() and FinalizeTicket::Continue() are called
// until one of them returns StatOk, which ends the finalization ticket.
// 4. AssignTicket::Continue() is then called; it creates a DerivedAssignTicket
// and then returns StatOk, which ends the ticket.
// 5. At this point, only one ticket remains. DerivedAssignTicket::Begin()
// and ::Continue() are called until they are done (not StatContinue).
// Along the way, it may create nested AssignTickets for components,
// and suspend itself so that they may each run to completion.
#ifndef FLANG_RT_RUNTIME_WORK_QUEUE_H_
#define FLANG_RT_RUNTIME_WORK_QUEUE_H_
#include "flang-rt/runtime/connection.h"
#include "flang-rt/runtime/descriptor.h"
#include "flang-rt/runtime/stat.h"
#include "flang-rt/runtime/type-info.h"
#include "flang/Common/api-attrs.h"
#include "flang/Runtime/freestanding-tools.h"
#include <flang/Common/variant.h>
namespace Fortran::runtime::io {
class IoStatementState;
struct NonTbpDefinedIoTable;
} // namespace Fortran::runtime::io
namespace Fortran::runtime {
class Terminator;
class WorkQueue;
// Ticket worker base classes
template <typename TICKET> class ImmediateTicketRunner {
public:
RT_API_ATTRS explicit ImmediateTicketRunner(TICKET &ticket)
: ticket_{ticket} {}
RT_API_ATTRS int Run(WorkQueue &workQueue) {
int status{ticket_.Begin(workQueue)};
while (status == StatContinue) {
status = ticket_.Continue(workQueue);
}
return status;
}
private:
TICKET &ticket_;
};
// Base class for ticket workers that operate elementwise over descriptors
class Elementwise {
public:
RT_API_ATTRS Elementwise(
const Descriptor &instance, const Descriptor *from = nullptr)
: instance_{instance}, from_{from} {
instance_.GetLowerBounds(subscripts_);
if (from_) {
from_->GetLowerBounds(fromSubscripts_);
}
}
RT_API_ATTRS bool IsComplete() const { return elementAt_ >= elements_; }
RT_API_ATTRS void Advance() {
++elementAt_;
instance_.IncrementSubscripts(subscripts_);
if (from_) {
from_->IncrementSubscripts(fromSubscripts_);
}
}
RT_API_ATTRS void SkipToEnd() { elementAt_ = elements_; }
RT_API_ATTRS void Reset() {
elementAt_ = 0;
instance_.GetLowerBounds(subscripts_);
if (from_) {
from_->GetLowerBounds(fromSubscripts_);
}
}
protected:
const Descriptor &instance_, *from_{nullptr};
std::size_t elements_{instance_.Elements()};
std::size_t elementAt_{0};
SubscriptValue subscripts_[common::maxRank];
SubscriptValue fromSubscripts_[common::maxRank];
};
// Base class for ticket workers that operate over derived type components.
class Componentwise {
public:
RT_API_ATTRS Componentwise(const typeInfo::DerivedType &);
RT_API_ATTRS bool IsComplete() const { return componentAt_ >= components_; }
RT_API_ATTRS void Advance() {
++componentAt_;
GetComponent();
}
RT_API_ATTRS void SkipToEnd() {
component_ = nullptr;
componentAt_ = components_;
}
RT_API_ATTRS void Reset() {
component_ = nullptr;
componentAt_ = 0;
GetComponent();
}
RT_API_ATTRS void GetComponent();
protected:
const typeInfo::DerivedType &derived_;
std::size_t components_{0}, componentAt_{0};
const typeInfo::Component *component_{nullptr};
StaticDescriptor<common::maxRank, true, 0> componentDescriptor_;
};
// Base class for ticket workers that operate over derived type components
// in an outer loop, and elements in an inner loop.
class ComponentsOverElements : public Componentwise, public Elementwise {
public:
RT_API_ATTRS ComponentsOverElements(const Descriptor &instance,
const typeInfo::DerivedType &derived, const Descriptor *from = nullptr)
: Componentwise{derived}, Elementwise{instance, from} {
if (Elementwise::IsComplete()) {
Componentwise::SkipToEnd();
}
}
RT_API_ATTRS bool IsComplete() const { return Componentwise::IsComplete(); }
RT_API_ATTRS void Advance() {
SkipToNextElement();
if (Elementwise::IsComplete()) {
Elementwise::Reset();
Componentwise::Advance();
}
}
RT_API_ATTRS void SkipToNextElement() {
phase_ = 0;
Elementwise::Advance();
}
RT_API_ATTRS void SkipToNextComponent() {
phase_ = 0;
Elementwise::Reset();
Componentwise::Advance();
}
RT_API_ATTRS void Reset() {
phase_ = 0;
Elementwise::Reset();
Componentwise::Reset();
}
protected:
int phase_{0};
};
// Base class for ticket workers that operate over elements in an outer loop,
// type components in an inner loop.
class ElementsOverComponents : public Elementwise, public Componentwise {
public:
RT_API_ATTRS ElementsOverComponents(const Descriptor &instance,
const typeInfo::DerivedType &derived, const Descriptor *from = nullptr)
: Elementwise{instance, from}, Componentwise{derived} {
if (Componentwise::IsComplete()) {
Elementwise::SkipToEnd();
}
}
RT_API_ATTRS bool IsComplete() const { return Elementwise::IsComplete(); }
RT_API_ATTRS void Advance() {
SkipToNextComponent();
if (Componentwise::IsComplete()) {
Componentwise::Reset();
Elementwise::Advance();
}
}
RT_API_ATTRS void SkipToNextComponent() {
phase_ = 0;
Componentwise::Advance();
}
RT_API_ATTRS void SkipToNextElement() {
phase_ = 0;
Componentwise::Reset();
Elementwise::Advance();
}
protected:
int phase_{0};
};
// Ticket worker classes
// Implements derived type instance initialization
class InitializeTicket : public ImmediateTicketRunner<InitializeTicket>,
private ComponentsOverElements {
public:
RT_API_ATTRS InitializeTicket(
const Descriptor &instance, const typeInfo::DerivedType &derived)
: ImmediateTicketRunner<InitializeTicket>{*this},
ComponentsOverElements{instance, derived} {}
RT_API_ATTRS int Begin(WorkQueue &);
RT_API_ATTRS int Continue(WorkQueue &);
};
// Initializes one derived type instance from the value of another
class InitializeCloneTicket
: public ImmediateTicketRunner<InitializeCloneTicket>,
private ComponentsOverElements {
public:
RT_API_ATTRS InitializeCloneTicket(const Descriptor &clone,
const Descriptor &original, const typeInfo::DerivedType &derived,
bool hasStat, const Descriptor *errMsg)
: ImmediateTicketRunner<InitializeCloneTicket>{*this},
ComponentsOverElements{original, derived}, clone_{clone},
hasStat_{hasStat}, errMsg_{errMsg} {}
RT_API_ATTRS int Begin(WorkQueue &) { return StatContinue; }
RT_API_ATTRS int Continue(WorkQueue &);
private:
const Descriptor &clone_;
bool hasStat_{false};
const Descriptor *errMsg_{nullptr};
StaticDescriptor<common::maxRank, true, 0> cloneComponentDescriptor_;
};
// Implements derived type instance finalization
class FinalizeTicket : public ImmediateTicketRunner<FinalizeTicket>,
private ComponentsOverElements {
public:
RT_API_ATTRS FinalizeTicket(
const Descriptor &instance, const typeInfo::DerivedType &derived)
: ImmediateTicketRunner<FinalizeTicket>{*this},
ComponentsOverElements{instance, derived} {}
RT_API_ATTRS int Begin(WorkQueue &);
RT_API_ATTRS int Continue(WorkQueue &);
private:
const typeInfo::DerivedType *finalizableParentType_{nullptr};
};
// Implements derived type instance destruction
class DestroyTicket : public ImmediateTicketRunner<DestroyTicket>,
private ComponentsOverElements {
public:
RT_API_ATTRS DestroyTicket(const Descriptor &instance,
const typeInfo::DerivedType &derived, bool finalize)
: ImmediateTicketRunner<DestroyTicket>{*this},
ComponentsOverElements{instance, derived}, finalize_{finalize} {}
RT_API_ATTRS int Begin(WorkQueue &);
RT_API_ATTRS int Continue(WorkQueue &);
private:
bool finalize_{false};
};
// Implements general intrinsic assignment
class AssignTicket : public ImmediateTicketRunner<AssignTicket> {
public:
RT_API_ATTRS AssignTicket(
Descriptor &to, const Descriptor &from, int flags, MemmoveFct memmoveFct)
: ImmediateTicketRunner<AssignTicket>{*this}, to_{to}, from_{&from},
flags_{flags}, memmoveFct_{memmoveFct} {}
RT_API_ATTRS int Begin(WorkQueue &);
RT_API_ATTRS int Continue(WorkQueue &);
private:
RT_API_ATTRS bool IsSimpleMemmove() const {
return !toDerived_ && to_.rank() == from_->rank() && to_.IsContiguous() &&
from_->IsContiguous() && to_.ElementBytes() == from_->ElementBytes();
}
RT_API_ATTRS Descriptor &GetTempDescriptor();
Descriptor &to_;
const Descriptor *from_{nullptr};
int flags_{0}; // enum AssignFlags
MemmoveFct memmoveFct_{nullptr};
StaticDescriptor<common::maxRank, true, 0> tempDescriptor_;
const typeInfo::DerivedType *toDerived_{nullptr};
Descriptor *toDeallocate_{nullptr};
bool persist_{false};
bool done_{false};
};
// Implements derived type intrinsic assignment.
template <bool IS_COMPONENTWISE>
class DerivedAssignTicket
: public ImmediateTicketRunner<DerivedAssignTicket<IS_COMPONENTWISE>>,
private std::conditional_t<IS_COMPONENTWISE, ComponentsOverElements,
ElementsOverComponents> {
public:
using Base = std::conditional_t<IS_COMPONENTWISE, ComponentsOverElements,
ElementsOverComponents>;
RT_API_ATTRS DerivedAssignTicket(const Descriptor &to, const Descriptor &from,
const typeInfo::DerivedType &derived, int flags, MemmoveFct memmoveFct,
Descriptor *deallocateAfter)
: ImmediateTicketRunner<DerivedAssignTicket>{*this},
Base{to, derived, &from}, flags_{flags}, memmoveFct_{memmoveFct},
deallocateAfter_{deallocateAfter} {}
RT_API_ATTRS int Begin(WorkQueue &);
RT_API_ATTRS int Continue(WorkQueue &);
private:
static constexpr bool isComponentwise_{IS_COMPONENTWISE};
bool toIsContiguous_{this->instance_.IsContiguous()};
bool fromIsContiguous_{this->from_->IsContiguous()};
int flags_{0};
MemmoveFct memmoveFct_{nullptr};
Descriptor *deallocateAfter_{nullptr};
StaticDescriptor<common::maxRank, true, 0> fromComponentDescriptor_;
};
namespace io::descr {
template <io::Direction DIR>
class DescriptorIoTicket
: public ImmediateTicketRunner<DescriptorIoTicket<DIR>>,
private Elementwise {
public:
RT_API_ATTRS DescriptorIoTicket(io::IoStatementState &io,
const Descriptor &descriptor, const io::NonTbpDefinedIoTable *table,
bool &anyIoTookPlace)
: ImmediateTicketRunner<DescriptorIoTicket>(*this),
Elementwise{descriptor}, io_{io}, table_{table},
anyIoTookPlace_{anyIoTookPlace} {}
RT_API_ATTRS int Begin(WorkQueue &);
RT_API_ATTRS int Continue(WorkQueue &);
RT_API_ATTRS bool &anyIoTookPlace() { return anyIoTookPlace_; }
private:
io::IoStatementState &io_;
const io::NonTbpDefinedIoTable *table_{nullptr};
bool &anyIoTookPlace_;
common::optional<typeInfo::SpecialBinding> nonTbpSpecial_;
const typeInfo::DerivedType *derived_{nullptr};
const typeInfo::SpecialBinding *special_{nullptr};
StaticDescriptor<common::maxRank, true, 0> elementDescriptor_;
};
template <io::Direction DIR>
class DerivedIoTicket : public ImmediateTicketRunner<DerivedIoTicket<DIR>>,
private ElementsOverComponents {
public:
RT_API_ATTRS DerivedIoTicket(io::IoStatementState &io,
const Descriptor &descriptor, const typeInfo::DerivedType &derived,
const io::NonTbpDefinedIoTable *table, bool &anyIoTookPlace)
: ImmediateTicketRunner<DerivedIoTicket>(*this),
ElementsOverComponents{descriptor, derived}, io_{io}, table_{table},
anyIoTookPlace_{anyIoTookPlace} {}
RT_API_ATTRS int Begin(WorkQueue &) { return StatContinue; }
RT_API_ATTRS int Continue(WorkQueue &);
private:
io::IoStatementState &io_;
const io::NonTbpDefinedIoTable *table_{nullptr};
bool &anyIoTookPlace_;
};
} // namespace io::descr
struct NullTicket {
RT_API_ATTRS int Begin(WorkQueue &) const { return StatOk; }
RT_API_ATTRS int Continue(WorkQueue &) const { return StatOk; }
};
struct Ticket {
RT_API_ATTRS int Continue(WorkQueue &);
bool begun{false};
std::variant<NullTicket, InitializeTicket, InitializeCloneTicket,
FinalizeTicket, DestroyTicket, AssignTicket, DerivedAssignTicket<false>,
DerivedAssignTicket<true>,
io::descr::DescriptorIoTicket<io::Direction::Output>,
io::descr::DescriptorIoTicket<io::Direction::Input>,
io::descr::DerivedIoTicket<io::Direction::Output>,
io::descr::DerivedIoTicket<io::Direction::Input>>
u;
};
class WorkQueue {
public:
RT_API_ATTRS explicit WorkQueue(Terminator &terminator)
: terminator_{terminator} {
for (int j{1}; j < numStatic_; ++j) {
static_[j].previous = &static_[j - 1];
static_[j - 1].next = &static_[j];
}
}
RT_API_ATTRS ~WorkQueue();
RT_API_ATTRS Terminator &terminator() { return terminator_; };
// APIs for particular tasks. These can return StatOk if the work is
// completed immediately.
RT_API_ATTRS int BeginInitialize(
const Descriptor &descriptor, const typeInfo::DerivedType &derived) {
if (runTicketsImmediately_) {
return InitializeTicket{descriptor, derived}.Run(*this);
} else {
StartTicket().u.emplace<InitializeTicket>(descriptor, derived);
return StatContinue;
}
}
RT_API_ATTRS int BeginInitializeClone(const Descriptor &clone,
const Descriptor &original, const typeInfo::DerivedType &derived,
bool hasStat, const Descriptor *errMsg) {
if (runTicketsImmediately_) {
return InitializeCloneTicket{clone, original, derived, hasStat, errMsg}
.Run(*this);
} else {
StartTicket().u.emplace<InitializeCloneTicket>(
clone, original, derived, hasStat, errMsg);
return StatContinue;
}
}
RT_API_ATTRS int BeginFinalize(
const Descriptor &descriptor, const typeInfo::DerivedType &derived) {
if (runTicketsImmediately_) {
return FinalizeTicket{descriptor, derived}.Run(*this);
} else {
StartTicket().u.emplace<FinalizeTicket>(descriptor, derived);
return StatContinue;
}
}
RT_API_ATTRS int BeginDestroy(const Descriptor &descriptor,
const typeInfo::DerivedType &derived, bool finalize) {
if (runTicketsImmediately_) {
return DestroyTicket{descriptor, derived, finalize}.Run(*this);
} else {
StartTicket().u.emplace<DestroyTicket>(descriptor, derived, finalize);
return StatContinue;
}
}
RT_API_ATTRS int BeginAssign(Descriptor &to, const Descriptor &from,
int flags, MemmoveFct memmoveFct) {
if (runTicketsImmediately_) {
return AssignTicket{to, from, flags, memmoveFct}.Run(*this);
} else {
StartTicket().u.emplace<AssignTicket>(to, from, flags, memmoveFct);
return StatContinue;
}
}
template <bool IS_COMPONENTWISE>
RT_API_ATTRS int BeginDerivedAssign(Descriptor &to, const Descriptor &from,
const typeInfo::DerivedType &derived, int flags, MemmoveFct memmoveFct,
Descriptor *deallocateAfter) {
if (runTicketsImmediately_) {
return DerivedAssignTicket<IS_COMPONENTWISE>{
to, from, derived, flags, memmoveFct, deallocateAfter}
.Run(*this);
} else {
StartTicket().u.emplace<DerivedAssignTicket<IS_COMPONENTWISE>>(
to, from, derived, flags, memmoveFct, deallocateAfter);
return StatContinue;
}
}
template <io::Direction DIR>
RT_API_ATTRS int BeginDescriptorIo(io::IoStatementState &io,
const Descriptor &descriptor, const io::NonTbpDefinedIoTable *table,
bool &anyIoTookPlace) {
if (runTicketsImmediately_) {
return io::descr::DescriptorIoTicket<DIR>{
io, descriptor, table, anyIoTookPlace}
.Run(*this);
} else {
StartTicket().u.emplace<io::descr::DescriptorIoTicket<DIR>>(
io, descriptor, table, anyIoTookPlace);
return StatContinue;
}
}
template <io::Direction DIR>
RT_API_ATTRS int BeginDerivedIo(io::IoStatementState &io,
const Descriptor &descriptor, const typeInfo::DerivedType &derived,
const io::NonTbpDefinedIoTable *table, bool &anyIoTookPlace) {
if (runTicketsImmediately_) {
return io::descr::DerivedIoTicket<DIR>{
io, descriptor, derived, table, anyIoTookPlace}
.Run(*this);
} else {
StartTicket().u.emplace<io::descr::DerivedIoTicket<DIR>>(
io, descriptor, derived, table, anyIoTookPlace);
return StatContinue;
}
}
RT_API_ATTRS int Run();
private:
#if RT_DEVICE_COMPILATION
// Always use the work queue on a GPU device to avoid recursion.
static constexpr bool runTicketsImmediately_{false};
#else
// Avoid the work queue overhead on the host, unless it needs
// debugging, which is so much easier there.
static constexpr bool runTicketsImmediately_{true};
#endif
// Most uses of the work queue won't go very deep.
static constexpr int numStatic_{2};
struct TicketList {
bool isStatic{true};
Ticket ticket;
TicketList *previous{nullptr}, *next{nullptr};
};
RT_API_ATTRS Ticket &StartTicket();
RT_API_ATTRS void Stop();
Terminator &terminator_;
TicketList *first_{nullptr}, *last_{nullptr}, *insertAfter_{nullptr};
TicketList static_[numStatic_];
TicketList *firstFree_{static_};
};
} // namespace Fortran::runtime
#endif // FLANG_RT_RUNTIME_WORK_QUEUE_H_

View File

@@ -68,7 +68,6 @@ set(supported_sources
type-info.cpp
unit.cpp
utf.cpp
work-queue.cpp
)
# List of source not used for GPU offloading.
@@ -132,7 +131,6 @@ set(gpu_sources
type-code.cpp
type-info.cpp
utf.cpp
work-queue.cpp
complex-powi.cpp
reduce.cpp
reduction.cpp

View File

@@ -14,7 +14,6 @@
#include "flang-rt/runtime/terminator.h"
#include "flang-rt/runtime/tools.h"
#include "flang-rt/runtime/type-info.h"
#include "flang-rt/runtime/work-queue.h"
namespace Fortran::runtime {
@@ -103,7 +102,11 @@ static RT_API_ATTRS int AllocateAssignmentLHS(
toDim.SetByteStride(stride);
stride *= toDim.Extent();
}
return ReturnError(terminator, to.Allocate(kNoAsyncObject));
int result{ReturnError(terminator, to.Allocate(kNoAsyncObject))};
if (result == StatOk && derived && !derived->noInitializationNeeded()) {
result = ReturnError(terminator, Initialize(to, *derived, terminator));
}
return result;
}
// least <= 0, most >= 0
@@ -228,8 +231,6 @@ static RT_API_ATTRS void BlankPadCharacterAssignment(Descriptor &to,
}
}
RT_OFFLOAD_API_GROUP_BEGIN
// Common implementation of assignments, both intrinsic assignments and
// those cases of polymorphic user-defined ASSIGNMENT(=) TBPs that could not
// be resolved in semantics. Most assignment statements do not need any
@@ -243,453 +244,275 @@ RT_OFFLOAD_API_GROUP_BEGIN
// dealing with array constructors.
RT_API_ATTRS void Assign(Descriptor &to, const Descriptor &from,
Terminator &terminator, int flags, MemmoveFct memmoveFct) {
WorkQueue workQueue{terminator};
if (workQueue.BeginAssign(to, from, flags, memmoveFct) == StatContinue) {
workQueue.Run();
bool mustDeallocateLHS{(flags & DeallocateLHS) ||
MustDeallocateLHS(to, from, terminator, flags)};
DescriptorAddendum *toAddendum{to.Addendum()};
const typeInfo::DerivedType *toDerived{
toAddendum ? toAddendum->derivedType() : nullptr};
if (toDerived && (flags & NeedFinalization) &&
toDerived->noFinalizationNeeded()) {
flags &= ~NeedFinalization;
}
}
RT_API_ATTRS int AssignTicket::Begin(WorkQueue &workQueue) {
bool mustDeallocateLHS{(flags_ & DeallocateLHS) ||
MustDeallocateLHS(to_, *from_, workQueue.terminator(), flags_)};
DescriptorAddendum *toAddendum{to_.Addendum()};
toDerived_ = toAddendum ? toAddendum->derivedType() : nullptr;
if (toDerived_ && (flags_ & NeedFinalization) &&
toDerived_->noFinalizationNeeded()) {
flags_ &= ~NeedFinalization;
}
if (MayAlias(to_, *from_)) {
std::size_t toElementBytes{to.ElementBytes()};
std::size_t fromElementBytes{from.ElementBytes()};
// The following lambda definition violates the conding style,
// but cuda-11.8 nvcc hits an internal error with the brace initialization.
auto isSimpleMemmove = [&]() {
return !toDerived && to.rank() == from.rank() && to.IsContiguous() &&
from.IsContiguous() && toElementBytes == fromElementBytes;
};
StaticDescriptor<maxRank, true, 10 /*?*/> deferredDeallocStatDesc;
Descriptor *deferDeallocation{nullptr};
if (MayAlias(to, from)) {
if (mustDeallocateLHS) {
// Convert the LHS into a temporary, then make it look deallocated.
toDeallocate_ = &tempDescriptor_.descriptor();
persist_ = true; // tempDescriptor_ state must outlive child tickets
deferDeallocation = &deferredDeallocStatDesc.descriptor();
std::memcpy(
reinterpret_cast<void *>(toDeallocate_), &to_, to_.SizeInBytes());
to_.set_base_addr(nullptr);
if (toDerived_ && (flags_ & NeedFinalization)) {
if (int status{workQueue.BeginFinalize(*toDeallocate_, *toDerived_)};
status != StatOk && status != StatContinue) {
return status;
}
flags_ &= ~NeedFinalization;
}
} else if (!IsSimpleMemmove()) {
reinterpret_cast<void *>(deferDeallocation), &to, to.SizeInBytes());
to.set_base_addr(nullptr);
} else if (!isSimpleMemmove()) {
// Handle LHS/RHS aliasing by copying RHS into a temp, then
// recursively assigning from that temp.
auto descBytes{from_->SizeInBytes()};
Descriptor &newFrom{tempDescriptor_.descriptor()};
persist_ = true; // tempDescriptor_ state must outlive child tickets
std::memcpy(reinterpret_cast<void *>(&newFrom), from_, descBytes);
auto descBytes{from.SizeInBytes()};
StaticDescriptor<maxRank, true, 16> staticDesc;
Descriptor &newFrom{staticDesc.descriptor()};
std::memcpy(reinterpret_cast<void *>(&newFrom), &from, descBytes);
// Pretend the temporary descriptor is for an ALLOCATABLE
// entity, otherwise, the Deallocate() below will not
// free the descriptor memory.
newFrom.raw().attribute = CFI_attribute_allocatable;
if (int stat{ReturnError(
workQueue.terminator(), newFrom.Allocate(kNoAsyncObject))};
stat != StatOk) {
return stat;
}
if (HasDynamicComponent(*from_)) {
// If 'from' has allocatable/automatic component, we cannot
// just make a shallow copy of the descriptor member.
// This will still leave data overlap in 'to' and 'newFrom'.
// For example:
// type t
// character, allocatable :: c(:)
// end type t
// type(t) :: x(3)
// x(2:3) = x(1:2)
// We have to make a deep copy into 'newFrom' in this case.
if (const DescriptorAddendum *addendum{newFrom.Addendum()}) {
if (const auto *derived{addendum->derivedType()}) {
if (!derived->noInitializationNeeded()) {
if (int status{workQueue.BeginInitialize(newFrom, *derived)};
status != StatOk && status != StatContinue) {
return status;
}
}
}
auto stat{ReturnError(terminator, newFrom.Allocate(kNoAsyncObject))};
if (stat == StatOk) {
if (HasDynamicComponent(from)) {
// If 'from' has allocatable/automatic component, we cannot
// just make a shallow copy of the descriptor member.
// This will still leave data overlap in 'to' and 'newFrom'.
// For example:
// type t
// character, allocatable :: c(:)
// end type t
// type(t) :: x(3)
// x(2:3) = x(1:2)
// We have to make a deep copy into 'newFrom' in this case.
RTNAME(AssignTemporary)
(newFrom, from, terminator.sourceFileName(), terminator.sourceLine());
} else {
ShallowCopy(newFrom, from, true, from.IsContiguous());
}
static constexpr int nestedFlags{MaybeReallocate | PolymorphicLHS};
if (int status{workQueue.BeginAssign(
newFrom, *from_, nestedFlags, memmoveFct_)};
status != StatOk && status != StatContinue) {
return status;
Assign(to, newFrom, terminator,
flags &
(NeedFinalization | ComponentCanBeDefinedAssignment |
ExplicitLengthCharacterLHS | CanBeDefinedAssignment));
newFrom.Deallocate();
}
return;
}
}
if (to.IsAllocatable()) {
if (mustDeallocateLHS) {
if (deferDeallocation) {
if ((flags & NeedFinalization) && toDerived) {
Finalize(*deferDeallocation, *toDerived, &terminator);
flags &= ~NeedFinalization;
}
} else {
ShallowCopy(newFrom, *from_, true, from_->IsContiguous());
to.Destroy((flags & NeedFinalization) != 0, /*destroyPointers=*/false,
&terminator);
flags &= ~NeedFinalization;
}
from_ = &newFrom;
flags_ &= NeedFinalization | ComponentCanBeDefinedAssignment |
ExplicitLengthCharacterLHS | CanBeDefinedAssignment;
toDeallocate_ = &newFrom;
} else if (to.rank() != from.rank() && !to.IsAllocated()) {
terminator.Crash("Assign: mismatched ranks (%d != %d) in assignment to "
"unallocated allocatable",
to.rank(), from.rank());
}
}
if (to_.IsAllocatable()) {
if (mustDeallocateLHS) {
if (!toDeallocate_ && to_.IsAllocated()) {
toDeallocate_ = &to_;
}
} else if (to_.rank() != from_->rank() && !to_.IsAllocated()) {
workQueue.terminator().Crash("Assign: mismatched ranks (%d != %d) in "
"assignment to unallocated allocatable",
to_.rank(), from_->rank());
}
} else if (!to_.IsAllocated()) {
workQueue.terminator().Crash(
"Assign: left-hand side variable is neither allocated nor allocatable");
}
if (toDerived_ && to_.IsAllocated()) {
// Schedule finalization or destruction of the LHS.
if (flags_ & NeedFinalization) {
if (int status{workQueue.BeginFinalize(to_, *toDerived_)};
status != StatOk && status != StatContinue) {
return status;
}
} else if (!toDerived_->noDestructionNeeded()) {
if (int status{
workQueue.BeginDestroy(to_, *toDerived_, /*finalize=*/false)};
status != StatOk && status != StatContinue) {
return status;
if (!to.IsAllocated()) {
if (AllocateAssignmentLHS(to, from, terminator, flags) != StatOk) {
return;
}
flags &= ~NeedFinalization;
toElementBytes = to.ElementBytes(); // may have changed
toDerived = toAddendum ? toAddendum->derivedType() : nullptr;
}
}
return StatContinue;
}
RT_API_ATTRS int AssignTicket::Continue(WorkQueue &workQueue) {
if (done_) {
// All child tickets are complete; can release this ticket's state.
if (toDeallocate_) {
toDeallocate_->Deallocate();
}
return StatOk;
}
// All necessary finalization or destruction that was initiated by Begin()
// has been completed. Deallocation may be pending, and if it's for the LHS,
// do it now so that the LHS gets reallocated.
if (toDeallocate_ == &to_) {
toDeallocate_ = nullptr;
to_.Deallocate();
}
// Allocate the LHS if needed
if (!to_.IsAllocated()) {
if (int stat{
AllocateAssignmentLHS(to_, *from_, workQueue.terminator(), flags_)};
stat != StatOk) {
return stat;
}
const auto *addendum{to_.Addendum()};
toDerived_ = addendum ? addendum->derivedType() : nullptr;
if (toDerived_ && !toDerived_->noInitializationNeeded()) {
if (int status{workQueue.BeginInitialize(to_, *toDerived_)};
status != StatOk) {
return status;
}
}
}
// Check for a user-defined assignment type-bound procedure;
// see 10.2.1.4-5.
// Note that the aliasing and LHS (re)allocation handling above
// needs to run even with CanBeDefinedAssignment flag, since
// Assign() can be invoked recursively for component-wise assignments.
if (toDerived_ && (flags_ & CanBeDefinedAssignment)) {
if (to_.rank() == 0) {
if (const auto *special{toDerived_->FindSpecialBinding(
if (toDerived && (flags & CanBeDefinedAssignment)) {
// Check for a user-defined assignment type-bound procedure;
// see 10.2.1.4-5. A user-defined assignment TBP defines all of
// the semantics, including allocatable (re)allocation and any
// finalization.
//
// Note that the aliasing and LHS (re)allocation handling above
// needs to run even with CanBeDefinedAssignment flag, when
// the Assign() is invoked recursively for component-per-component
// assignments.
if (to.rank() == 0) {
if (const auto *special{toDerived->FindSpecialBinding(
typeInfo::SpecialBinding::Which::ScalarAssignment)}) {
DoScalarDefinedAssignment(to_, *from_, *special);
done_ = true;
return StatContinue;
return DoScalarDefinedAssignment(to, from, *special);
}
}
if (const auto *special{toDerived_->FindSpecialBinding(
if (const auto *special{toDerived->FindSpecialBinding(
typeInfo::SpecialBinding::Which::ElementalAssignment)}) {
DoElementalDefinedAssignment(to_, *from_, *toDerived_, *special);
done_ = true;
return StatContinue;
return DoElementalDefinedAssignment(to, from, *toDerived, *special);
}
}
// Intrinsic assignment
std::size_t toElements{to_.Elements()};
if (from_->rank() > 0 && toElements != from_->Elements()) {
workQueue.terminator().Crash("Assign: mismatching element counts in array "
"assignment (to %zd, from %zd)",
toElements, from_->Elements());
SubscriptValue toAt[maxRank];
to.GetLowerBounds(toAt);
// Scalar expansion of the RHS is implied by using the same empty
// subscript values on each (seemingly) elemental reference into
// "from".
SubscriptValue fromAt[maxRank];
from.GetLowerBounds(fromAt);
std::size_t toElements{to.Elements()};
if (from.rank() > 0 && toElements != from.Elements()) {
terminator.Crash("Assign: mismatching element counts in array assignment "
"(to %zd, from %zd)",
toElements, from.Elements());
}
if (to_.type() != from_->type()) {
workQueue.terminator().Crash(
"Assign: mismatching types (to code %d != from code %d)",
to_.type().raw(), from_->type().raw());
if (to.type() != from.type()) {
terminator.Crash("Assign: mismatching types (to code %d != from code %d)",
to.type().raw(), from.type().raw());
}
std::size_t toElementBytes{to_.ElementBytes()};
std::size_t fromElementBytes{from_->ElementBytes()};
if (toElementBytes > fromElementBytes && !to_.type().IsCharacter()) {
workQueue.terminator().Crash("Assign: mismatching non-character element "
"sizes (to %zd bytes != from %zd bytes)",
if (toElementBytes > fromElementBytes && !to.type().IsCharacter()) {
terminator.Crash("Assign: mismatching non-character element sizes (to %zd "
"bytes != from %zd bytes)",
toElementBytes, fromElementBytes);
}
if (toDerived_) {
if (toDerived_->noDefinedAssignment()) { // componentwise
if (int status{workQueue.BeginDerivedAssign<true>(
to_, *from_, *toDerived_, flags_, memmoveFct_, toDeallocate_)};
status != StatOk && status != StatContinue) {
return status;
if (const typeInfo::DerivedType *
updatedToDerived{toAddendum ? toAddendum->derivedType() : nullptr}) {
// Derived type intrinsic assignment, which is componentwise and elementwise
// for all components, including parent components (10.2.1.2-3).
// The target is first finalized if still necessary (7.5.6.3(1))
if (flags & NeedFinalization) {
Finalize(to, *updatedToDerived, &terminator);
} else if (updatedToDerived && !updatedToDerived->noDestructionNeeded()) {
Destroy(to, /*finalize=*/false, *updatedToDerived, &terminator);
}
// Copy the data components (incl. the parent) first.
const Descriptor &componentDesc{updatedToDerived->component()};
std::size_t numComponents{componentDesc.Elements()};
for (std::size_t j{0}; j < toElements;
++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) {
for (std::size_t k{0}; k < numComponents; ++k) {
const auto &comp{
*componentDesc.ZeroBasedIndexedElement<typeInfo::Component>(
k)}; // TODO: exploit contiguity here
// Use PolymorphicLHS for components so that the right things happen
// when the components are polymorphic; when they're not, they're both
// not, and their declared types will match.
int nestedFlags{MaybeReallocate | PolymorphicLHS};
if (flags & ComponentCanBeDefinedAssignment) {
nestedFlags |=
CanBeDefinedAssignment | ComponentCanBeDefinedAssignment;
}
switch (comp.genre()) {
case typeInfo::Component::Genre::Data:
if (comp.category() == TypeCategory::Derived) {
StaticDescriptor<maxRank, true, 10 /*?*/> statDesc[2];
Descriptor &toCompDesc{statDesc[0].descriptor()};
Descriptor &fromCompDesc{statDesc[1].descriptor()};
comp.CreatePointerDescriptor(toCompDesc, to, terminator, toAt);
comp.CreatePointerDescriptor(
fromCompDesc, from, terminator, fromAt);
Assign(toCompDesc, fromCompDesc, terminator, nestedFlags);
} else { // Component has intrinsic type; simply copy raw bytes
std::size_t componentByteSize{comp.SizeInBytes(to)};
memmoveFct(to.Element<char>(toAt) + comp.offset(),
from.Element<const char>(fromAt) + comp.offset(),
componentByteSize);
}
break;
case typeInfo::Component::Genre::Pointer: {
std::size_t componentByteSize{comp.SizeInBytes(to)};
memmoveFct(to.Element<char>(toAt) + comp.offset(),
from.Element<const char>(fromAt) + comp.offset(),
componentByteSize);
} break;
case typeInfo::Component::Genre::Allocatable:
case typeInfo::Component::Genre::Automatic: {
auto *toDesc{reinterpret_cast<Descriptor *>(
to.Element<char>(toAt) + comp.offset())};
const auto *fromDesc{reinterpret_cast<const Descriptor *>(
from.Element<char>(fromAt) + comp.offset())};
// Allocatable components of the LHS are unconditionally
// deallocated before assignment (F'2018 10.2.1.3(13)(1)),
// unlike a "top-level" assignment to a variable, where
// deallocation is optional.
//
// Be careful not to destroy/reallocate the LHS, if there is
// overlap between LHS and RHS (it seems that partial overlap
// is not possible, though).
// Invoke Assign() recursively to deal with potential aliasing.
if (toDesc->IsAllocatable()) {
if (!fromDesc->IsAllocated()) {
// No aliasing.
//
// If to is not allocated, the Destroy() call is a no-op.
// This is just a shortcut, because the recursive Assign()
// below would initiate the destruction for to.
// No finalization is required.
toDesc->Destroy(
/*finalize=*/false, /*destroyPointers=*/false, &terminator);
continue; // F'2018 10.2.1.3(13)(2)
}
}
// Force LHS deallocation with DeallocateLHS flag.
// The actual deallocation may be avoided, if the existing
// location can be reoccupied.
Assign(*toDesc, *fromDesc, terminator, nestedFlags | DeallocateLHS);
} break;
}
}
} else { // elementwise
if (int status{workQueue.BeginDerivedAssign<false>(
to_, *from_, *toDerived_, flags_, memmoveFct_, toDeallocate_)};
status != StatOk && status != StatContinue) {
return status;
// Copy procedure pointer components
const Descriptor &procPtrDesc{updatedToDerived->procPtr()};
std::size_t numProcPtrs{procPtrDesc.Elements()};
for (std::size_t k{0}; k < numProcPtrs; ++k) {
const auto &procPtr{
*procPtrDesc.ZeroBasedIndexedElement<typeInfo::ProcPtrComponent>(
k)};
memmoveFct(to.Element<char>(toAt) + procPtr.offset,
from.Element<const char>(fromAt) + procPtr.offset,
sizeof(typeInfo::ProcedurePointer));
}
}
toDeallocate_ = nullptr;
} else if (IsSimpleMemmove()) {
memmoveFct_(to_.raw().base_addr, from_->raw().base_addr,
toElements * toElementBytes);
} else {
// Scalar expansion of the RHS is implied by using the same empty
// subscript values on each (seemingly) elemental reference into
// "from".
SubscriptValue toAt[maxRank];
to_.GetLowerBounds(toAt);
SubscriptValue fromAt[maxRank];
from_->GetLowerBounds(fromAt);
if (toElementBytes > fromElementBytes) { // blank padding
switch (to_.type().raw()) {
} else { // intrinsic type, intrinsic assignment
if (isSimpleMemmove()) {
memmoveFct(to.raw().base_addr, from.raw().base_addr,
toElements * toElementBytes);
} else if (toElementBytes > fromElementBytes) { // blank padding
switch (to.type().raw()) {
case CFI_type_signed_char:
case CFI_type_char:
BlankPadCharacterAssignment<char>(to_, *from_, toAt, fromAt, toElements,
BlankPadCharacterAssignment<char>(to, from, toAt, fromAt, toElements,
toElementBytes, fromElementBytes);
break;
case CFI_type_char16_t:
BlankPadCharacterAssignment<char16_t>(to_, *from_, toAt, fromAt,
BlankPadCharacterAssignment<char16_t>(to, from, toAt, fromAt,
toElements, toElementBytes, fromElementBytes);
break;
case CFI_type_char32_t:
BlankPadCharacterAssignment<char32_t>(to_, *from_, toAt, fromAt,
BlankPadCharacterAssignment<char32_t>(to, from, toAt, fromAt,
toElements, toElementBytes, fromElementBytes);
break;
default:
workQueue.terminator().Crash(
"unexpected type code %d in blank padded Assign()",
to_.type().raw());
terminator.Crash("unexpected type code %d in blank padded Assign()",
to.type().raw());
}
} else { // elemental copies, possibly with character truncation
for (std::size_t n{toElements}; n-- > 0;
to_.IncrementSubscripts(toAt), from_->IncrementSubscripts(fromAt)) {
memmoveFct_(to_.Element<char>(toAt), from_->Element<const char>(fromAt),
to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) {
memmoveFct(to.Element<char>(toAt), from.Element<const char>(fromAt),
toElementBytes);
}
}
}
if (persist_) {
done_ = true;
return StatContinue;
} else {
if (toDeallocate_) {
toDeallocate_->Deallocate();
toDeallocate_ = nullptr;
}
return StatOk;
if (deferDeallocation) {
// deferDeallocation is used only when LHS is an allocatable.
// The finalization has already been run for it.
deferDeallocation->Destroy(
/*finalize=*/false, /*destroyPointers=*/false, &terminator);
}
}
template <bool IS_COMPONENTWISE>
RT_API_ATTRS int DerivedAssignTicket<IS_COMPONENTWISE>::Begin(
WorkQueue &workQueue) {
if (toIsContiguous_ && fromIsContiguous_ &&
this->derived_.noDestructionNeeded() &&
this->derived_.noDefinedAssignment() &&
this->instance_.rank() == this->from_->rank()) {
if (std::size_t elementBytes{this->instance_.ElementBytes()};
elementBytes == this->from_->ElementBytes()) {
// Fastest path. Both LHS and RHS are contiguous, RHS is not a scalar
// to be expanded, the types have the same size, and there are no
// allocatable components or defined ASSIGNMENT(=) at any level.
memmoveFct_(this->instance_.template OffsetElement<char>(),
this->from_->template OffsetElement<const char *>(),
this->instance_.Elements() * elementBytes);
return StatOk;
}
}
// Use PolymorphicLHS for components so that the right things happen
// when the components are polymorphic; when they're not, they're both
// not, and their declared types will match.
int nestedFlags{MaybeReallocate | PolymorphicLHS};
if (flags_ & ComponentCanBeDefinedAssignment) {
nestedFlags |= CanBeDefinedAssignment | ComponentCanBeDefinedAssignment;
}
flags_ = nestedFlags;
// Copy procedure pointer components
const Descriptor &procPtrDesc{this->derived_.procPtr()};
bool noDataComponents{this->IsComplete()};
if (std::size_t numProcPtrs{procPtrDesc.Elements()}) {
for (std::size_t k{0}; k < numProcPtrs; ++k) {
const auto &procPtr{
*procPtrDesc.ZeroBasedIndexedElement<typeInfo::ProcPtrComponent>(k)};
// Loop only over elements
if (noDataComponents) {
Elementwise::Reset();
}
for (; !Elementwise::IsComplete(); Elementwise::Advance()) {
memmoveFct_(this->instance_.template ElementComponent<char>(
this->subscripts_, procPtr.offset),
this->from_->template ElementComponent<const char>(
this->fromSubscripts_, procPtr.offset),
sizeof(typeInfo::ProcedurePointer));
}
}
if (noDataComponents) {
return StatOk;
}
Elementwise::Reset();
}
if (noDataComponents) {
return StatOk;
}
return StatContinue;
}
template RT_API_ATTRS int DerivedAssignTicket<false>::Begin(WorkQueue &);
template RT_API_ATTRS int DerivedAssignTicket<true>::Begin(WorkQueue &);
template <bool IS_COMPONENTWISE>
RT_API_ATTRS int DerivedAssignTicket<IS_COMPONENTWISE>::Continue(
WorkQueue &workQueue) {
while (!this->IsComplete()) {
// Copy the data components (incl. the parent) first.
switch (this->component_->genre()) {
case typeInfo::Component::Genre::Data:
if (this->component_->category() == TypeCategory::Derived) {
Descriptor &toCompDesc{this->componentDescriptor_.descriptor()};
Descriptor &fromCompDesc{this->fromComponentDescriptor_.descriptor()};
this->component_->CreatePointerDescriptor(toCompDesc, this->instance_,
workQueue.terminator(), this->subscripts_);
this->component_->CreatePointerDescriptor(fromCompDesc, *this->from_,
workQueue.terminator(), this->fromSubscripts_);
this->Advance();
if (int status{workQueue.BeginAssign(
toCompDesc, fromCompDesc, flags_, memmoveFct_)};
status != StatOk) {
return status;
}
} else { // Component has intrinsic type; simply copy raw bytes
std::size_t componentByteSize{
this->component_->SizeInBytes(this->instance_)};
if (IS_COMPONENTWISE && toIsContiguous_ && fromIsContiguous_) {
std::size_t offset{this->component_->offset()};
char *to{this->instance_.template OffsetElement<char>(offset)};
const char *from{
this->from_->template OffsetElement<const char>(offset)};
std::size_t toElementStride{this->instance_.ElementBytes()};
std::size_t fromElementStride{
this->from_->rank() == 0 ? 0 : this->from_->ElementBytes()};
if (toElementStride == fromElementStride &&
toElementStride == componentByteSize) {
memmoveFct_(to, from, this->elements_ * componentByteSize);
} else {
for (std::size_t n{this->elements_}; n--;
to += toElementStride, from += fromElementStride) {
memmoveFct_(to, from, componentByteSize);
}
}
this->Componentwise::Advance();
} else {
memmoveFct_(
this->instance_.template Element<char>(this->subscripts_) +
this->component_->offset(),
this->from_->template Element<const char>(this->fromSubscripts_) +
this->component_->offset(),
componentByteSize);
this->Advance();
}
}
break;
case typeInfo::Component::Genre::Pointer: {
std::size_t componentByteSize{
this->component_->SizeInBytes(this->instance_)};
if (IS_COMPONENTWISE && toIsContiguous_ && fromIsContiguous_) {
std::size_t offset{this->component_->offset()};
char *to{this->instance_.template OffsetElement<char>(offset)};
const char *from{
this->from_->template OffsetElement<const char>(offset)};
std::size_t toElementStride{this->instance_.ElementBytes()};
std::size_t fromElementStride{
this->from_->rank() == 0 ? 0 : this->from_->ElementBytes()};
if (toElementStride == fromElementStride &&
toElementStride == componentByteSize) {
memmoveFct_(to, from, this->elements_ * componentByteSize);
} else {
for (std::size_t n{this->elements_}; n--;
to += toElementStride, from += fromElementStride) {
memmoveFct_(to, from, componentByteSize);
}
}
this->Componentwise::Advance();
} else {
memmoveFct_(this->instance_.template Element<char>(this->subscripts_) +
this->component_->offset(),
this->from_->template Element<const char>(this->fromSubscripts_) +
this->component_->offset(),
componentByteSize);
this->Advance();
}
} break;
case typeInfo::Component::Genre::Allocatable:
case typeInfo::Component::Genre::Automatic: {
auto *toDesc{reinterpret_cast<Descriptor *>(
this->instance_.template Element<char>(this->subscripts_) +
this->component_->offset())};
const auto *fromDesc{reinterpret_cast<const Descriptor *>(
this->from_->template Element<char>(this->fromSubscripts_) +
this->component_->offset())};
if (toDesc->IsAllocatable() && !fromDesc->IsAllocated()) {
if (toDesc->IsAllocated()) {
if (this->phase_ == 0) {
this->phase_++;
if (const auto *componentDerived{this->component_->derivedType()};
componentDerived && !componentDerived->noDestructionNeeded()) {
if (int status{workQueue.BeginDestroy(
*toDesc, *componentDerived, /*finalize=*/false)};
status != StatOk) {
return status;
}
}
}
toDesc->Deallocate();
}
this->Advance();
} else {
// Allocatable components of the LHS are unconditionally
// deallocated before assignment (F'2018 10.2.1.3(13)(1)),
// unlike a "top-level" assignment to a variable, where
// deallocation is optional.
this->Advance();
int nestedFlags{flags_};
if (this->derived_.noFinalizationNeeded() &&
this->derived_.noInitializationNeeded() &&
this->derived_.noDestructionNeeded()) {
// The actual deallocation may be avoided, if the existing
// location can be reoccupied.
} else {
// Force LHS deallocation with DeallocateLHS flag.
nestedFlags |= DeallocateLHS;
}
if (int status{workQueue.BeginAssign(
*toDesc, *fromDesc, nestedFlags, memmoveFct_)};
status != StatOk) {
return status;
}
}
} break;
}
}
if (deallocateAfter_) {
deallocateAfter_->Deallocate();
}
return StatOk;
}
template RT_API_ATTRS int DerivedAssignTicket<false>::Continue(WorkQueue &);
template RT_API_ATTRS int DerivedAssignTicket<true>::Continue(WorkQueue &);
RT_OFFLOAD_API_GROUP_BEGIN
RT_API_ATTRS void DoFromSourceAssign(Descriptor &alloc,
const Descriptor &source, Terminator &terminator, MemmoveFct memmoveFct) {
@@ -759,6 +582,7 @@ void RTDEF(AssignTemporary)(Descriptor &to, const Descriptor &from,
}
}
}
Assign(to, from, terminator, MaybeReallocate | PolymorphicLHS);
}
@@ -775,6 +599,7 @@ void RTDEF(CopyInAssign)(Descriptor &temp, const Descriptor &var,
void RTDEF(CopyOutAssign)(
Descriptor *var, Descriptor &temp, const char *sourceFile, int sourceLine) {
Terminator terminator{sourceFile, sourceLine};
// Copyout from the temporary must not cause any finalizations
// for LHS. The variable must be properly initialized already.
if (var) {

View File

@@ -12,7 +12,6 @@
#include "flang-rt/runtime/terminator.h"
#include "flang-rt/runtime/tools.h"
#include "flang-rt/runtime/type-info.h"
#include "flang-rt/runtime/work-queue.h"
namespace Fortran::runtime {
@@ -31,193 +30,180 @@ static RT_API_ATTRS void GetComponentExtents(SubscriptValue (&extents)[maxRank],
}
RT_API_ATTRS int Initialize(const Descriptor &instance,
const typeInfo::DerivedType &derived, Terminator &terminator, bool,
const Descriptor *) {
WorkQueue workQueue{terminator};
int status{workQueue.BeginInitialize(instance, derived)};
return status == StatContinue ? workQueue.Run() : status;
}
RT_API_ATTRS int InitializeTicket::Begin(WorkQueue &) {
// Initialize procedure pointer components in each element
const Descriptor &procPtrDesc{derived_.procPtr()};
if (std::size_t numProcPtrs{procPtrDesc.Elements()}) {
bool noDataComponents{IsComplete()};
for (std::size_t k{0}; k < numProcPtrs; ++k) {
const auto &comp{
*procPtrDesc.ZeroBasedIndexedElement<typeInfo::ProcPtrComponent>(k)};
// Loop only over elements
if (noDataComponents) {
Elementwise::Reset();
}
for (; !Elementwise::IsComplete(); Elementwise::Advance()) {
auto &pptr{*instance_.ElementComponent<typeInfo::ProcedurePointer>(
subscripts_, comp.offset)};
pptr = comp.procInitialization;
}
}
if (noDataComponents) {
return StatOk;
}
Elementwise::Reset();
}
return StatContinue;
}
RT_API_ATTRS int InitializeTicket::Continue(WorkQueue &workQueue) {
while (!IsComplete()) {
if (component_->genre() == typeInfo::Component::Genre::Allocatable) {
// Establish allocatable descriptors
for (; !Elementwise::IsComplete(); Elementwise::Advance()) {
Descriptor &allocDesc{*instance_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
component_->EstablishDescriptor(
allocDesc, instance_, workQueue.terminator());
const typeInfo::DerivedType &derived, Terminator &terminator, bool hasStat,
const Descriptor *errMsg) {
const Descriptor &componentDesc{derived.component()};
std::size_t elements{instance.Elements()};
int stat{StatOk};
// Initialize data components in each element; the per-element iterations
// constitute the inner loops, not the outer ones
std::size_t myComponents{componentDesc.Elements()};
for (std::size_t k{0}; k < myComponents; ++k) {
const auto &comp{
*componentDesc.ZeroBasedIndexedElement<typeInfo::Component>(k)};
SubscriptValue at[maxRank];
instance.GetLowerBounds(at);
if (comp.genre() == typeInfo::Component::Genre::Allocatable ||
comp.genre() == typeInfo::Component::Genre::Automatic) {
for (std::size_t j{0}; j++ < elements; instance.IncrementSubscripts(at)) {
Descriptor &allocDesc{
*instance.ElementComponent<Descriptor>(at, comp.offset())};
comp.EstablishDescriptor(allocDesc, instance, terminator);
allocDesc.raw().attribute = CFI_attribute_allocatable;
if (comp.genre() == typeInfo::Component::Genre::Automatic) {
stat = ReturnError(
terminator, allocDesc.Allocate(kNoAsyncObject), errMsg, hasStat);
if (stat == StatOk) {
if (const DescriptorAddendum * addendum{allocDesc.Addendum()}) {
if (const auto *derived{addendum->derivedType()}) {
if (!derived->noInitializationNeeded()) {
stat = Initialize(
allocDesc, *derived, terminator, hasStat, errMsg);
}
}
}
}
if (stat != StatOk) {
break;
}
}
}
SkipToNextComponent();
} else if (const void *init{component_->initialization()}) {
} else if (const void *init{comp.initialization()}) {
// Explicit initialization of data pointers and
// non-allocatable non-automatic components
std::size_t bytes{component_->SizeInBytes(instance_)};
for (; !Elementwise::IsComplete(); Elementwise::Advance()) {
char *ptr{instance_.ElementComponent<char>(
subscripts_, component_->offset())};
std::size_t bytes{comp.SizeInBytes(instance)};
for (std::size_t j{0}; j++ < elements; instance.IncrementSubscripts(at)) {
char *ptr{instance.ElementComponent<char>(at, comp.offset())};
std::memcpy(ptr, init, bytes);
}
SkipToNextComponent();
} else if (component_->genre() == typeInfo::Component::Genre::Pointer) {
} else if (comp.genre() == typeInfo::Component::Genre::Pointer) {
// Data pointers without explicit initialization are established
// so that they are valid right-hand side targets of pointer
// assignment statements.
for (; !Elementwise::IsComplete(); Elementwise::Advance()) {
Descriptor &ptrDesc{*instance_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
component_->EstablishDescriptor(
ptrDesc, instance_, workQueue.terminator());
for (std::size_t j{0}; j++ < elements; instance.IncrementSubscripts(at)) {
Descriptor &ptrDesc{
*instance.ElementComponent<Descriptor>(at, comp.offset())};
comp.EstablishDescriptor(ptrDesc, instance, terminator);
ptrDesc.raw().attribute = CFI_attribute_pointer;
}
SkipToNextComponent();
} else if (component_->genre() == typeInfo::Component::Genre::Data &&
component_->derivedType() &&
!component_->derivedType()->noInitializationNeeded()) {
} else if (comp.genre() == typeInfo::Component::Genre::Data &&
comp.derivedType() && !comp.derivedType()->noInitializationNeeded()) {
// Default initialization of non-pointer non-allocatable/automatic
// data component. Handles parent component's elements.
// data component. Handles parent component's elements. Recursive.
SubscriptValue extents[maxRank];
GetComponentExtents(extents, *component_, instance_);
Descriptor &compDesc{componentDescriptor_.descriptor()};
const typeInfo::DerivedType &compType{*component_->derivedType()};
compDesc.Establish(compType,
instance_.ElementComponent<char>(subscripts_, component_->offset()),
component_->rank(), extents);
Advance();
if (int status{workQueue.BeginInitialize(compDesc, compType)};
status != StatOk) {
return status;
GetComponentExtents(extents, comp, instance);
StaticDescriptor<maxRank, true, 0> staticDescriptor;
Descriptor &compDesc{staticDescriptor.descriptor()};
const typeInfo::DerivedType &compType{*comp.derivedType()};
for (std::size_t j{0}; j++ < elements; instance.IncrementSubscripts(at)) {
compDesc.Establish(compType,
instance.ElementComponent<char>(at, comp.offset()), comp.rank(),
extents);
stat = Initialize(compDesc, compType, terminator, hasStat, errMsg);
if (stat != StatOk) {
break;
}
}
} else {
SkipToNextComponent();
}
}
return StatOk;
// Initialize procedure pointer components in each element
const Descriptor &procPtrDesc{derived.procPtr()};
std::size_t myProcPtrs{procPtrDesc.Elements()};
for (std::size_t k{0}; k < myProcPtrs; ++k) {
const auto &comp{
*procPtrDesc.ZeroBasedIndexedElement<typeInfo::ProcPtrComponent>(k)};
SubscriptValue at[maxRank];
instance.GetLowerBounds(at);
for (std::size_t j{0}; j++ < elements; instance.IncrementSubscripts(at)) {
auto &pptr{*instance.ElementComponent<typeInfo::ProcedurePointer>(
at, comp.offset)};
pptr = comp.procInitialization;
}
}
return stat;
}
RT_API_ATTRS int InitializeClone(const Descriptor &clone,
const Descriptor &original, const typeInfo::DerivedType &derived,
const Descriptor &orig, const typeInfo::DerivedType &derived,
Terminator &terminator, bool hasStat, const Descriptor *errMsg) {
if (original.IsPointer() || !original.IsAllocated()) {
return StatOk; // nothing to do
} else {
WorkQueue workQueue{terminator};
int status{workQueue.BeginInitializeClone(
clone, original, derived, hasStat, errMsg)};
return status == StatContinue ? workQueue.Run() : status;
}
}
const Descriptor &componentDesc{derived.component()};
std::size_t elements{orig.Elements()};
int stat{StatOk};
RT_API_ATTRS int InitializeCloneTicket::Continue(WorkQueue &workQueue) {
while (!IsComplete()) {
if (component_->genre() == typeInfo::Component::Genre::Allocatable) {
Descriptor &origDesc{*instance_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
if (origDesc.IsAllocated()) {
Descriptor &cloneDesc{*clone_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
if (phase_ == 0) {
++phase_;
// Skip pointers and unallocated variables.
if (orig.IsPointer() || !orig.IsAllocated()) {
return stat;
}
// Initialize each data component.
std::size_t components{componentDesc.Elements()};
for (std::size_t i{0}; i < components; ++i) {
const typeInfo::Component &comp{
*componentDesc.ZeroBasedIndexedElement<typeInfo::Component>(i)};
SubscriptValue at[maxRank];
orig.GetLowerBounds(at);
// Allocate allocatable components that are also allocated in the original
// object.
if (comp.genre() == typeInfo::Component::Genre::Allocatable) {
// Initialize each element.
for (std::size_t j{0}; j < elements; ++j, orig.IncrementSubscripts(at)) {
Descriptor &origDesc{
*orig.ElementComponent<Descriptor>(at, comp.offset())};
Descriptor &cloneDesc{
*clone.ElementComponent<Descriptor>(at, comp.offset())};
if (origDesc.IsAllocated()) {
cloneDesc.ApplyMold(origDesc, origDesc.rank());
if (int stat{ReturnError(workQueue.terminator(),
cloneDesc.Allocate(kNoAsyncObject), errMsg_, hasStat_)};
stat != StatOk) {
return stat;
}
if (const DescriptorAddendum *addendum{cloneDesc.Addendum()}) {
if (const typeInfo::DerivedType *derived{addendum->derivedType()}) {
if (!derived->noInitializationNeeded()) {
// Perform default initialization for the allocated element.
if (int status{workQueue.BeginInitialize(cloneDesc, *derived)};
status != StatOk) {
return status;
stat = ReturnError(
terminator, cloneDesc.Allocate(kNoAsyncObject), errMsg, hasStat);
if (stat == StatOk) {
if (const DescriptorAddendum * addendum{cloneDesc.Addendum()}) {
if (const typeInfo::DerivedType *
derived{addendum->derivedType()}) {
if (!derived->noInitializationNeeded()) {
// Perform default initialization for the allocated element.
stat = Initialize(
cloneDesc, *derived, terminator, hasStat, errMsg);
}
// Initialize derived type's allocatables.
if (stat == StatOk) {
stat = InitializeClone(cloneDesc, origDesc, *derived,
terminator, hasStat, errMsg);
}
}
}
}
}
if (phase_ == 1) {
++phase_;
if (const DescriptorAddendum *addendum{cloneDesc.Addendum()}) {
if (const typeInfo::DerivedType *derived{addendum->derivedType()}) {
// Initialize derived type's allocatables.
if (int status{workQueue.BeginInitializeClone(
cloneDesc, origDesc, *derived, hasStat_, errMsg_)};
status != StatOk) {
return status;
}
}
}
if (stat != StatOk) {
break;
}
}
Advance();
} else if (component_->genre() == typeInfo::Component::Genre::Data) {
if (component_->derivedType()) {
// Handle nested derived types.
const typeInfo::DerivedType &compType{*component_->derivedType()};
SubscriptValue extents[maxRank];
GetComponentExtents(extents, *component_, instance_);
Descriptor &origDesc{componentDescriptor_.descriptor()};
Descriptor &cloneDesc{cloneComponentDescriptor_.descriptor()};
} else if (comp.genre() == typeInfo::Component::Genre::Data &&
comp.derivedType()) {
// Handle nested derived types.
const typeInfo::DerivedType &compType{*comp.derivedType()};
SubscriptValue extents[maxRank];
GetComponentExtents(extents, comp, orig);
// Data components don't have descriptors, allocate them.
StaticDescriptor<maxRank, true, 0> origStaticDesc;
StaticDescriptor<maxRank, true, 0> cloneStaticDesc;
Descriptor &origDesc{origStaticDesc.descriptor()};
Descriptor &cloneDesc{cloneStaticDesc.descriptor()};
// Initialize each element.
for (std::size_t j{0}; j < elements; ++j, orig.IncrementSubscripts(at)) {
origDesc.Establish(compType,
instance_.ElementComponent<char>(subscripts_, component_->offset()),
component_->rank(), extents);
orig.ElementComponent<char>(at, comp.offset()), comp.rank(),
extents);
cloneDesc.Establish(compType,
clone_.ElementComponent<char>(subscripts_, component_->offset()),
component_->rank(), extents);
Advance();
if (int status{workQueue.BeginInitializeClone(
cloneDesc, origDesc, compType, hasStat_, errMsg_)};
status != StatOk) {
return status;
clone.ElementComponent<char>(at, comp.offset()), comp.rank(),
extents);
stat = InitializeClone(
cloneDesc, origDesc, compType, terminator, hasStat, errMsg);
if (stat != StatOk) {
break;
}
} else {
SkipToNextComponent();
}
} else {
SkipToNextComponent();
}
}
return StatOk;
}
// Fortran 2018 subclause 7.5.6.2
RT_API_ATTRS void Finalize(const Descriptor &descriptor,
const typeInfo::DerivedType &derived, Terminator *terminator) {
if (!derived.noFinalizationNeeded() && descriptor.IsAllocated()) {
Terminator stubTerminator{"Finalize() in Fortran runtime", 0};
WorkQueue workQueue{terminator ? *terminator : stubTerminator};
if (workQueue.BeginFinalize(descriptor, derived) == StatContinue) {
workQueue.Run();
}
}
return stat;
}
static RT_API_ATTRS const typeInfo::SpecialBinding *FindFinal(
@@ -235,7 +221,7 @@ static RT_API_ATTRS const typeInfo::SpecialBinding *FindFinal(
}
static RT_API_ATTRS void CallFinalSubroutine(const Descriptor &descriptor,
const typeInfo::DerivedType &derived, Terminator &terminator) {
const typeInfo::DerivedType &derived, Terminator *terminator) {
if (const auto *special{FindFinal(derived, descriptor.rank())}) {
if (special->which() == typeInfo::SpecialBinding::Which::ElementalFinal) {
std::size_t elements{descriptor.Elements()};
@@ -272,7 +258,9 @@ static RT_API_ATTRS void CallFinalSubroutine(const Descriptor &descriptor,
copy = descriptor;
copy.set_base_addr(nullptr);
copy.raw().attribute = CFI_attribute_allocatable;
RUNTIME_CHECK(terminator, copy.Allocate(kNoAsyncObject) == CFI_SUCCESS);
Terminator stubTerminator{"CallFinalProcedure() in Fortran runtime", 0};
RUNTIME_CHECK(terminator ? *terminator : stubTerminator,
copy.Allocate(kNoAsyncObject) == CFI_SUCCESS);
ShallowCopyDiscontiguousToContiguous(copy, descriptor);
argDescriptor = &copy;
}
@@ -296,94 +284,87 @@ static RT_API_ATTRS void CallFinalSubroutine(const Descriptor &descriptor,
}
}
RT_API_ATTRS int FinalizeTicket::Begin(WorkQueue &workQueue) {
CallFinalSubroutine(instance_, derived_, workQueue.terminator());
// Fortran 2018 subclause 7.5.6.2
RT_API_ATTRS void Finalize(const Descriptor &descriptor,
const typeInfo::DerivedType &derived, Terminator *terminator) {
if (derived.noFinalizationNeeded() || !descriptor.IsAllocated()) {
return;
}
CallFinalSubroutine(descriptor, derived, terminator);
const auto *parentType{derived.GetParentType()};
bool recurse{parentType && !parentType->noFinalizationNeeded()};
// If there's a finalizable parent component, handle it last, as required
// by the Fortran standard (7.5.6.2), and do so recursively with the same
// descriptor so that the rank is preserved.
finalizableParentType_ = derived_.GetParentType();
if (finalizableParentType_) {
if (finalizableParentType_->noFinalizationNeeded()) {
finalizableParentType_ = nullptr;
} else {
SkipToNextComponent();
}
}
return StatContinue;
}
RT_API_ATTRS int FinalizeTicket::Continue(WorkQueue &workQueue) {
while (!IsComplete()) {
if (component_->genre() == typeInfo::Component::Genre::Allocatable &&
component_->category() == TypeCategory::Derived) {
const Descriptor &componentDesc{derived.component()};
std::size_t myComponents{componentDesc.Elements()};
std::size_t elements{descriptor.Elements()};
for (auto k{recurse ? std::size_t{1}
/* skip first component, it's the parent */
: 0};
k < myComponents; ++k) {
const auto &comp{
*componentDesc.ZeroBasedIndexedElement<typeInfo::Component>(k)};
SubscriptValue at[maxRank];
descriptor.GetLowerBounds(at);
if (comp.genre() == typeInfo::Component::Genre::Allocatable &&
comp.category() == TypeCategory::Derived) {
// Component may be polymorphic or unlimited polymorphic. Need to use the
// dynamic type to check whether finalization is needed.
const Descriptor &compDesc{*instance_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
Advance();
if (compDesc.IsAllocated()) {
if (const DescriptorAddendum *addendum{compDesc.Addendum()}) {
if (const typeInfo::DerivedType *compDynamicType{
addendum->derivedType()}) {
if (!compDynamicType->noFinalizationNeeded()) {
if (int status{
workQueue.BeginFinalize(compDesc, *compDynamicType)};
status != StatOk) {
return status;
for (std::size_t j{0}; j++ < elements;
descriptor.IncrementSubscripts(at)) {
const Descriptor &compDesc{
*descriptor.ElementComponent<Descriptor>(at, comp.offset())};
if (compDesc.IsAllocated()) {
if (const DescriptorAddendum * addendum{compDesc.Addendum()}) {
if (const typeInfo::DerivedType *
compDynamicType{addendum->derivedType()}) {
if (!compDynamicType->noFinalizationNeeded()) {
Finalize(compDesc, *compDynamicType, terminator);
}
}
}
}
}
} else if (component_->genre() == typeInfo::Component::Genre::Allocatable ||
component_->genre() == typeInfo::Component::Genre::Automatic) {
if (const typeInfo::DerivedType *compType{component_->derivedType()};
compType && !compType->noFinalizationNeeded()) {
const Descriptor &compDesc{*instance_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
Advance();
if (compDesc.IsAllocated()) {
if (int status{workQueue.BeginFinalize(compDesc, *compType)};
status != StatOk) {
return status;
} else if (comp.genre() == typeInfo::Component::Genre::Allocatable ||
comp.genre() == typeInfo::Component::Genre::Automatic) {
if (const typeInfo::DerivedType * compType{comp.derivedType()}) {
if (!compType->noFinalizationNeeded()) {
for (std::size_t j{0}; j++ < elements;
descriptor.IncrementSubscripts(at)) {
const Descriptor &compDesc{
*descriptor.ElementComponent<Descriptor>(at, comp.offset())};
if (compDesc.IsAllocated()) {
Finalize(compDesc, *compType, terminator);
}
}
}
} else {
SkipToNextComponent();
}
} else if (component_->genre() == typeInfo::Component::Genre::Data &&
component_->derivedType() &&
!component_->derivedType()->noFinalizationNeeded()) {
} else if (comp.genre() == typeInfo::Component::Genre::Data &&
comp.derivedType() && !comp.derivedType()->noFinalizationNeeded()) {
SubscriptValue extents[maxRank];
GetComponentExtents(extents, *component_, instance_);
Descriptor &compDesc{componentDescriptor_.descriptor()};
const typeInfo::DerivedType &compType{*component_->derivedType()};
compDesc.Establish(compType,
instance_.ElementComponent<char>(subscripts_, component_->offset()),
component_->rank(), extents);
Advance();
if (int status{workQueue.BeginFinalize(compDesc, compType)};
status != StatOk) {
return status;
GetComponentExtents(extents, comp, descriptor);
StaticDescriptor<maxRank, true, 0> staticDescriptor;
Descriptor &compDesc{staticDescriptor.descriptor()};
const typeInfo::DerivedType &compType{*comp.derivedType()};
for (std::size_t j{0}; j++ < elements;
descriptor.IncrementSubscripts(at)) {
compDesc.Establish(compType,
descriptor.ElementComponent<char>(at, comp.offset()), comp.rank(),
extents);
Finalize(compDesc, compType, terminator);
}
} else {
SkipToNextComponent();
}
}
// Last, do the parent component, if any and finalizable.
if (finalizableParentType_) {
Descriptor &tmpDesc{componentDescriptor_.descriptor()};
tmpDesc = instance_;
if (recurse) {
StaticDescriptor<maxRank, true, 8 /*?*/> statDesc;
Descriptor &tmpDesc{statDesc.descriptor()};
tmpDesc = descriptor;
tmpDesc.raw().attribute = CFI_attribute_pointer;
tmpDesc.Addendum()->set_derivedType(finalizableParentType_);
tmpDesc.raw().elem_len = finalizableParentType_->sizeInBytes();
const auto &parentType{*finalizableParentType_};
finalizableParentType_ = nullptr;
// Don't return StatOk here if the nested FInalize is still running;
// it needs this->componentDescriptor_.
return workQueue.BeginFinalize(tmpDesc, parentType);
tmpDesc.Addendum()->set_derivedType(parentType);
tmpDesc.raw().elem_len = parentType->sizeInBytes();
Finalize(tmpDesc, *parentType, terminator);
}
return StatOk;
}
// The order of finalization follows Fortran 2018 7.5.6.2, with
@@ -392,71 +373,51 @@ RT_API_ATTRS int FinalizeTicket::Continue(WorkQueue &workQueue) {
// preceding any deallocation.
RT_API_ATTRS void Destroy(const Descriptor &descriptor, bool finalize,
const typeInfo::DerivedType &derived, Terminator *terminator) {
if (!derived.noFinalizationNeeded() && descriptor.IsAllocated()) {
Terminator stubTerminator{"Destroy() in Fortran runtime", 0};
WorkQueue workQueue{terminator ? *terminator : stubTerminator};
if (workQueue.BeginDestroy(descriptor, derived, finalize) == StatContinue) {
workQueue.Run();
}
if (derived.noDestructionNeeded() || !descriptor.IsAllocated()) {
return;
}
}
RT_API_ATTRS int DestroyTicket::Begin(WorkQueue &workQueue) {
if (finalize_ && !derived_.noFinalizationNeeded()) {
if (int status{workQueue.BeginFinalize(instance_, derived_)};
status != StatOk && status != StatContinue) {
return status;
}
if (finalize && !derived.noFinalizationNeeded()) {
Finalize(descriptor, derived, terminator);
}
return StatContinue;
}
RT_API_ATTRS int DestroyTicket::Continue(WorkQueue &workQueue) {
// Deallocate all direct and indirect allocatable and automatic components.
// Contrary to finalization, the order of deallocation does not matter.
while (!IsComplete()) {
const auto *componentDerived{component_->derivedType()};
if (component_->genre() == typeInfo::Component::Genre::Allocatable ||
component_->genre() == typeInfo::Component::Genre::Automatic) {
Descriptor *d{instance_.ElementComponent<Descriptor>(
subscripts_, component_->offset())};
if (d->IsAllocated()) {
if (phase_ == 0) {
++phase_;
if (componentDerived && !componentDerived->noDestructionNeeded()) {
if (int status{workQueue.BeginDestroy(
*d, *componentDerived, /*finalize=*/false)};
status != StatOk) {
return status;
}
}
const Descriptor &componentDesc{derived.component()};
std::size_t myComponents{componentDesc.Elements()};
std::size_t elements{descriptor.Elements()};
SubscriptValue at[maxRank];
descriptor.GetLowerBounds(at);
for (std::size_t k{0}; k < myComponents; ++k) {
const auto &comp{
*componentDesc.ZeroBasedIndexedElement<typeInfo::Component>(k)};
const bool destroyComp{
comp.derivedType() && !comp.derivedType()->noDestructionNeeded()};
if (comp.genre() == typeInfo::Component::Genre::Allocatable ||
comp.genre() == typeInfo::Component::Genre::Automatic) {
for (std::size_t j{0}; j < elements; ++j) {
Descriptor *d{
descriptor.ElementComponent<Descriptor>(at, comp.offset())};
if (destroyComp) {
Destroy(*d, /*finalize=*/false, *comp.derivedType(), terminator);
}
d->Deallocate();
descriptor.IncrementSubscripts(at);
}
Advance();
} else if (component_->genre() == typeInfo::Component::Genre::Data) {
if (!componentDerived || componentDerived->noDestructionNeeded()) {
SkipToNextComponent();
} else {
SubscriptValue extents[maxRank];
GetComponentExtents(extents, *component_, instance_);
Descriptor &compDesc{componentDescriptor_.descriptor()};
const typeInfo::DerivedType &compType{*componentDerived};
} else if (destroyComp &&
comp.genre() == typeInfo::Component::Genre::Data) {
SubscriptValue extents[maxRank];
GetComponentExtents(extents, comp, descriptor);
StaticDescriptor<maxRank, true, 0> staticDescriptor;
Descriptor &compDesc{staticDescriptor.descriptor()};
const typeInfo::DerivedType &compType{*comp.derivedType()};
for (std::size_t j{0}; j++ < elements;
descriptor.IncrementSubscripts(at)) {
compDesc.Establish(compType,
instance_.ElementComponent<char>(subscripts_, component_->offset()),
component_->rank(), extents);
Advance();
if (int status{workQueue.BeginDestroy(
compDesc, *componentDerived, /*finalize=*/false)};
status != StatOk) {
return status;
}
descriptor.ElementComponent<char>(at, comp.offset()), comp.rank(),
extents);
Destroy(compDesc, /*finalize=*/false, *comp.derivedType(), terminator);
}
} else {
SkipToNextComponent();
}
}
return StatOk;
}
RT_API_ATTRS bool HasDynamicComponent(const Descriptor &descriptor) {

View File

@@ -7,44 +7,15 @@
//===----------------------------------------------------------------------===//
#include "descriptor-io.h"
#include "edit-input.h"
#include "edit-output.h"
#include "unit.h"
#include "flang-rt/runtime/descriptor.h"
#include "flang-rt/runtime/io-stmt.h"
#include "flang-rt/runtime/namelist.h"
#include "flang-rt/runtime/terminator.h"
#include "flang-rt/runtime/type-info.h"
#include "flang-rt/runtime/work-queue.h"
#include "flang/Common/optional.h"
#include "flang/Common/restorer.h"
#include "flang/Common/uint128.h"
#include "flang/Runtime/cpp-type.h"
#include "flang/Runtime/freestanding-tools.h"
// Implementation of I/O data list item transfers based on descriptors.
// (All I/O items come through here so that the code is exercised for test;
// some scalar I/O data transfer APIs could be changed to bypass their use
// of descriptors in the future for better efficiency.)
namespace Fortran::runtime::io::descr {
RT_OFFLOAD_API_GROUP_BEGIN
template <typename A>
inline RT_API_ATTRS A &ExtractElement(IoStatementState &io,
const Descriptor &descriptor, const SubscriptValue subscripts[]) {
A *p{descriptor.Element<A>(subscripts)};
if (!p) {
io.GetIoErrorHandler().Crash("Bad address for I/O item -- null base "
"address or subscripts out of range");
}
return *p;
}
// Defined formatted I/O (maybe)
static RT_API_ATTRS Fortran::common::optional<bool> DefinedFormattedIo(
IoStatementState &io, const Descriptor &descriptor,
const typeInfo::DerivedType &derived,
Fortran::common::optional<bool> DefinedFormattedIo(IoStatementState &io,
const Descriptor &descriptor, const typeInfo::DerivedType &derived,
const typeInfo::SpecialBinding &special,
const SubscriptValue subscripts[]) {
Fortran::common::optional<DataEdit> peek{
@@ -133,8 +104,8 @@ static RT_API_ATTRS Fortran::common::optional<bool> DefinedFormattedIo(
}
// Defined unformatted I/O
static RT_API_ATTRS bool DefinedUnformattedIo(IoStatementState &io,
const Descriptor &descriptor, const typeInfo::DerivedType &derived,
bool DefinedUnformattedIo(IoStatementState &io, const Descriptor &descriptor,
const typeInfo::DerivedType &derived,
const typeInfo::SpecialBinding &special) {
// Unformatted I/O must have an external unit (or child thereof).
IoErrorHandler &handler{io.GetIoErrorHandler()};
@@ -181,619 +152,5 @@ static RT_API_ATTRS bool DefinedUnformattedIo(IoStatementState &io,
return handler.GetIoStat() == IostatOk;
}
// Per-category descriptor-based I/O templates
// TODO (perhaps as a nontrivial but small starter project): implement
// automatic repetition counts, like "10*3.14159", for list-directed and
// NAMELIST array output.
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedIntegerIO(IoStatementState &io,
const Descriptor &descriptor, [[maybe_unused]] bool isSigned) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
using IntType = CppTypeFor<common::TypeCategory::Integer, KIND>;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
if (auto edit{io.GetNextDataEdit()}) {
IntType &x{ExtractElement<IntType>(io, descriptor, subscripts)};
if constexpr (DIR == Direction::Output) {
if (!EditIntegerOutput<KIND>(io, *edit, x, isSigned)) {
return false;
}
} else if (edit->descriptor != DataEdit::ListDirectedNullValue) {
if (EditIntegerInput(
io, *edit, reinterpret_cast<void *>(&x), KIND, isSigned)) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedIntegerIO: subscripts out of bounds");
}
} else {
return false;
}
}
return true;
}
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedRealIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
using RawType = typename RealOutputEditing<KIND>::BinaryFloatingPoint;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
if (auto edit{io.GetNextDataEdit()}) {
RawType &x{ExtractElement<RawType>(io, descriptor, subscripts)};
if constexpr (DIR == Direction::Output) {
if (!RealOutputEditing<KIND>{io, x}.Edit(*edit)) {
return false;
}
} else if (edit->descriptor != DataEdit::ListDirectedNullValue) {
if (EditRealInput<KIND>(io, *edit, reinterpret_cast<void *>(&x))) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedRealIO: subscripts out of bounds");
}
} else {
return false;
}
}
return true;
}
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedComplexIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
bool isListOutput{
io.get_if<ListDirectedStatementState<Direction::Output>>() != nullptr};
using RawType = typename RealOutputEditing<KIND>::BinaryFloatingPoint;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
RawType *x{&ExtractElement<RawType>(io, descriptor, subscripts)};
if (isListOutput) {
DataEdit rEdit, iEdit;
rEdit.descriptor = DataEdit::ListDirectedRealPart;
iEdit.descriptor = DataEdit::ListDirectedImaginaryPart;
rEdit.modes = iEdit.modes = io.mutableModes();
if (!RealOutputEditing<KIND>{io, x[0]}.Edit(rEdit) ||
!RealOutputEditing<KIND>{io, x[1]}.Edit(iEdit)) {
return false;
}
} else {
for (int k{0}; k < 2; ++k, ++x) {
auto edit{io.GetNextDataEdit()};
if (!edit) {
return false;
} else if constexpr (DIR == Direction::Output) {
if (!RealOutputEditing<KIND>{io, *x}.Edit(*edit)) {
return false;
}
} else if (edit->descriptor == DataEdit::ListDirectedNullValue) {
break;
} else if (EditRealInput<KIND>(
io, *edit, reinterpret_cast<void *>(x))) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedComplexIO: subscripts out of bounds");
}
}
return true;
}
template <typename A, Direction DIR>
inline RT_API_ATTRS bool FormattedCharacterIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
std::size_t length{descriptor.ElementBytes() / sizeof(A)};
auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
A *x{&ExtractElement<A>(io, descriptor, subscripts)};
if (listOutput) {
if (!ListDirectedCharacterOutput(io, *listOutput, x, length)) {
return false;
}
} else if (auto edit{io.GetNextDataEdit()}) {
if constexpr (DIR == Direction::Output) {
if (!EditCharacterOutput(io, *edit, x, length)) {
return false;
}
} else { // input
if (edit->descriptor != DataEdit::ListDirectedNullValue) {
if (EditCharacterInput(io, *edit, x, length)) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
}
} else {
return false;
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedCharacterIO: subscripts out of bounds");
}
}
return true;
}
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedLogicalIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
using IntType = CppTypeFor<TypeCategory::Integer, KIND>;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
IntType &x{ExtractElement<IntType>(io, descriptor, subscripts)};
if (listOutput) {
if (!ListDirectedLogicalOutput(io, *listOutput, x != 0)) {
return false;
}
} else if (auto edit{io.GetNextDataEdit()}) {
if constexpr (DIR == Direction::Output) {
if (!EditLogicalOutput(io, *edit, x != 0)) {
return false;
}
} else {
if (edit->descriptor != DataEdit::ListDirectedNullValue) {
bool truth{};
if (EditLogicalInput(io, *edit, truth)) {
x = truth;
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
}
} else {
return false;
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedLogicalIO: subscripts out of bounds");
}
}
return true;
}
template <Direction DIR>
RT_API_ATTRS int DerivedIoTicket<DIR>::Continue(WorkQueue &workQueue) {
while (!IsComplete()) {
if (component_->genre() == typeInfo::Component::Genre::Data) {
// Create a descriptor for the component
Descriptor &compDesc{componentDescriptor_.descriptor()};
component_->CreatePointerDescriptor(
compDesc, instance_, io_.GetIoErrorHandler(), subscripts_);
Advance();
if (int status{workQueue.BeginDescriptorIo<DIR>(
io_, compDesc, table_, anyIoTookPlace_)};
status != StatOk) {
return status;
}
} else {
// Component is itself a descriptor
char *pointer{
instance_.Element<char>(subscripts_) + component_->offset()};
const Descriptor &compDesc{
*reinterpret_cast<const Descriptor *>(pointer)};
Advance();
if (compDesc.IsAllocated()) {
if (int status{workQueue.BeginDescriptorIo<DIR>(
io_, compDesc, table_, anyIoTookPlace_)};
status != StatOk) {
return status;
}
}
}
}
return StatOk;
}
template RT_API_ATTRS int DerivedIoTicket<Direction::Output>::Continue(
WorkQueue &);
template RT_API_ATTRS int DerivedIoTicket<Direction::Input>::Continue(
WorkQueue &);
template <Direction DIR>
RT_API_ATTRS int DescriptorIoTicket<DIR>::Begin(WorkQueue &workQueue) {
IoErrorHandler &handler{io_.GetIoErrorHandler()};
if (handler.InError()) {
return handler.GetIoStat();
}
if (!io_.get_if<IoDirectionState<DIR>>()) {
handler.Crash("DescriptorIO() called for wrong I/O direction");
return handler.GetIoStat();
}
if constexpr (DIR == Direction::Input) {
if (!io_.BeginReadingRecord()) {
return StatOk;
}
}
if (!io_.get_if<FormattedIoStatementState<DIR>>()) {
// Unformatted I/O
IoErrorHandler &handler{io_.GetIoErrorHandler()};
const DescriptorAddendum *addendum{instance_.Addendum()};
if (const typeInfo::DerivedType *type{
addendum ? addendum->derivedType() : nullptr}) {
// derived type unformatted I/O
if (table_) {
if (const auto *definedIo{table_->Find(*type,
DIR == Direction::Input
? common::DefinedIo::ReadUnformatted
: common::DefinedIo::WriteUnformatted)}) {
if (definedIo->subroutine) {
typeInfo::SpecialBinding special{DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadUnformatted
: typeInfo::SpecialBinding::Which::WriteUnformatted,
definedIo->subroutine, definedIo->isDtvArgPolymorphic, false,
false};
if (DefinedUnformattedIo(io_, instance_, *type, special)) {
anyIoTookPlace_ = true;
return StatOk;
}
} else {
int status{workQueue.BeginDerivedIo<DIR>(
io_, instance_, *type, table_, anyIoTookPlace_)};
return status == StatContinue ? StatOk : status; // done here
}
}
}
if (const typeInfo::SpecialBinding *special{
type->FindSpecialBinding(DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadUnformatted
: typeInfo::SpecialBinding::Which::WriteUnformatted)}) {
if (!table_ || !table_->ignoreNonTbpEntries || special->isTypeBound()) {
// defined derived type unformatted I/O
if (DefinedUnformattedIo(io_, instance_, *type, *special)) {
anyIoTookPlace_ = true;
return StatOk;
} else {
return IostatEnd;
}
}
}
// Default derived type unformatted I/O
// TODO: If no component at any level has defined READ or WRITE
// (as appropriate), the elements are contiguous, and no byte swapping
// is active, do a block transfer via the code below.
int status{workQueue.BeginDerivedIo<DIR>(
io_, instance_, *type, table_, anyIoTookPlace_)};
return status == StatContinue ? StatOk : status; // done here
} else {
// intrinsic type unformatted I/O
auto *externalUnf{io_.get_if<ExternalUnformattedIoStatementState<DIR>>()};
ChildUnformattedIoStatementState<DIR> *childUnf{nullptr};
InquireIOLengthState *inq{nullptr};
bool swapEndianness{false};
if (externalUnf) {
swapEndianness = externalUnf->unit().swapEndianness();
} else {
childUnf = io_.get_if<ChildUnformattedIoStatementState<DIR>>();
if (!childUnf) {
inq = DIR == Direction::Output ? io_.get_if<InquireIOLengthState>()
: nullptr;
RUNTIME_CHECK(handler, inq != nullptr);
}
}
std::size_t elementBytes{instance_.ElementBytes()};
std::size_t swappingBytes{elementBytes};
if (auto maybeCatAndKind{instance_.type().GetCategoryAndKind()}) {
// Byte swapping units can be smaller than elements, namely
// for COMPLEX and CHARACTER.
if (maybeCatAndKind->first == TypeCategory::Character) {
// swap each character position independently
swappingBytes = maybeCatAndKind->second; // kind
} else if (maybeCatAndKind->first == TypeCategory::Complex) {
// swap real and imaginary components independently
swappingBytes /= 2;
}
}
using CharType =
std::conditional_t<DIR == Direction::Output, const char, char>;
auto Transfer{[=](CharType &x, std::size_t totalBytes) -> bool {
if constexpr (DIR == Direction::Output) {
return externalUnf ? externalUnf->Emit(&x, totalBytes, swappingBytes)
: childUnf ? childUnf->Emit(&x, totalBytes, swappingBytes)
: inq->Emit(&x, totalBytes, swappingBytes);
} else {
return externalUnf
? externalUnf->Receive(&x, totalBytes, swappingBytes)
: childUnf->Receive(&x, totalBytes, swappingBytes);
}
}};
if (!swapEndianness &&
instance_.IsContiguous()) { // contiguous unformatted I/O
char &x{ExtractElement<char>(io_, instance_, subscripts_)};
if (Transfer(x, elements_ * elementBytes)) {
anyIoTookPlace_ = true;
} else {
return IostatEnd;
}
} else { // non-contiguous or byte-swapped intrinsic type unformatted I/O
for (; !IsComplete(); Advance()) {
char &x{ExtractElement<char>(io_, instance_, subscripts_)};
if (Transfer(x, elementBytes)) {
anyIoTookPlace_ = true;
} else {
return IostatEnd;
}
}
}
}
// Unformatted I/O never needs to call Continue().
return StatOk;
}
// Formatted I/O
if (auto catAndKind{instance_.type().GetCategoryAndKind()}) {
TypeCategory cat{catAndKind->first};
int kind{catAndKind->second};
bool any{false};
switch (cat) {
case TypeCategory::Integer:
switch (kind) {
case 1:
any = FormattedIntegerIO<1, DIR>(io_, instance_, true);
break;
case 2:
any = FormattedIntegerIO<2, DIR>(io_, instance_, true);
break;
case 4:
any = FormattedIntegerIO<4, DIR>(io_, instance_, true);
break;
case 8:
any = FormattedIntegerIO<8, DIR>(io_, instance_, true);
break;
case 16:
any = FormattedIntegerIO<16, DIR>(io_, instance_, true);
break;
default:
handler.Crash(
"not yet implemented: INTEGER(KIND=%d) in formatted IO", kind);
return IostatEnd;
}
break;
case TypeCategory::Unsigned:
switch (kind) {
case 1:
any = FormattedIntegerIO<1, DIR>(io_, instance_, false);
break;
case 2:
any = FormattedIntegerIO<2, DIR>(io_, instance_, false);
break;
case 4:
any = FormattedIntegerIO<4, DIR>(io_, instance_, false);
break;
case 8:
any = FormattedIntegerIO<8, DIR>(io_, instance_, false);
break;
case 16:
any = FormattedIntegerIO<16, DIR>(io_, instance_, false);
break;
default:
handler.Crash(
"not yet implemented: UNSIGNED(KIND=%d) in formatted IO", kind);
return IostatEnd;
}
break;
case TypeCategory::Real:
switch (kind) {
case 2:
any = FormattedRealIO<2, DIR>(io_, instance_);
break;
case 3:
any = FormattedRealIO<3, DIR>(io_, instance_);
break;
case 4:
any = FormattedRealIO<4, DIR>(io_, instance_);
break;
case 8:
any = FormattedRealIO<8, DIR>(io_, instance_);
break;
case 10:
any = FormattedRealIO<10, DIR>(io_, instance_);
break;
// TODO: case double/double
case 16:
any = FormattedRealIO<16, DIR>(io_, instance_);
break;
default:
handler.Crash(
"not yet implemented: REAL(KIND=%d) in formatted IO", kind);
return IostatEnd;
}
break;
case TypeCategory::Complex:
switch (kind) {
case 2:
any = FormattedComplexIO<2, DIR>(io_, instance_);
break;
case 3:
any = FormattedComplexIO<3, DIR>(io_, instance_);
break;
case 4:
any = FormattedComplexIO<4, DIR>(io_, instance_);
break;
case 8:
any = FormattedComplexIO<8, DIR>(io_, instance_);
break;
case 10:
any = FormattedComplexIO<10, DIR>(io_, instance_);
break;
// TODO: case double/double
case 16:
any = FormattedComplexIO<16, DIR>(io_, instance_);
break;
default:
handler.Crash(
"not yet implemented: COMPLEX(KIND=%d) in formatted IO", kind);
return IostatEnd;
}
break;
case TypeCategory::Character:
switch (kind) {
case 1:
any = FormattedCharacterIO<char, DIR>(io_, instance_);
break;
case 2:
any = FormattedCharacterIO<char16_t, DIR>(io_, instance_);
break;
case 4:
any = FormattedCharacterIO<char32_t, DIR>(io_, instance_);
break;
default:
handler.Crash(
"not yet implemented: CHARACTER(KIND=%d) in formatted IO", kind);
return IostatEnd;
}
break;
case TypeCategory::Logical:
switch (kind) {
case 1:
any = FormattedLogicalIO<1, DIR>(io_, instance_);
break;
case 2:
any = FormattedLogicalIO<2, DIR>(io_, instance_);
break;
case 4:
any = FormattedLogicalIO<4, DIR>(io_, instance_);
break;
case 8:
any = FormattedLogicalIO<8, DIR>(io_, instance_);
break;
default:
handler.Crash(
"not yet implemented: LOGICAL(KIND=%d) in formatted IO", kind);
return IostatEnd;
}
break;
case TypeCategory::Derived: {
// Derived type information must be present for formatted I/O.
IoErrorHandler &handler{io_.GetIoErrorHandler()};
const DescriptorAddendum *addendum{instance_.Addendum()};
RUNTIME_CHECK(handler, addendum != nullptr);
derived_ = addendum->derivedType();
RUNTIME_CHECK(handler, derived_ != nullptr);
if (table_) {
if (const auto *definedIo{table_->Find(*derived_,
DIR == Direction::Input ? common::DefinedIo::ReadFormatted
: common::DefinedIo::WriteFormatted)}) {
if (definedIo->subroutine) {
nonTbpSpecial_.emplace(DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadFormatted
: typeInfo::SpecialBinding::Which::WriteFormatted,
definedIo->subroutine, definedIo->isDtvArgPolymorphic, false,
false);
special_ = &*nonTbpSpecial_;
}
}
}
if (!special_) {
if (const typeInfo::SpecialBinding *binding{
derived_->FindSpecialBinding(DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadFormatted
: typeInfo::SpecialBinding::Which::WriteFormatted)}) {
if (!table_ || !table_->ignoreNonTbpEntries ||
binding->isTypeBound()) {
special_ = binding;
}
}
}
return StatContinue;
}
}
if (any) {
anyIoTookPlace_ = true;
} else {
return IostatEnd;
}
} else {
handler.Crash("DescriptorIO: bad type code (%d) in descriptor",
static_cast<int>(instance_.type().raw()));
return handler.GetIoStat();
}
return StatOk;
}
template RT_API_ATTRS int DescriptorIoTicket<Direction::Output>::Begin(
WorkQueue &);
template RT_API_ATTRS int DescriptorIoTicket<Direction::Input>::Begin(
WorkQueue &);
template <Direction DIR>
RT_API_ATTRS int DescriptorIoTicket<DIR>::Continue(WorkQueue &workQueue) {
// Only derived type formatted I/O gets here.
while (!IsComplete()) {
if (special_) {
if (auto defined{DefinedFormattedIo(
io_, instance_, *derived_, *special_, subscripts_)}) {
anyIoTookPlace_ |= *defined;
Advance();
continue;
}
}
Descriptor &elementDesc{elementDescriptor_.descriptor()};
elementDesc.Establish(
*derived_, nullptr, 0, nullptr, CFI_attribute_pointer);
elementDesc.set_base_addr(instance_.Element<char>(subscripts_));
Advance();
if (int status{workQueue.BeginDerivedIo<DIR>(
io_, elementDesc, *derived_, table_, anyIoTookPlace_)};
status != StatOk) {
return status;
}
}
return StatOk;
}
template RT_API_ATTRS int DescriptorIoTicket<Direction::Output>::Continue(
WorkQueue &);
template RT_API_ATTRS int DescriptorIoTicket<Direction::Input>::Continue(
WorkQueue &);
template <Direction DIR>
RT_API_ATTRS bool DescriptorIO(IoStatementState &io,
const Descriptor &descriptor, const NonTbpDefinedIoTable *table) {
bool anyIoTookPlace{false};
WorkQueue workQueue{io.GetIoErrorHandler()};
if (workQueue.BeginDescriptorIo<DIR>(io, descriptor, table, anyIoTookPlace) ==
StatContinue) {
workQueue.Run();
}
return anyIoTookPlace;
}
template RT_API_ATTRS bool DescriptorIO<Direction::Output>(
IoStatementState &, const Descriptor &, const NonTbpDefinedIoTable *);
template RT_API_ATTRS bool DescriptorIO<Direction::Input>(
IoStatementState &, const Descriptor &, const NonTbpDefinedIoTable *);
RT_OFFLOAD_API_GROUP_END
} // namespace Fortran::runtime::io::descr

View File

@@ -9,27 +9,619 @@
#ifndef FLANG_RT_RUNTIME_DESCRIPTOR_IO_H_
#define FLANG_RT_RUNTIME_DESCRIPTOR_IO_H_
#include "flang-rt/runtime/connection.h"
// Implementation of I/O data list item transfers based on descriptors.
// (All I/O items come through here so that the code is exercised for test;
// some scalar I/O data transfer APIs could be changed to bypass their use
// of descriptors in the future for better efficiency.)
namespace Fortran::runtime {
class Descriptor;
} // namespace Fortran::runtime
namespace Fortran::runtime::io {
class IoStatementState;
struct NonTbpDefinedIoTable;
} // namespace Fortran::runtime::io
#include "edit-input.h"
#include "edit-output.h"
#include "unit.h"
#include "flang-rt/runtime/descriptor.h"
#include "flang-rt/runtime/io-stmt.h"
#include "flang-rt/runtime/namelist.h"
#include "flang-rt/runtime/terminator.h"
#include "flang-rt/runtime/type-info.h"
#include "flang/Common/optional.h"
#include "flang/Common/uint128.h"
#include "flang/Runtime/cpp-type.h"
namespace Fortran::runtime::io::descr {
template <typename A>
inline RT_API_ATTRS A &ExtractElement(IoStatementState &io,
const Descriptor &descriptor, const SubscriptValue subscripts[]) {
A *p{descriptor.Element<A>(subscripts)};
if (!p) {
io.GetIoErrorHandler().Crash("Bad address for I/O item -- null base "
"address or subscripts out of range");
}
return *p;
}
// Per-category descriptor-based I/O templates
// TODO (perhaps as a nontrivial but small starter project): implement
// automatic repetition counts, like "10*3.14159", for list-directed and
// NAMELIST array output.
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedIntegerIO(IoStatementState &io,
const Descriptor &descriptor, [[maybe_unused]] bool isSigned) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
using IntType = CppTypeFor<common::TypeCategory::Integer, KIND>;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
if (auto edit{io.GetNextDataEdit()}) {
IntType &x{ExtractElement<IntType>(io, descriptor, subscripts)};
if constexpr (DIR == Direction::Output) {
if (!EditIntegerOutput<KIND>(io, *edit, x, isSigned)) {
return false;
}
} else if (edit->descriptor != DataEdit::ListDirectedNullValue) {
if (EditIntegerInput(
io, *edit, reinterpret_cast<void *>(&x), KIND, isSigned)) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedIntegerIO: subscripts out of bounds");
}
} else {
return false;
}
}
return true;
}
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedRealIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
using RawType = typename RealOutputEditing<KIND>::BinaryFloatingPoint;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
if (auto edit{io.GetNextDataEdit()}) {
RawType &x{ExtractElement<RawType>(io, descriptor, subscripts)};
if constexpr (DIR == Direction::Output) {
if (!RealOutputEditing<KIND>{io, x}.Edit(*edit)) {
return false;
}
} else if (edit->descriptor != DataEdit::ListDirectedNullValue) {
if (EditRealInput<KIND>(io, *edit, reinterpret_cast<void *>(&x))) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedRealIO: subscripts out of bounds");
}
} else {
return false;
}
}
return true;
}
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedComplexIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
bool isListOutput{
io.get_if<ListDirectedStatementState<Direction::Output>>() != nullptr};
using RawType = typename RealOutputEditing<KIND>::BinaryFloatingPoint;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
RawType *x{&ExtractElement<RawType>(io, descriptor, subscripts)};
if (isListOutput) {
DataEdit rEdit, iEdit;
rEdit.descriptor = DataEdit::ListDirectedRealPart;
iEdit.descriptor = DataEdit::ListDirectedImaginaryPart;
rEdit.modes = iEdit.modes = io.mutableModes();
if (!RealOutputEditing<KIND>{io, x[0]}.Edit(rEdit) ||
!RealOutputEditing<KIND>{io, x[1]}.Edit(iEdit)) {
return false;
}
} else {
for (int k{0}; k < 2; ++k, ++x) {
auto edit{io.GetNextDataEdit()};
if (!edit) {
return false;
} else if constexpr (DIR == Direction::Output) {
if (!RealOutputEditing<KIND>{io, *x}.Edit(*edit)) {
return false;
}
} else if (edit->descriptor == DataEdit::ListDirectedNullValue) {
break;
} else if (EditRealInput<KIND>(
io, *edit, reinterpret_cast<void *>(x))) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedComplexIO: subscripts out of bounds");
}
}
return true;
}
template <typename A, Direction DIR>
inline RT_API_ATTRS bool FormattedCharacterIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
std::size_t length{descriptor.ElementBytes() / sizeof(A)};
auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
A *x{&ExtractElement<A>(io, descriptor, subscripts)};
if (listOutput) {
if (!ListDirectedCharacterOutput(io, *listOutput, x, length)) {
return false;
}
} else if (auto edit{io.GetNextDataEdit()}) {
if constexpr (DIR == Direction::Output) {
if (!EditCharacterOutput(io, *edit, x, length)) {
return false;
}
} else { // input
if (edit->descriptor != DataEdit::ListDirectedNullValue) {
if (EditCharacterInput(io, *edit, x, length)) {
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
}
} else {
return false;
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedCharacterIO: subscripts out of bounds");
}
}
return true;
}
template <int KIND, Direction DIR>
inline RT_API_ATTRS bool FormattedLogicalIO(
IoStatementState &io, const Descriptor &descriptor) {
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
auto *listOutput{io.get_if<ListDirectedStatementState<Direction::Output>>()};
using IntType = CppTypeFor<TypeCategory::Integer, KIND>;
bool anyInput{false};
for (std::size_t j{0}; j < numElements; ++j) {
IntType &x{ExtractElement<IntType>(io, descriptor, subscripts)};
if (listOutput) {
if (!ListDirectedLogicalOutput(io, *listOutput, x != 0)) {
return false;
}
} else if (auto edit{io.GetNextDataEdit()}) {
if constexpr (DIR == Direction::Output) {
if (!EditLogicalOutput(io, *edit, x != 0)) {
return false;
}
} else {
if (edit->descriptor != DataEdit::ListDirectedNullValue) {
bool truth{};
if (EditLogicalInput(io, *edit, truth)) {
x = truth;
anyInput = true;
} else {
return anyInput && edit->IsNamelist();
}
}
}
} else {
return false;
}
if (!descriptor.IncrementSubscripts(subscripts) && j + 1 < numElements) {
io.GetIoErrorHandler().Crash(
"FormattedLogicalIO: subscripts out of bounds");
}
}
return true;
}
template <Direction DIR>
RT_API_ATTRS bool DescriptorIO(IoStatementState &, const Descriptor &,
static RT_API_ATTRS bool DescriptorIO(IoStatementState &, const Descriptor &,
const NonTbpDefinedIoTable * = nullptr);
extern template RT_API_ATTRS bool DescriptorIO<Direction::Output>(
IoStatementState &, const Descriptor &, const NonTbpDefinedIoTable *);
extern template RT_API_ATTRS bool DescriptorIO<Direction::Input>(
IoStatementState &, const Descriptor &, const NonTbpDefinedIoTable *);
// For intrinsic (not defined) derived type I/O, formatted & unformatted
template <Direction DIR>
static RT_API_ATTRS bool DefaultComponentIO(IoStatementState &io,
const typeInfo::Component &component, const Descriptor &origDescriptor,
const SubscriptValue origSubscripts[], Terminator &terminator,
const NonTbpDefinedIoTable *table) {
#if !defined(RT_DEVICE_AVOID_RECURSION)
if (component.genre() == typeInfo::Component::Genre::Data) {
// Create a descriptor for the component
StaticDescriptor<maxRank, true, 16 /*?*/> statDesc;
Descriptor &desc{statDesc.descriptor()};
component.CreatePointerDescriptor(
desc, origDescriptor, terminator, origSubscripts);
return DescriptorIO<DIR>(io, desc, table);
} else {
// Component is itself a descriptor
char *pointer{
origDescriptor.Element<char>(origSubscripts) + component.offset()};
const Descriptor &compDesc{*reinterpret_cast<const Descriptor *>(pointer)};
return compDesc.IsAllocated() && DescriptorIO<DIR>(io, compDesc, table);
}
#else
terminator.Crash("not yet implemented: component IO");
#endif
}
template <Direction DIR>
static RT_API_ATTRS bool DefaultComponentwiseFormattedIO(IoStatementState &io,
const Descriptor &descriptor, const typeInfo::DerivedType &type,
const NonTbpDefinedIoTable *table, const SubscriptValue subscripts[]) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
const Descriptor &compArray{type.component()};
RUNTIME_CHECK(handler, compArray.rank() == 1);
std::size_t numComponents{compArray.Elements()};
SubscriptValue at[maxRank];
compArray.GetLowerBounds(at);
for (std::size_t k{0}; k < numComponents;
++k, compArray.IncrementSubscripts(at)) {
const typeInfo::Component &component{
*compArray.Element<typeInfo::Component>(at)};
if (!DefaultComponentIO<DIR>(
io, component, descriptor, subscripts, handler, table)) {
// Return true for NAMELIST input if any component appeared.
auto *listInput{
io.get_if<ListDirectedStatementState<Direction::Input>>()};
return DIR == Direction::Input && k > 0 && listInput &&
listInput->inNamelistSequence();
}
}
return true;
}
template <Direction DIR>
static RT_API_ATTRS bool DefaultComponentwiseUnformattedIO(IoStatementState &io,
const Descriptor &descriptor, const typeInfo::DerivedType &type,
const NonTbpDefinedIoTable *table) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
const Descriptor &compArray{type.component()};
RUNTIME_CHECK(handler, compArray.rank() == 1);
std::size_t numComponents{compArray.Elements()};
std::size_t numElements{descriptor.Elements()};
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
for (std::size_t j{0}; j < numElements;
++j, descriptor.IncrementSubscripts(subscripts)) {
SubscriptValue at[maxRank];
compArray.GetLowerBounds(at);
for (std::size_t k{0}; k < numComponents;
++k, compArray.IncrementSubscripts(at)) {
const typeInfo::Component &component{
*compArray.Element<typeInfo::Component>(at)};
if (!DefaultComponentIO<DIR>(
io, component, descriptor, subscripts, handler, table)) {
return false;
}
}
}
return true;
}
RT_API_ATTRS Fortran::common::optional<bool> DefinedFormattedIo(
IoStatementState &, const Descriptor &, const typeInfo::DerivedType &,
const typeInfo::SpecialBinding &, const SubscriptValue[]);
template <Direction DIR>
static RT_API_ATTRS bool FormattedDerivedTypeIO(IoStatementState &io,
const Descriptor &descriptor, const NonTbpDefinedIoTable *table) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
// Derived type information must be present for formatted I/O.
const DescriptorAddendum *addendum{descriptor.Addendum()};
RUNTIME_CHECK(handler, addendum != nullptr);
const typeInfo::DerivedType *type{addendum->derivedType()};
RUNTIME_CHECK(handler, type != nullptr);
Fortran::common::optional<typeInfo::SpecialBinding> nonTbpSpecial;
const typeInfo::SpecialBinding *special{nullptr};
if (table) {
if (const auto *definedIo{table->Find(*type,
DIR == Direction::Input ? common::DefinedIo::ReadFormatted
: common::DefinedIo::WriteFormatted)}) {
if (definedIo->subroutine) {
nonTbpSpecial.emplace(DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadFormatted
: typeInfo::SpecialBinding::Which::WriteFormatted,
definedIo->subroutine, definedIo->isDtvArgPolymorphic, false,
false);
special = &*nonTbpSpecial;
}
}
}
if (!special) {
if (const typeInfo::SpecialBinding *
binding{type->FindSpecialBinding(DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadFormatted
: typeInfo::SpecialBinding::Which::WriteFormatted)}) {
if (!table || !table->ignoreNonTbpEntries || binding->isTypeBound()) {
special = binding;
}
}
}
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
std::size_t numElements{descriptor.Elements()};
for (std::size_t j{0}; j < numElements;
++j, descriptor.IncrementSubscripts(subscripts)) {
Fortran::common::optional<bool> result;
if (special) {
result = DefinedFormattedIo(io, descriptor, *type, *special, subscripts);
}
if (!result) {
result = DefaultComponentwiseFormattedIO<DIR>(
io, descriptor, *type, table, subscripts);
}
if (!result.value()) {
// Return true for NAMELIST input if we got anything.
auto *listInput{
io.get_if<ListDirectedStatementState<Direction::Input>>()};
return DIR == Direction::Input && j > 0 && listInput &&
listInput->inNamelistSequence();
}
}
return true;
}
RT_API_ATTRS bool DefinedUnformattedIo(IoStatementState &, const Descriptor &,
const typeInfo::DerivedType &, const typeInfo::SpecialBinding &);
// Unformatted I/O
template <Direction DIR>
static RT_API_ATTRS bool UnformattedDescriptorIO(IoStatementState &io,
const Descriptor &descriptor, const NonTbpDefinedIoTable *table = nullptr) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
const DescriptorAddendum *addendum{descriptor.Addendum()};
if (const typeInfo::DerivedType *
type{addendum ? addendum->derivedType() : nullptr}) {
// derived type unformatted I/O
if (table) {
if (const auto *definedIo{table->Find(*type,
DIR == Direction::Input ? common::DefinedIo::ReadUnformatted
: common::DefinedIo::WriteUnformatted)}) {
if (definedIo->subroutine) {
typeInfo::SpecialBinding special{DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadUnformatted
: typeInfo::SpecialBinding::Which::WriteUnformatted,
definedIo->subroutine, definedIo->isDtvArgPolymorphic, false,
false};
if (Fortran::common::optional<bool> wasDefined{
DefinedUnformattedIo(io, descriptor, *type, special)}) {
return *wasDefined;
}
} else {
return DefaultComponentwiseUnformattedIO<DIR>(
io, descriptor, *type, table);
}
}
}
if (const typeInfo::SpecialBinding *
special{type->FindSpecialBinding(DIR == Direction::Input
? typeInfo::SpecialBinding::Which::ReadUnformatted
: typeInfo::SpecialBinding::Which::WriteUnformatted)}) {
if (!table || !table->ignoreNonTbpEntries || special->isTypeBound()) {
// defined derived type unformatted I/O
return DefinedUnformattedIo(io, descriptor, *type, *special);
}
}
// Default derived type unformatted I/O
// TODO: If no component at any level has defined READ or WRITE
// (as appropriate), the elements are contiguous, and no byte swapping
// is active, do a block transfer via the code below.
return DefaultComponentwiseUnformattedIO<DIR>(io, descriptor, *type, table);
} else {
// intrinsic type unformatted I/O
auto *externalUnf{io.get_if<ExternalUnformattedIoStatementState<DIR>>()};
auto *childUnf{io.get_if<ChildUnformattedIoStatementState<DIR>>()};
auto *inq{
DIR == Direction::Output ? io.get_if<InquireIOLengthState>() : nullptr};
RUNTIME_CHECK(handler, externalUnf || childUnf || inq);
std::size_t elementBytes{descriptor.ElementBytes()};
std::size_t numElements{descriptor.Elements()};
std::size_t swappingBytes{elementBytes};
if (auto maybeCatAndKind{descriptor.type().GetCategoryAndKind()}) {
// Byte swapping units can be smaller than elements, namely
// for COMPLEX and CHARACTER.
if (maybeCatAndKind->first == TypeCategory::Character) {
// swap each character position independently
swappingBytes = maybeCatAndKind->second; // kind
} else if (maybeCatAndKind->first == TypeCategory::Complex) {
// swap real and imaginary components independently
swappingBytes /= 2;
}
}
SubscriptValue subscripts[maxRank];
descriptor.GetLowerBounds(subscripts);
using CharType =
std::conditional_t<DIR == Direction::Output, const char, char>;
auto Transfer{[=](CharType &x, std::size_t totalBytes) -> bool {
if constexpr (DIR == Direction::Output) {
return externalUnf ? externalUnf->Emit(&x, totalBytes, swappingBytes)
: childUnf ? childUnf->Emit(&x, totalBytes, swappingBytes)
: inq->Emit(&x, totalBytes, swappingBytes);
} else {
return externalUnf ? externalUnf->Receive(&x, totalBytes, swappingBytes)
: childUnf->Receive(&x, totalBytes, swappingBytes);
}
}};
bool swapEndianness{externalUnf && externalUnf->unit().swapEndianness()};
if (!swapEndianness &&
descriptor.IsContiguous()) { // contiguous unformatted I/O
char &x{ExtractElement<char>(io, descriptor, subscripts)};
return Transfer(x, numElements * elementBytes);
} else { // non-contiguous or byte-swapped intrinsic type unformatted I/O
for (std::size_t j{0}; j < numElements; ++j) {
char &x{ExtractElement<char>(io, descriptor, subscripts)};
if (!Transfer(x, elementBytes)) {
return false;
}
if (!descriptor.IncrementSubscripts(subscripts) &&
j + 1 < numElements) {
handler.Crash("DescriptorIO: subscripts out of bounds");
}
}
return true;
}
}
}
template <Direction DIR>
static RT_API_ATTRS bool DescriptorIO(IoStatementState &io,
const Descriptor &descriptor, const NonTbpDefinedIoTable *table) {
IoErrorHandler &handler{io.GetIoErrorHandler()};
if (handler.InError()) {
return false;
}
if (!io.get_if<IoDirectionState<DIR>>()) {
handler.Crash("DescriptorIO() called for wrong I/O direction");
return false;
}
if constexpr (DIR == Direction::Input) {
if (!io.BeginReadingRecord()) {
return false;
}
}
if (!io.get_if<FormattedIoStatementState<DIR>>()) {
return UnformattedDescriptorIO<DIR>(io, descriptor, table);
}
if (auto catAndKind{descriptor.type().GetCategoryAndKind()}) {
TypeCategory cat{catAndKind->first};
int kind{catAndKind->second};
switch (cat) {
case TypeCategory::Integer:
switch (kind) {
case 1:
return FormattedIntegerIO<1, DIR>(io, descriptor, true);
case 2:
return FormattedIntegerIO<2, DIR>(io, descriptor, true);
case 4:
return FormattedIntegerIO<4, DIR>(io, descriptor, true);
case 8:
return FormattedIntegerIO<8, DIR>(io, descriptor, true);
case 16:
return FormattedIntegerIO<16, DIR>(io, descriptor, true);
default:
handler.Crash(
"not yet implemented: INTEGER(KIND=%d) in formatted IO", kind);
return false;
}
case TypeCategory::Unsigned:
switch (kind) {
case 1:
return FormattedIntegerIO<1, DIR>(io, descriptor, false);
case 2:
return FormattedIntegerIO<2, DIR>(io, descriptor, false);
case 4:
return FormattedIntegerIO<4, DIR>(io, descriptor, false);
case 8:
return FormattedIntegerIO<8, DIR>(io, descriptor, false);
case 16:
return FormattedIntegerIO<16, DIR>(io, descriptor, false);
default:
handler.Crash(
"not yet implemented: UNSIGNED(KIND=%d) in formatted IO", kind);
return false;
}
case TypeCategory::Real:
switch (kind) {
case 2:
return FormattedRealIO<2, DIR>(io, descriptor);
case 3:
return FormattedRealIO<3, DIR>(io, descriptor);
case 4:
return FormattedRealIO<4, DIR>(io, descriptor);
case 8:
return FormattedRealIO<8, DIR>(io, descriptor);
case 10:
return FormattedRealIO<10, DIR>(io, descriptor);
// TODO: case double/double
case 16:
return FormattedRealIO<16, DIR>(io, descriptor);
default:
handler.Crash(
"not yet implemented: REAL(KIND=%d) in formatted IO", kind);
return false;
}
case TypeCategory::Complex:
switch (kind) {
case 2:
return FormattedComplexIO<2, DIR>(io, descriptor);
case 3:
return FormattedComplexIO<3, DIR>(io, descriptor);
case 4:
return FormattedComplexIO<4, DIR>(io, descriptor);
case 8:
return FormattedComplexIO<8, DIR>(io, descriptor);
case 10:
return FormattedComplexIO<10, DIR>(io, descriptor);
// TODO: case double/double
case 16:
return FormattedComplexIO<16, DIR>(io, descriptor);
default:
handler.Crash(
"not yet implemented: COMPLEX(KIND=%d) in formatted IO", kind);
return false;
}
case TypeCategory::Character:
switch (kind) {
case 1:
return FormattedCharacterIO<char, DIR>(io, descriptor);
case 2:
return FormattedCharacterIO<char16_t, DIR>(io, descriptor);
case 4:
return FormattedCharacterIO<char32_t, DIR>(io, descriptor);
default:
handler.Crash(
"not yet implemented: CHARACTER(KIND=%d) in formatted IO", kind);
return false;
}
case TypeCategory::Logical:
switch (kind) {
case 1:
return FormattedLogicalIO<1, DIR>(io, descriptor);
case 2:
return FormattedLogicalIO<2, DIR>(io, descriptor);
case 4:
return FormattedLogicalIO<4, DIR>(io, descriptor);
case 8:
return FormattedLogicalIO<8, DIR>(io, descriptor);
default:
handler.Crash(
"not yet implemented: LOGICAL(KIND=%d) in formatted IO", kind);
return false;
}
case TypeCategory::Derived:
return FormattedDerivedTypeIO<DIR>(io, descriptor, table);
}
}
handler.Crash("DescriptorIO: bad type code (%d) in descriptor",
static_cast<int>(descriptor.type().raw()));
return false;
}
} // namespace Fortran::runtime::io::descr
#endif // FLANG_RT_RUNTIME_DESCRIPTOR_IO_H_

View File

@@ -143,10 +143,6 @@ void ExecutionEnvironment::Configure(int ac, const char *av[],
}
}
if (auto *x{std::getenv("FLANG_RT_DEBUG")}) {
internalDebugging = std::strtol(x, nullptr, 10);
}
if (auto *x{std::getenv("ACC_OFFLOAD_STACK_SIZE")}) {
char *end;
auto n{std::strtoul(x, &end, 10)};

View File

@@ -10,7 +10,6 @@
#include "descriptor-io.h"
#include "flang-rt/runtime/emit-encoded.h"
#include "flang-rt/runtime/io-stmt.h"
#include "flang-rt/runtime/type-info.h"
#include "flang/Runtime/io-api.h"
#include <algorithm>
#include <cstring>

View File

@@ -205,7 +205,7 @@ RT_API_ATTRS void ShallowCopyInner(const Descriptor &to, const Descriptor &from,
// Doing the recursion upwards instead of downwards puts the more common
// cases earlier in the if-chain and has a tangible impact on performance.
template <typename P, int RANK> struct ShallowCopyRankSpecialize {
static RT_API_ATTRS bool execute(const Descriptor &to, const Descriptor &from,
static bool execute(const Descriptor &to, const Descriptor &from,
bool toIsContiguous, bool fromIsContiguous) {
if (to.rank() == RANK && from.rank() == RANK) {
ShallowCopyInner<P, RANK>(to, from, toIsContiguous, fromIsContiguous);
@@ -217,7 +217,7 @@ template <typename P, int RANK> struct ShallowCopyRankSpecialize {
};
template <typename P> struct ShallowCopyRankSpecialize<P, maxRank + 1> {
static RT_API_ATTRS bool execute(const Descriptor &to, const Descriptor &from,
static bool execute(const Descriptor &to, const Descriptor &from,
bool toIsContiguous, bool fromIsContiguous) {
return false;
}

View File

@@ -140,11 +140,11 @@ RT_API_ATTRS void Component::CreatePointerDescriptor(Descriptor &descriptor,
const SubscriptValue *subscripts) const {
RUNTIME_CHECK(terminator, genre_ == Genre::Data);
EstablishDescriptor(descriptor, container, terminator);
std::size_t offset{offset_};
if (subscripts) {
offset += container.SubscriptsToByteOffset(subscripts);
descriptor.set_base_addr(container.Element<char>(subscripts) + offset_);
} else {
descriptor.set_base_addr(container.OffsetElement<char>() + offset_);
}
descriptor.set_base_addr(container.OffsetElement<char>() + offset);
descriptor.raw().attribute = CFI_attribute_pointer;
}

View File

@@ -1,161 +0,0 @@
//===-- lib/runtime/work-queue.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 "flang-rt/runtime/work-queue.h"
#include "flang-rt/runtime/environment.h"
#include "flang-rt/runtime/memory.h"
#include "flang-rt/runtime/type-info.h"
#include "flang/Common/visit.h"
namespace Fortran::runtime {
#if !defined(RT_DEVICE_COMPILATION)
// FLANG_RT_DEBUG code is disabled when false.
static constexpr bool enableDebugOutput{false};
#endif
RT_OFFLOAD_API_GROUP_BEGIN
RT_API_ATTRS Componentwise::Componentwise(const typeInfo::DerivedType &derived)
: derived_{derived}, components_{derived_.component().Elements()} {
GetComponent();
}
RT_API_ATTRS void Componentwise::GetComponent() {
if (IsComplete()) {
component_ = nullptr;
} else {
const Descriptor &componentDesc{derived_.component()};
component_ = componentDesc.ZeroBasedIndexedElement<typeInfo::Component>(
componentAt_);
}
}
RT_API_ATTRS int Ticket::Continue(WorkQueue &workQueue) {
if (!begun) {
begun = true;
return common::visit(
[&workQueue](
auto &specificTicket) { return specificTicket.Begin(workQueue); },
u);
} else {
return common::visit(
[&workQueue](auto &specificTicket) {
return specificTicket.Continue(workQueue);
},
u);
}
}
RT_API_ATTRS WorkQueue::~WorkQueue() {
if (last_) {
if ((last_->next = firstFree_)) {
last_->next->previous = last_;
}
firstFree_ = first_;
first_ = last_ = nullptr;
}
while (firstFree_) {
TicketList *next{firstFree_->next};
if (!firstFree_->isStatic) {
FreeMemory(firstFree_);
}
firstFree_ = next;
}
}
RT_API_ATTRS Ticket &WorkQueue::StartTicket() {
if (!firstFree_) {
void *p{AllocateMemoryOrCrash(terminator_, sizeof(TicketList))};
firstFree_ = new (p) TicketList;
firstFree_->isStatic = false;
}
TicketList *newTicket{firstFree_};
if ((firstFree_ = newTicket->next)) {
firstFree_->previous = nullptr;
}
TicketList *after{insertAfter_ ? insertAfter_->next : nullptr};
if ((newTicket->previous = insertAfter_ ? insertAfter_ : last_)) {
newTicket->previous->next = newTicket;
} else {
first_ = newTicket;
}
if ((newTicket->next = after)) {
after->previous = newTicket;
} else {
last_ = newTicket;
}
newTicket->ticket.begun = false;
#if !defined(RT_DEVICE_COMPILATION)
if (enableDebugOutput &&
(executionEnvironment.internalDebugging &
ExecutionEnvironment::WorkQueue)) {
std::fprintf(stderr, "WQ: new ticket\n");
}
#endif
return newTicket->ticket;
}
RT_API_ATTRS int WorkQueue::Run() {
while (last_) {
TicketList *at{last_};
insertAfter_ = last_;
#if !defined(RT_DEVICE_COMPILATION)
if (enableDebugOutput &&
(executionEnvironment.internalDebugging &
ExecutionEnvironment::WorkQueue)) {
std::fprintf(stderr, "WQ: %zd %s\n", at->ticket.u.index(),
at->ticket.begun ? "Continue" : "Begin");
}
#endif
int stat{at->ticket.Continue(*this)};
#if !defined(RT_DEVICE_COMPILATION)
if (enableDebugOutput &&
(executionEnvironment.internalDebugging &
ExecutionEnvironment::WorkQueue)) {
std::fprintf(stderr, "WQ: ... stat %d\n", stat);
}
#endif
insertAfter_ = nullptr;
if (stat == StatOk) {
if (at->previous) {
at->previous->next = at->next;
} else {
first_ = at->next;
}
if (at->next) {
at->next->previous = at->previous;
} else {
last_ = at->previous;
}
if ((at->next = firstFree_)) {
at->next->previous = at;
}
at->previous = nullptr;
firstFree_ = at;
} else if (stat != StatContinue) {
Stop();
return stat;
}
}
return StatOk;
}
RT_API_ATTRS void WorkQueue::Stop() {
if (last_) {
if ((last_->next = firstFree_)) {
last_->next->previous = last_;
}
firstFree_ = first_;
first_ = last_ = nullptr;
}
}
RT_OFFLOAD_API_GROUP_END
} // namespace Fortran::runtime

View File

@@ -184,7 +184,7 @@ TEST(ExternalIOTests, TestSequentialFixedUnformatted) {
io = IONAME(BeginInquireIoLength)(__FILE__, __LINE__);
for (int j{1}; j <= 3; ++j) {
ASSERT_TRUE(IONAME(OutputDescriptor)(io, desc))
<< "OutputDescriptor() for InquireIoLength " << j;
<< "OutputDescriptor() for InquireIoLength";
}
ASSERT_EQ(IONAME(GetIoLength)(io), 3 * recl) << "GetIoLength";
ASSERT_EQ(IONAME(EndIoStatement)(io), IostatOk)