[flang-commits] [flang] [flang] Fix TRANSFER into derived type with tail padding zeroing pad bytes (PR #223814)
via flang-commits
flang-commits at lists.llvm.org
Wed Sep 16 20:37:05 PDT 2026
================
@@ -1552,15 +1472,24 @@ void fir::factory::genRecordAssignment(fir::FirOpBuilder &builder,
return;
}
- // Otherwise, the derived type has compile time constant size and for which
- // the component by component assignment can be replaced by a memory copy.
- // Since we do not know the size of the derived type in lowering, do a
- // component by component assignment. Note that a single fir.load/fir.store
- // could be used on "small" record types, but as the type size grows, this
- // leads to issues in LLVM (long compile times, long IR files, and even
- // asserts at some point). Since there is no good size boundary, just always
- // use component by component assignment here.
- genComponentByComponentAssignment(builder, loc, lhs, rhs, isTemporaryLHS);
+ // Otherwise, the derived type has compile time constant size, no
+ // allocatable components, and no user-defined assignment. The size of the
+ // type is not known at this point in lowering, but fir.copy defers the size
+ // computation to codegen where the LLVM data layout is available. That allows
+ // it to copy the full allocated storage including any ABI tail-padding bytes,
+ // which a field-by-field copy would silently skip. Preserving those bytes
+ // matters for SEQUENCE types whose storage is reinterpreted via EQUIVALENCE
+ // or TRANSFER.
+ mlir::Value fromAddr = fir::getBase(rhs);
+ mlir::Value toAddr = fir::getBase(lhs);
+ // Ensure we have raw ref<RecordType> pointers for fir.copy.
+ auto refTy = builder.getRefType(recTy);
+ if (fromAddr.getType() != refTy)
+ fromAddr = builder.createConvert(loc, refTy, fromAddr);
+ if (toAddr.getType() != refTy)
+ toAddr = builder.createConvert(loc, refTy, toAddr);
+ // disjoint == true at this point (guaranteed by the condition above).
----------------
MattPD wrote:
`disjoint` is true for every non-SEQUENCE type without consulting alias analysis. Using it as `no_overlap` produces a `memcpy` requiring equal or non-overlapping operands. `disjoint` predates the patch, but the `memcpy` is new. `flang/docs/Aliasing.md` documents one configuration with overlapping operands: a Cray pointee aliasing a variable with the `TARGET` attribute.
```fortran
program overlap_target
use iso_c_binding, only: c_int, c_intptr_t
implicit none
type, bind(c) :: t
integer(c_int) :: a(6)
end type
type(t), target :: buf(2)
type(t) :: src
integer(c_intptr_t) :: sp
pointer (sp, src)
integer :: i
buf(1)%a = [(i, i=1,6)]
buf(2)%a = [(i, i=7,12)]
sp = loc(buf(1)%a(2))
buf(1) = src
if (any(buf(1)%a /= [(i, i=2,7)])) error stop 1
if (any(buf(2)%a /= [(i, i=7,12)])) error stop 2
end program
```
The parent passes at `-O0` and `-O2`. This branch passes at `-O0` and stops with `Fortran ERROR STOP: code 1` at `-O2`. At `-O2`, `buf(1) = src` is a 24-byte `llvm.memcpy` from `@_QFEbuf + 4` to `@_QFEbuf`. For `sp = loc(buf(1)%a(6)); buf(2) = src`, the pointee is below the destination. The parent's component loop was already wrong in this direction, so neither version handles both overlap directions. Is `no_overlap` intended when the non-SEQUENCE `disjoint` value comes from an assumption rather than alias analysis? Without `no_overlap`, `fir.copy` lowers to `memmove`. `memmove` handles both overlap directions correctly and doesn't add any cost for records of this size.
https://github.com/llvm/llvm-project/pull/223814
More information about the flang-commits
mailing list