//===-- runtime/assign.cpp ------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "flang/Runtime/assign.h" #include "derived.h" #include "stat.h" #include "terminator.h" #include "type-info.h" #include "flang/Runtime/descriptor.h" namespace Fortran::runtime { static void DoScalarDefinedAssignment(const Descriptor &to, const Descriptor &from, const typeInfo::SpecialBinding &special) { bool toIsDesc{special.IsArgDescriptor(0)}; bool fromIsDesc{special.IsArgDescriptor(1)}; if (toIsDesc) { if (fromIsDesc) { auto *p{ special.GetProc()}; p(to, from); } else { auto *p{special.GetProc()}; p(to, from.raw().base_addr); } } else { if (fromIsDesc) { auto *p{special.GetProc()}; p(to.raw().base_addr, from); } else { auto *p{special.GetProc()}; p(to.raw().base_addr, from.raw().base_addr); } } } static void DoElementalDefinedAssignment(const Descriptor &to, const Descriptor &from, const typeInfo::SpecialBinding &special, std::size_t toElements, SubscriptValue toAt[], SubscriptValue fromAt[]) { StaticDescriptor statDesc[2]; Descriptor &toElementDesc{statDesc[0].descriptor()}; Descriptor &fromElementDesc{statDesc[1].descriptor()}; toElementDesc = to; toElementDesc.raw().attribute = CFI_attribute_pointer; toElementDesc.raw().rank = 0; fromElementDesc = from; fromElementDesc.raw().attribute = CFI_attribute_pointer; fromElementDesc.raw().rank = 0; for (std::size_t j{0}; j < toElements; ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { toElementDesc.set_base_addr(to.Element(toAt)); fromElementDesc.set_base_addr(from.Element(fromAt)); DoScalarDefinedAssignment(toElementDesc, fromElementDesc, special); } } void Assign(Descriptor &to, const Descriptor &from, Terminator &terminator) { DescriptorAddendum *toAddendum{to.Addendum()}; const typeInfo::DerivedType *toDerived{ toAddendum ? toAddendum->derivedType() : nullptr}; const DescriptorAddendum *fromAddendum{from.Addendum()}; const typeInfo::DerivedType *fromDerived{ fromAddendum ? fromAddendum->derivedType() : nullptr}; bool wasJustAllocated{false}; if (to.IsAllocatable()) { std::size_t lenParms{fromDerived ? fromDerived->LenParameters() : 0}; if (to.IsAllocated()) { // Top-level assignments to allocatable variables (*not* components) // may first deallocate existing content if there's about to be a // change in type or shape; see F'2018 10.2.1.3(3). bool deallocate{false}; if (to.type() != from.type()) { deallocate = true; } else if (toDerived != fromDerived) { deallocate = true; } else { if (toAddendum) { // Distinct LEN parameters? Deallocate for (std::size_t j{0}; j < lenParms; ++j) { if (toAddendum->LenParameterValue(j) != fromAddendum->LenParameterValue(j)) { deallocate = true; break; } } } if (from.rank() > 0) { // Distinct shape? Deallocate int rank{to.rank()}; for (int j{0}; j < rank; ++j) { if (to.GetDimension(j).Extent() != from.GetDimension(j).Extent()) { deallocate = true; break; } } } } if (deallocate) { to.Destroy(true /*finalize*/); } } else if (to.rank() != from.rank()) { terminator.Crash("Assign: mismatched ranks (%d != %d) in assignment to " "unallocated allocatable", to.rank(), from.rank()); } if (!to.IsAllocated()) { to.raw().type = from.raw().type; to.raw().elem_len = from.ElementBytes(); if (toAddendum) { toDerived = fromDerived; toAddendum->set_derivedType(toDerived); for (std::size_t j{0}; j < lenParms; ++j) { toAddendum->SetLenParameterValue( j, fromAddendum->LenParameterValue(j)); } } // subtle: leave bounds in place when "from" is scalar (10.2.1.3(3)) int rank{from.rank()}; auto stride{static_cast(to.ElementBytes())}; for (int j{0}; j < rank; ++j) { auto &toDim{to.GetDimension(j)}; const auto &fromDim{from.GetDimension(j)}; toDim.SetBounds(fromDim.LowerBound(), fromDim.UpperBound()); toDim.SetByteStride(stride); stride *= toDim.Extent(); } ReturnError(terminator, to.Allocate()); if (fromDerived && !fromDerived->noInitializationNeeded()) { ReturnError(terminator, Initialize(to, *toDerived, terminator)); } wasJustAllocated = true; } } 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()) { terminator.Crash("Assign: mismatching types (to code %d != from code %d)", to.type().raw(), from.type().raw()); } std::size_t elementBytes{to.ElementBytes()}; if (elementBytes != from.ElementBytes()) { terminator.Crash( "Assign: mismatching element sizes (to %zd bytes != from %zd bytes)", elementBytes, from.ElementBytes()); } if (toDerived) { // Derived type assignment // Check for defined assignment type-bound procedures (10.2.1.4-5) if (to.rank() == 0) { if (const auto *special{toDerived->FindSpecialBinding( typeInfo::SpecialBinding::Which::ScalarAssignment)}) { return DoScalarDefinedAssignment(to, from, *special); } } if (const auto *special{toDerived->FindSpecialBinding( typeInfo::SpecialBinding::Which::ElementalAssignment)}) { return DoElementalDefinedAssignment( to, from, *special, toElements, toAt, fromAt); } // 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 (!wasJustAllocated && !toDerived->noFinalizationNeeded()) { Finalize(to, *toDerived); } // Copy the data components (incl. the parent) first. const Descriptor &componentDesc{toDerived->component()}; std::size_t numComponents{componentDesc.Elements()}; for (std::size_t k{0}; k < numComponents; ++k) { const auto &comp{ *componentDesc.ZeroBasedIndexedElement( k)}; // TODO: exploit contiguity here switch (comp.genre()) { case typeInfo::Component::Genre::Data: if (comp.category() == TypeCategory::Derived) { StaticDescriptor statDesc[2]; Descriptor &toCompDesc{statDesc[0].descriptor()}; Descriptor &fromCompDesc{statDesc[1].descriptor()}; for (std::size_t j{0}; j < toElements; ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { comp.CreatePointerDescriptor(toCompDesc, to, terminator, toAt); comp.CreatePointerDescriptor( fromCompDesc, from, terminator, fromAt); Assign(toCompDesc, fromCompDesc, terminator); } } else { // Component has intrinsic type; simply copy raw bytes std::size_t componentByteSize{comp.SizeInBytes(to)}; for (std::size_t j{0}; j < toElements; ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { std::memmove(to.Element(toAt) + comp.offset(), from.Element(fromAt) + comp.offset(), componentByteSize); } } break; case typeInfo::Component::Genre::Pointer: { std::size_t componentByteSize{comp.SizeInBytes(to)}; for (std::size_t j{0}; j < toElements; ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { std::memmove(to.Element(toAt) + comp.offset(), from.Element(fromAt) + comp.offset(), componentByteSize); } } break; case typeInfo::Component::Genre::Allocatable: case typeInfo::Component::Genre::Automatic: for (std::size_t j{0}; j < toElements; ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { auto *toDesc{reinterpret_cast( to.Element(toAt) + comp.offset())}; const auto *fromDesc{reinterpret_cast( from.Element(fromAt) + comp.offset())}; if (toDesc->IsAllocatable()) { if (toDesc->IsAllocated()) { // 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. // TODO: Consider skipping this step and deferring the // deallocation to the recursive activation of Assign(), // which might be able to avoid deallocation/reallocation // when the existing allocation can be reoccupied. toDesc->Destroy(false /*already finalized*/); } if (!fromDesc->IsAllocated()) { continue; // F'2018 10.2.1.3(13)(2) } } Assign(*toDesc, *fromDesc, terminator); } break; } } // Copy procedure pointer components const Descriptor &procPtrDesc{toDerived->procPtr()}; std::size_t numProcPtrs{procPtrDesc.Elements()}; for (std::size_t k{0}; k < numProcPtrs; ++k) { const auto &procPtr{ *procPtrDesc.ZeroBasedIndexedElement(k)}; for (std::size_t j{0}; j < toElements; ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { std::memmove(to.Element(toAt) + procPtr.offset, from.Element(fromAt) + procPtr.offset, sizeof(typeInfo::ProcedurePointer)); } } } else { // intrinsic type, intrinsic assignment if (to.rank() == from.rank() && to.IsContiguous() && from.IsContiguous()) { // Everything is contiguous; do a single big copy std::memmove( to.raw().base_addr, from.raw().base_addr, toElements * elementBytes); } else { // elemental copies for (std::size_t n{toElements}; n-- > 0; to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { std::memmove(to.Element(toAt), from.Element(fromAt), elementBytes); } } } } extern "C" { void RTNAME(Assign)(Descriptor &to, const Descriptor &from, const char *sourceFile, int sourceLine) { Terminator terminator{sourceFile, sourceLine}; Assign(to, from, terminator); } } // extern "C" } // namespace Fortran::runtime