1 | //===-- lib/runtime/assign.cpp ----------------------------------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "flang/Runtime/assign.h" |
10 | #include "flang-rt/runtime/assign-impl.h" |
11 | #include "flang-rt/runtime/derived.h" |
12 | #include "flang-rt/runtime/descriptor.h" |
13 | #include "flang-rt/runtime/stat.h" |
14 | #include "flang-rt/runtime/terminator.h" |
15 | #include "flang-rt/runtime/tools.h" |
16 | #include "flang-rt/runtime/type-info.h" |
17 | |
18 | namespace Fortran::runtime { |
19 | |
20 | // Predicate: is the left-hand side of an assignment an allocated allocatable |
21 | // that must be deallocated? |
22 | static inline RT_API_ATTRS bool MustDeallocateLHS( |
23 | Descriptor &to, const Descriptor &from, Terminator &terminator, int flags) { |
24 | // Top-level assignments to allocatable variables (*not* components) |
25 | // may first deallocate existing content if there's about to be a |
26 | // change in type or shape; see F'2018 10.2.1.3(3). |
27 | if (!(flags & MaybeReallocate)) { |
28 | return false; |
29 | } |
30 | if (!to.IsAllocatable() || !to.IsAllocated()) { |
31 | return false; |
32 | } |
33 | if (to.type() != from.type()) { |
34 | return true; |
35 | } |
36 | if (!(flags & ExplicitLengthCharacterLHS) && to.type().IsCharacter() && |
37 | to.ElementBytes() != from.ElementBytes()) { |
38 | return true; |
39 | } |
40 | if (flags & PolymorphicLHS) { |
41 | DescriptorAddendum *toAddendum{to.Addendum()}; |
42 | const typeInfo::DerivedType *toDerived{ |
43 | toAddendum ? toAddendum->derivedType() : nullptr}; |
44 | const DescriptorAddendum *fromAddendum{from.Addendum()}; |
45 | const typeInfo::DerivedType *fromDerived{ |
46 | fromAddendum ? fromAddendum->derivedType() : nullptr}; |
47 | if (toDerived != fromDerived) { |
48 | return true; |
49 | } |
50 | if (fromDerived) { |
51 | // Distinct LEN parameters? Deallocate |
52 | std::size_t lenParms{fromDerived->LenParameters()}; |
53 | for (std::size_t j{0}; j < lenParms; ++j) { |
54 | if (toAddendum->LenParameterValue(j) != |
55 | fromAddendum->LenParameterValue(j)) { |
56 | return true; |
57 | } |
58 | } |
59 | } |
60 | } |
61 | if (from.rank() > 0) { |
62 | // Distinct shape? Deallocate |
63 | int rank{to.rank()}; |
64 | for (int j{0}; j < rank; ++j) { |
65 | if (to.GetDimension(j).Extent() != from.GetDimension(j).Extent()) { |
66 | return true; |
67 | } |
68 | } |
69 | } |
70 | return false; |
71 | } |
72 | |
73 | // Utility: allocate the allocatable left-hand side, either because it was |
74 | // originally deallocated or because it required reallocation |
75 | static RT_API_ATTRS int AllocateAssignmentLHS( |
76 | Descriptor &to, const Descriptor &from, Terminator &terminator, int flags) { |
77 | to.raw().type = from.raw().type; |
78 | if (!(flags & ExplicitLengthCharacterLHS)) { |
79 | to.raw().elem_len = from.ElementBytes(); |
80 | } |
81 | const typeInfo::DerivedType *derived{nullptr}; |
82 | DescriptorAddendum *toAddendum{to.Addendum()}; |
83 | if (const DescriptorAddendum * fromAddendum{from.Addendum()}) { |
84 | derived = fromAddendum->derivedType(); |
85 | if (toAddendum) { |
86 | toAddendum->set_derivedType(derived); |
87 | std::size_t lenParms{derived ? derived->LenParameters() : 0}; |
88 | for (std::size_t j{0}; j < lenParms; ++j) { |
89 | toAddendum->SetLenParameterValue(j, fromAddendum->LenParameterValue(j)); |
90 | } |
91 | } |
92 | } else if (toAddendum) { |
93 | toAddendum->set_derivedType(nullptr); |
94 | } |
95 | // subtle: leave bounds in place when "from" is scalar (10.2.1.3(3)) |
96 | int rank{from.rank()}; |
97 | auto stride{static_cast<SubscriptValue>(to.ElementBytes())}; |
98 | for (int j{0}; j < rank; ++j) { |
99 | auto &toDim{to.GetDimension(j)}; |
100 | const auto &fromDim{from.GetDimension(j)}; |
101 | toDim.SetBounds(fromDim.LowerBound(), fromDim.UpperBound()); |
102 | toDim.SetByteStride(stride); |
103 | stride *= toDim.Extent(); |
104 | } |
105 | int result{ReturnError(terminator, to.Allocate(kNoAsyncObject))}; |
106 | if (result == StatOk && derived && !derived->noInitializationNeeded()) { |
107 | result = ReturnError(terminator, Initialize(to, *derived, terminator)); |
108 | } |
109 | return result; |
110 | } |
111 | |
112 | // least <= 0, most >= 0 |
113 | static RT_API_ATTRS void MaximalByteOffsetRange( |
114 | const Descriptor &desc, std::int64_t &least, std::int64_t &most) { |
115 | least = most = 0; |
116 | if (desc.ElementBytes() == 0) { |
117 | return; |
118 | } |
119 | int n{desc.raw().rank}; |
120 | for (int j{0}; j < n; ++j) { |
121 | const auto &dim{desc.GetDimension(j)}; |
122 | auto extent{dim.Extent()}; |
123 | if (extent > 0) { |
124 | auto sm{dim.ByteStride()}; |
125 | if (sm < 0) { |
126 | least += (extent - 1) * sm; |
127 | } else { |
128 | most += (extent - 1) * sm; |
129 | } |
130 | } |
131 | } |
132 | most += desc.ElementBytes() - 1; |
133 | } |
134 | |
135 | static inline RT_API_ATTRS bool RangesOverlap(const char *aStart, |
136 | const char *aEnd, const char *bStart, const char *bEnd) { |
137 | return aEnd >= bStart && bEnd >= aStart; |
138 | } |
139 | |
140 | // Predicate: could the left-hand and right-hand sides of the assignment |
141 | // possibly overlap in memory? Note that the descriptors themeselves |
142 | // are included in the test. |
143 | static RT_API_ATTRS bool MayAlias(const Descriptor &x, const Descriptor &y) { |
144 | const char *xBase{x.OffsetElement()}; |
145 | const char *yBase{y.OffsetElement()}; |
146 | if (!xBase || !yBase) { |
147 | return false; // not both allocated |
148 | } |
149 | const char *xDesc{reinterpret_cast<const char *>(&x)}; |
150 | const char *xDescLast{xDesc + x.SizeInBytes() - 1}; |
151 | const char *yDesc{reinterpret_cast<const char *>(&y)}; |
152 | const char *yDescLast{yDesc + y.SizeInBytes() - 1}; |
153 | std::int64_t xLeast, xMost, yLeast, yMost; |
154 | MaximalByteOffsetRange(x, xLeast, xMost); |
155 | MaximalByteOffsetRange(y, yLeast, yMost); |
156 | if (RangesOverlap(xDesc, xDescLast, yBase + yLeast, yBase + yMost) || |
157 | RangesOverlap(yDesc, yDescLast, xBase + xLeast, xBase + xMost)) { |
158 | // A descriptor overlaps with the storage described by the other; |
159 | // this can arise when an allocatable or pointer component is |
160 | // being assigned to/from. |
161 | return true; |
162 | } |
163 | if (!RangesOverlap( |
164 | xBase + xLeast, xBase + xMost, yBase + yLeast, yBase + yMost)) { |
165 | return false; // no storage overlap |
166 | } |
167 | // TODO: check dimensions: if any is independent, return false |
168 | return true; |
169 | } |
170 | |
171 | static RT_API_ATTRS void DoScalarDefinedAssignment(const Descriptor &to, |
172 | const Descriptor &from, const typeInfo::SpecialBinding &special) { |
173 | bool toIsDesc{special.IsArgDescriptor(0)}; |
174 | bool fromIsDesc{special.IsArgDescriptor(1)}; |
175 | if (toIsDesc) { |
176 | if (fromIsDesc) { |
177 | auto *p{ |
178 | special.GetProc<void (*)(const Descriptor &, const Descriptor &)>()}; |
179 | p(to, from); |
180 | } else { |
181 | auto *p{special.GetProc<void (*)(const Descriptor &, void *)>()}; |
182 | p(to, from.raw().base_addr); |
183 | } |
184 | } else { |
185 | if (fromIsDesc) { |
186 | auto *p{special.GetProc<void (*)(void *, const Descriptor &)>()}; |
187 | p(to.raw().base_addr, from); |
188 | } else { |
189 | auto *p{special.GetProc<void (*)(void *, void *)>()}; |
190 | p(to.raw().base_addr, from.raw().base_addr); |
191 | } |
192 | } |
193 | } |
194 | |
195 | static RT_API_ATTRS void DoElementalDefinedAssignment(const Descriptor &to, |
196 | const Descriptor &from, const typeInfo::DerivedType &derived, |
197 | const typeInfo::SpecialBinding &special) { |
198 | SubscriptValue toAt[maxRank], fromAt[maxRank]; |
199 | to.GetLowerBounds(toAt); |
200 | from.GetLowerBounds(fromAt); |
201 | StaticDescriptor<maxRank, true, 8 /*?*/> statDesc[2]; |
202 | Descriptor &toElementDesc{statDesc[0].descriptor()}; |
203 | Descriptor &fromElementDesc{statDesc[1].descriptor()}; |
204 | toElementDesc.Establish(derived, nullptr, 0, nullptr, CFI_attribute_pointer); |
205 | fromElementDesc.Establish( |
206 | derived, nullptr, 0, nullptr, CFI_attribute_pointer); |
207 | for (std::size_t toElements{to.Elements()}; toElements-- > 0; |
208 | to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { |
209 | toElementDesc.set_base_addr(to.Element<char>(toAt)); |
210 | fromElementDesc.set_base_addr(from.Element<char>(fromAt)); |
211 | DoScalarDefinedAssignment(toElementDesc, fromElementDesc, special); |
212 | } |
213 | } |
214 | |
215 | template <typename CHAR> |
216 | static RT_API_ATTRS void BlankPadCharacterAssignment(Descriptor &to, |
217 | const Descriptor &from, SubscriptValue toAt[], SubscriptValue fromAt[], |
218 | std::size_t elements, std::size_t toElementBytes, |
219 | std::size_t fromElementBytes) { |
220 | std::size_t padding{(toElementBytes - fromElementBytes) / sizeof(CHAR)}; |
221 | std::size_t copiedCharacters{fromElementBytes / sizeof(CHAR)}; |
222 | for (; elements-- > 0; |
223 | to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { |
224 | CHAR *p{to.Element<CHAR>(toAt)}; |
225 | Fortran::runtime::memmove( |
226 | p, from.Element<std::add_const_t<CHAR>>(fromAt), fromElementBytes); |
227 | p += copiedCharacters; |
228 | for (auto n{padding}; n-- > 0;) { |
229 | *p++ = CHAR{' '}; |
230 | } |
231 | } |
232 | } |
233 | |
234 | // Common implementation of assignments, both intrinsic assignments and |
235 | // those cases of polymorphic user-defined ASSIGNMENT(=) TBPs that could not |
236 | // be resolved in semantics. Most assignment statements do not need any |
237 | // of the capabilities of this function -- but when the LHS is allocatable, |
238 | // the type might have a user-defined ASSIGNMENT(=), or the type might be |
239 | // finalizable, this function should be used. |
240 | // When "to" is not a whole allocatable, "from" is an array, and defined |
241 | // assignments are not used, "to" and "from" only need to have the same number |
242 | // of elements, but their shape need not to conform (the assignment is done in |
243 | // element sequence order). This facilitates some internal usages, like when |
244 | // dealing with array constructors. |
245 | RT_API_ATTRS void Assign(Descriptor &to, const Descriptor &from, |
246 | Terminator &terminator, int flags, MemmoveFct memmoveFct) { |
247 | bool mustDeallocateLHS{(flags & DeallocateLHS) || |
248 | MustDeallocateLHS(to, from, terminator, flags)}; |
249 | DescriptorAddendum *toAddendum{to.Addendum()}; |
250 | const typeInfo::DerivedType *toDerived{ |
251 | toAddendum ? toAddendum->derivedType() : nullptr}; |
252 | if (toDerived && (flags & NeedFinalization) && |
253 | toDerived->noFinalizationNeeded()) { |
254 | flags &= ~NeedFinalization; |
255 | } |
256 | std::size_t toElementBytes{to.ElementBytes()}; |
257 | std::size_t fromElementBytes{from.ElementBytes()}; |
258 | // The following lambda definition violates the conding style, |
259 | // but cuda-11.8 nvcc hits an internal error with the brace initialization. |
260 | auto isSimpleMemmove = [&]() { |
261 | return !toDerived && to.rank() == from.rank() && to.IsContiguous() && |
262 | from.IsContiguous() && toElementBytes == fromElementBytes; |
263 | }; |
264 | StaticDescriptor<maxRank, true, 10 /*?*/> deferredDeallocStatDesc; |
265 | Descriptor *deferDeallocation{nullptr}; |
266 | if (MayAlias(to, from)) { |
267 | if (mustDeallocateLHS) { |
268 | deferDeallocation = &deferredDeallocStatDesc.descriptor(); |
269 | std::memcpy( |
270 | reinterpret_cast<void *>(deferDeallocation), &to, to.SizeInBytes()); |
271 | to.set_base_addr(nullptr); |
272 | } else if (!isSimpleMemmove()) { |
273 | // Handle LHS/RHS aliasing by copying RHS into a temp, then |
274 | // recursively assigning from that temp. |
275 | auto descBytes{from.SizeInBytes()}; |
276 | StaticDescriptor<maxRank, true, 16> staticDesc; |
277 | Descriptor &newFrom{staticDesc.descriptor()}; |
278 | std::memcpy(reinterpret_cast<void *>(&newFrom), &from, descBytes); |
279 | // Pretend the temporary descriptor is for an ALLOCATABLE |
280 | // entity, otherwise, the Deallocate() below will not |
281 | // free the descriptor memory. |
282 | newFrom.raw().attribute = CFI_attribute_allocatable; |
283 | auto stat{ReturnError(terminator, newFrom.Allocate(kNoAsyncObject))}; |
284 | if (stat == StatOk) { |
285 | if (HasDynamicComponent(from)) { |
286 | // If 'from' has allocatable/automatic component, we cannot |
287 | // just make a shallow copy of the descriptor member. |
288 | // This will still leave data overlap in 'to' and 'newFrom'. |
289 | // For example: |
290 | // type t |
291 | // character, allocatable :: c(:) |
292 | // end type t |
293 | // type(t) :: x(3) |
294 | // x(2:3) = x(1:2) |
295 | // We have to make a deep copy into 'newFrom' in this case. |
296 | RTNAME(AssignTemporary) |
297 | (newFrom, from, terminator.sourceFileName(), terminator.sourceLine()); |
298 | } else { |
299 | ShallowCopy(newFrom, from, true, from.IsContiguous()); |
300 | } |
301 | Assign(to, newFrom, terminator, |
302 | flags & |
303 | (NeedFinalization | ComponentCanBeDefinedAssignment | |
304 | ExplicitLengthCharacterLHS | CanBeDefinedAssignment)); |
305 | newFrom.Deallocate(); |
306 | } |
307 | return; |
308 | } |
309 | } |
310 | if (to.IsAllocatable()) { |
311 | if (mustDeallocateLHS) { |
312 | if (deferDeallocation) { |
313 | if ((flags & NeedFinalization) && toDerived) { |
314 | Finalize(*deferDeallocation, *toDerived, &terminator); |
315 | flags &= ~NeedFinalization; |
316 | } |
317 | } else { |
318 | to.Destroy((flags & NeedFinalization) != 0, /*destroyPointers=*/false, |
319 | &terminator); |
320 | flags &= ~NeedFinalization; |
321 | } |
322 | } else if (to.rank() != from.rank() && !to.IsAllocated()) { |
323 | terminator.Crash("Assign: mismatched ranks (%d != %d) in assignment to " |
324 | "unallocated allocatable" , |
325 | to.rank(), from.rank()); |
326 | } |
327 | if (!to.IsAllocated()) { |
328 | if (AllocateAssignmentLHS(to, from, terminator, flags) != StatOk) { |
329 | return; |
330 | } |
331 | flags &= ~NeedFinalization; |
332 | toElementBytes = to.ElementBytes(); // may have changed |
333 | toDerived = toAddendum ? toAddendum->derivedType() : nullptr; |
334 | } |
335 | } |
336 | if (toDerived && (flags & CanBeDefinedAssignment)) { |
337 | // Check for a user-defined assignment type-bound procedure; |
338 | // see 10.2.1.4-5. A user-defined assignment TBP defines all of |
339 | // the semantics, including allocatable (re)allocation and any |
340 | // finalization. |
341 | // |
342 | // Note that the aliasing and LHS (re)allocation handling above |
343 | // needs to run even with CanBeDefinedAssignment flag, when |
344 | // the Assign() is invoked recursively for component-per-component |
345 | // assignments. |
346 | if (to.rank() == 0) { |
347 | if (const auto *special{toDerived->FindSpecialBinding( |
348 | typeInfo::SpecialBinding::Which::ScalarAssignment)}) { |
349 | return DoScalarDefinedAssignment(to, from, *special); |
350 | } |
351 | } |
352 | if (const auto *special{toDerived->FindSpecialBinding( |
353 | typeInfo::SpecialBinding::Which::ElementalAssignment)}) { |
354 | return DoElementalDefinedAssignment(to, from, *toDerived, *special); |
355 | } |
356 | } |
357 | SubscriptValue toAt[maxRank]; |
358 | to.GetLowerBounds(toAt); |
359 | // Scalar expansion of the RHS is implied by using the same empty |
360 | // subscript values on each (seemingly) elemental reference into |
361 | // "from". |
362 | SubscriptValue fromAt[maxRank]; |
363 | from.GetLowerBounds(fromAt); |
364 | std::size_t toElements{to.Elements()}; |
365 | if (from.rank() > 0 && toElements != from.Elements()) { |
366 | terminator.Crash("Assign: mismatching element counts in array assignment " |
367 | "(to %zd, from %zd)" , |
368 | toElements, from.Elements()); |
369 | } |
370 | if (to.type() != from.type()) { |
371 | terminator.Crash("Assign: mismatching types (to code %d != from code %d)" , |
372 | to.type().raw(), from.type().raw()); |
373 | } |
374 | if (toElementBytes > fromElementBytes && !to.type().IsCharacter()) { |
375 | terminator.Crash("Assign: mismatching non-character element sizes (to %zd " |
376 | "bytes != from %zd bytes)" , |
377 | toElementBytes, fromElementBytes); |
378 | } |
379 | if (const typeInfo::DerivedType * |
380 | updatedToDerived{toAddendum ? toAddendum->derivedType() : nullptr}) { |
381 | // Derived type intrinsic assignment, which is componentwise and elementwise |
382 | // for all components, including parent components (10.2.1.2-3). |
383 | // The target is first finalized if still necessary (7.5.6.3(1)) |
384 | if (flags & NeedFinalization) { |
385 | Finalize(to, *updatedToDerived, &terminator); |
386 | } else if (updatedToDerived && !updatedToDerived->noDestructionNeeded()) { |
387 | Destroy(to, /*finalize=*/false, *updatedToDerived, &terminator); |
388 | } |
389 | // Copy the data components (incl. the parent) first. |
390 | const Descriptor &componentDesc{updatedToDerived->component()}; |
391 | std::size_t numComponents{componentDesc.Elements()}; |
392 | for (std::size_t j{0}; j < toElements; |
393 | ++j, to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { |
394 | for (std::size_t k{0}; k < numComponents; ++k) { |
395 | const auto &comp{ |
396 | *componentDesc.ZeroBasedIndexedElement<typeInfo::Component>( |
397 | k)}; // TODO: exploit contiguity here |
398 | // Use PolymorphicLHS for components so that the right things happen |
399 | // when the components are polymorphic; when they're not, they're both |
400 | // not, and their declared types will match. |
401 | int nestedFlags{MaybeReallocate | PolymorphicLHS}; |
402 | if (flags & ComponentCanBeDefinedAssignment) { |
403 | nestedFlags |= |
404 | CanBeDefinedAssignment | ComponentCanBeDefinedAssignment; |
405 | } |
406 | switch (comp.genre()) { |
407 | case typeInfo::Component::Genre::Data: |
408 | if (comp.category() == TypeCategory::Derived) { |
409 | StaticDescriptor<maxRank, true, 10 /*?*/> statDesc[2]; |
410 | Descriptor &toCompDesc{statDesc[0].descriptor()}; |
411 | Descriptor &fromCompDesc{statDesc[1].descriptor()}; |
412 | comp.CreatePointerDescriptor(toCompDesc, to, terminator, toAt); |
413 | comp.CreatePointerDescriptor( |
414 | fromCompDesc, from, terminator, fromAt); |
415 | Assign(toCompDesc, fromCompDesc, terminator, nestedFlags); |
416 | } else { // Component has intrinsic type; simply copy raw bytes |
417 | std::size_t componentByteSize{comp.SizeInBytes(to)}; |
418 | memmoveFct(to.Element<char>(toAt) + comp.offset(), |
419 | from.Element<const char>(fromAt) + comp.offset(), |
420 | componentByteSize); |
421 | } |
422 | break; |
423 | case typeInfo::Component::Genre::Pointer: { |
424 | std::size_t componentByteSize{comp.SizeInBytes(to)}; |
425 | memmoveFct(to.Element<char>(toAt) + comp.offset(), |
426 | from.Element<const char>(fromAt) + comp.offset(), |
427 | componentByteSize); |
428 | } break; |
429 | case typeInfo::Component::Genre::Allocatable: |
430 | case typeInfo::Component::Genre::Automatic: { |
431 | auto *toDesc{reinterpret_cast<Descriptor *>( |
432 | to.Element<char>(toAt) + comp.offset())}; |
433 | const auto *fromDesc{reinterpret_cast<const Descriptor *>( |
434 | from.Element<char>(fromAt) + comp.offset())}; |
435 | // Allocatable components of the LHS are unconditionally |
436 | // deallocated before assignment (F'2018 10.2.1.3(13)(1)), |
437 | // unlike a "top-level" assignment to a variable, where |
438 | // deallocation is optional. |
439 | // |
440 | // Be careful not to destroy/reallocate the LHS, if there is |
441 | // overlap between LHS and RHS (it seems that partial overlap |
442 | // is not possible, though). |
443 | // Invoke Assign() recursively to deal with potential aliasing. |
444 | if (toDesc->IsAllocatable()) { |
445 | if (!fromDesc->IsAllocated()) { |
446 | // No aliasing. |
447 | // |
448 | // If to is not allocated, the Destroy() call is a no-op. |
449 | // This is just a shortcut, because the recursive Assign() |
450 | // below would initiate the destruction for to. |
451 | // No finalization is required. |
452 | toDesc->Destroy( |
453 | /*finalize=*/false, /*destroyPointers=*/false, &terminator); |
454 | continue; // F'2018 10.2.1.3(13)(2) |
455 | } |
456 | } |
457 | // Force LHS deallocation with DeallocateLHS flag. |
458 | // The actual deallocation may be avoided, if the existing |
459 | // location can be reoccupied. |
460 | Assign(*toDesc, *fromDesc, terminator, nestedFlags | DeallocateLHS); |
461 | } break; |
462 | } |
463 | } |
464 | // Copy procedure pointer components |
465 | const Descriptor &procPtrDesc{updatedToDerived->procPtr()}; |
466 | std::size_t numProcPtrs{procPtrDesc.Elements()}; |
467 | for (std::size_t k{0}; k < numProcPtrs; ++k) { |
468 | const auto &procPtr{ |
469 | *procPtrDesc.ZeroBasedIndexedElement<typeInfo::ProcPtrComponent>( |
470 | k)}; |
471 | memmoveFct(to.Element<char>(toAt) + procPtr.offset, |
472 | from.Element<const char>(fromAt) + procPtr.offset, |
473 | sizeof(typeInfo::ProcedurePointer)); |
474 | } |
475 | } |
476 | } else { // intrinsic type, intrinsic assignment |
477 | if (isSimpleMemmove()) { |
478 | memmoveFct(to.raw().base_addr, from.raw().base_addr, |
479 | toElements * toElementBytes); |
480 | } else if (toElementBytes > fromElementBytes) { // blank padding |
481 | switch (to.type().raw()) { |
482 | case CFI_type_signed_char: |
483 | case CFI_type_char: |
484 | BlankPadCharacterAssignment<char>(to, from, toAt, fromAt, toElements, |
485 | toElementBytes, fromElementBytes); |
486 | break; |
487 | case CFI_type_char16_t: |
488 | BlankPadCharacterAssignment<char16_t>(to, from, toAt, fromAt, |
489 | toElements, toElementBytes, fromElementBytes); |
490 | break; |
491 | case CFI_type_char32_t: |
492 | BlankPadCharacterAssignment<char32_t>(to, from, toAt, fromAt, |
493 | toElements, toElementBytes, fromElementBytes); |
494 | break; |
495 | default: |
496 | terminator.Crash("unexpected type code %d in blank padded Assign()" , |
497 | to.type().raw()); |
498 | } |
499 | } else { // elemental copies, possibly with character truncation |
500 | for (std::size_t n{toElements}; n-- > 0; |
501 | to.IncrementSubscripts(toAt), from.IncrementSubscripts(fromAt)) { |
502 | memmoveFct(to.Element<char>(toAt), from.Element<const char>(fromAt), |
503 | toElementBytes); |
504 | } |
505 | } |
506 | } |
507 | if (deferDeallocation) { |
508 | // deferDeallocation is used only when LHS is an allocatable. |
509 | // The finalization has already been run for it. |
510 | deferDeallocation->Destroy( |
511 | /*finalize=*/false, /*destroyPointers=*/false, &terminator); |
512 | } |
513 | } |
514 | |
515 | RT_OFFLOAD_API_GROUP_BEGIN |
516 | |
517 | RT_API_ATTRS void DoFromSourceAssign(Descriptor &alloc, |
518 | const Descriptor &source, Terminator &terminator, MemmoveFct memmoveFct) { |
519 | if (alloc.rank() > 0 && source.rank() == 0) { |
520 | // The value of each element of allocate object becomes the value of source. |
521 | DescriptorAddendum *allocAddendum{alloc.Addendum()}; |
522 | const typeInfo::DerivedType *allocDerived{ |
523 | allocAddendum ? allocAddendum->derivedType() : nullptr}; |
524 | SubscriptValue allocAt[maxRank]; |
525 | alloc.GetLowerBounds(allocAt); |
526 | if (allocDerived) { |
527 | for (std::size_t n{alloc.Elements()}; n-- > 0; |
528 | alloc.IncrementSubscripts(allocAt)) { |
529 | Descriptor allocElement{*Descriptor::Create(*allocDerived, |
530 | reinterpret_cast<void *>(alloc.Element<char>(allocAt)), 0)}; |
531 | Assign(allocElement, source, terminator, NoAssignFlags, memmoveFct); |
532 | } |
533 | } else { // intrinsic type |
534 | for (std::size_t n{alloc.Elements()}; n-- > 0; |
535 | alloc.IncrementSubscripts(allocAt)) { |
536 | memmoveFct(alloc.Element<char>(allocAt), source.raw().base_addr, |
537 | alloc.ElementBytes()); |
538 | } |
539 | } |
540 | } else { |
541 | Assign(alloc, source, terminator, NoAssignFlags, memmoveFct); |
542 | } |
543 | } |
544 | |
545 | RT_OFFLOAD_API_GROUP_END |
546 | |
547 | extern "C" { |
548 | RT_EXT_API_GROUP_BEGIN |
549 | |
550 | void RTDEF(Assign)(Descriptor &to, const Descriptor &from, |
551 | const char *sourceFile, int sourceLine) { |
552 | Terminator terminator{sourceFile, sourceLine}; |
553 | // All top-level defined assignments can be recognized in semantics and |
554 | // will have been already been converted to calls, so don't check for |
555 | // defined assignment apart from components. |
556 | Assign(to, from, terminator, |
557 | MaybeReallocate | NeedFinalization | ComponentCanBeDefinedAssignment); |
558 | } |
559 | |
560 | void RTDEF(AssignTemporary)(Descriptor &to, const Descriptor &from, |
561 | const char *sourceFile, int sourceLine) { |
562 | Terminator terminator{sourceFile, sourceLine}; |
563 | // Initialize the "to" if it is of derived type that needs initialization. |
564 | if (const DescriptorAddendum * addendum{to.Addendum()}) { |
565 | if (const auto *derived{addendum->derivedType()}) { |
566 | // Do not invoke the initialization, if the descriptor is unallocated. |
567 | // AssignTemporary() is used for component-by-component assignments, |
568 | // for example, for structure constructors. This means that the LHS |
569 | // may be an allocatable component with unallocated status. |
570 | // The initialization will just fail in this case. By skipping |
571 | // the initialization we let Assign() automatically allocate |
572 | // and initialize the component according to the RHS. |
573 | // So we only need to initialize the LHS here if it is allocated. |
574 | // Note that initializing already initialized entity has no visible |
575 | // effect, though, it is assumed that the compiler does not initialize |
576 | // the temporary and leaves the initialization to this runtime code. |
577 | if (!derived->noInitializationNeeded() && to.IsAllocated()) { |
578 | if (ReturnError(terminator, Initialize(to, *derived, terminator)) != |
579 | StatOk) { |
580 | return; |
581 | } |
582 | } |
583 | } |
584 | } |
585 | |
586 | Assign(to, from, terminator, MaybeReallocate | PolymorphicLHS); |
587 | } |
588 | |
589 | void RTDEF(CopyInAssign)(Descriptor &temp, const Descriptor &var, |
590 | const char *sourceFile, int sourceLine) { |
591 | Terminator terminator{sourceFile, sourceLine}; |
592 | temp = var; |
593 | temp.set_base_addr(nullptr); |
594 | temp.raw().attribute = CFI_attribute_allocatable; |
595 | temp.Allocate(kNoAsyncObject); |
596 | ShallowCopy(temp, var); |
597 | } |
598 | |
599 | void RTDEF(CopyOutAssign)( |
600 | Descriptor *var, Descriptor &temp, const char *sourceFile, int sourceLine) { |
601 | Terminator terminator{sourceFile, sourceLine}; |
602 | |
603 | // Copyout from the temporary must not cause any finalizations |
604 | // for LHS. The variable must be properly initialized already. |
605 | if (var) { |
606 | ShallowCopy(*var, temp); |
607 | } |
608 | temp.Deallocate(); |
609 | } |
610 | |
611 | void RTDEF(AssignExplicitLengthCharacter)(Descriptor &to, |
612 | const Descriptor &from, const char *sourceFile, int sourceLine) { |
613 | Terminator terminator{sourceFile, sourceLine}; |
614 | Assign(to, from, terminator, |
615 | MaybeReallocate | NeedFinalization | ComponentCanBeDefinedAssignment | |
616 | ExplicitLengthCharacterLHS); |
617 | } |
618 | |
619 | void RTDEF(AssignPolymorphic)(Descriptor &to, const Descriptor &from, |
620 | const char *sourceFile, int sourceLine) { |
621 | Terminator terminator{sourceFile, sourceLine}; |
622 | Assign(to, from, terminator, |
623 | MaybeReallocate | NeedFinalization | ComponentCanBeDefinedAssignment | |
624 | PolymorphicLHS); |
625 | } |
626 | |
627 | RT_EXT_API_GROUP_END |
628 | } // extern "C" |
629 | } // namespace Fortran::runtime |
630 | |