[flang-commits] [flang] 5fa7f81 - [flang] Fix RecordType sizes, TRANSFER lowering, and BIND(C) ABI on SystemZ/PPC64le (#220377)
via flang-commits
flang-commits at lists.llvm.org
Thu Sep 17 18:57:56 PDT 2026
Author: Daniel Chen
Date: 2026-09-17T21:57:49-04:00
New Revision: 5fa7f81e0a2208b87584f2fec12e09c73105fed1
URL: https://github.com/llvm/llvm-project/commit/5fa7f81e0a2208b87584f2fec12e09c73105fed1
DIFF: https://github.com/llvm/llvm-project/commit/5fa7f81e0a2208b87584f2fec12e09c73105fed1.diff
LOG: [flang] Fix RecordType sizes, TRANSFER lowering, and BIND(C) ABI on SystemZ/PPC64le (#220377)
### Summary
`fir::getTypeSizeAndAlignment` had two bugs in its `RecordType` branch:
1. **Packed records**: `isPacked()` was ignored. LLVM packed structs
advance by `getTypeAllocSize` per field (not `getTypeStoreSize`), so
each component occupies `alignTo(storeSize, ABIalign)` bytes with no
inter-field or tail padding, and the struct ABI alignment is 1. For
example, a packed `{i32, f64}` on x86-64 is 12 bytes, not 16.
2. **Tail padding**: the unpacked field loop returned the raw summed
size without the final `alignTo(size, align)`. For example, `{i32, i8}`
(sum = 5 bytes, align = 4) was returned as 5 bytes instead of the
correct allocation size 8 bytes.
### Changes
- **`RecordType` Sizing**: Fixed `fir::getTypeSizeAndAlignment` to
respect `isPacked()` and include the final alignment padding for
unpacked records.
- **`TRANSFER` Inline Gate**: Updated `genTransfer` to compare sizes
using `fir::getTypeSizeAndAlignment`, which includes tail padding for
`RecordType`, matching the allocation extent used by Fortran
`STORAGE_SIZE`, descriptor element extent, and the runtime `TRANSFER`
path.
- **Aligned Temporary & Byte Copy**: For `RecordType` sources where
`sourceAlign < resultAlign` (e.g. `{i32, i8}` with 4-byte alignment
transferred to `integer(8)` with 8-byte alignment), copy via `fir.copy`
(a non-overlapping byte copy / `memcpy` of the full allocation extent)
into a result-aligned alloca instead of loading directly from the source
pointer. This avoids UB from over-aligned loads and preserves both
inter-field and tail padding bytes, satisfying F2023 16.9.212.
- **`BIND(C)` / `VALUE` ABI on SystemZ and PPC64le**: The corrected
allocation sizes propagate into `Target.cpp`'s ABI classification,
fixing argument passing for both packed and tail-padded records. In the
SystemZ and PPC64le examples the record layout already included the
padding, but the size query used for argument classification was wrong:
- SystemZ: `{i32, i8}` (allocation size 8 bytes) now correctly passes as
`i64`; `{i16, i8}` (4 bytes) as `i32`.
- PPC64le: `{f128, i8}` (allocation size 32 bytes) now correctly passes
as `[4 x i64]`.
Both now match the C ABI. Programs that pass `BIND(C)` `VALUE`
derived-type arguments of these shapes to C functions were already
producing incorrect results. Programs compiled entirely in Fortran
relying on the old convention must be recompiled.
Assisted-by: IBM Bob
Added:
flang/test/Lower/Intrinsics/transfer-rec-tail-pad-llvm.f90
flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
Modified:
flang/docs/ReleaseNotes.md
flang/include/flang/Optimizer/Dialect/FIRType.h
flang/lib/Optimizer/Builder/IntrinsicCall.cpp
flang/lib/Optimizer/Dialect/FIROps.cpp
flang/lib/Optimizer/Dialect/FIRType.cpp
flang/test/Fir/CUDA/cuda-constructor-2.f90
flang/test/Fir/CUDA/cuda-shared-offset.mlir
flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
flang/test/Fir/box-elesize-canonicalize.fir
flang/test/Fir/struct-passing-ppc64le-byval.fir
flang/test/Fir/struct-passing-systemz-reference.fir
flang/test/Transforms/allocation-placement.fir
Removed:
################################################################################
diff --git a/flang/docs/ReleaseNotes.md b/flang/docs/ReleaseNotes.md
index 76a6b95dd3ecc..ac302d7e75f61 100644
--- a/flang/docs/ReleaseNotes.md
+++ b/flang/docs/ReleaseNotes.md
@@ -31,6 +31,60 @@ page](https://llvm.org/releases/).
## Bug Fixes
+- Fixed `fir::getTypeSizeAndAlignment` returning the wrong allocation size for
+ **packed `fir::RecordType`s** (produced by the AIX lowering of `BIND(C)`
+ derived types, or declared directly in textual FIR). Fields in a packed
+ record are placed back-to-back using each component's allocation size
+ (`alignTo(storeSize, ABIalign)`), not its raw store size, and the record's
+ ABI alignment is 1. For example, a packed `{i32, f64}` on x86-64 now
+ correctly reports 12 bytes instead of 16.
+ ([#220377](https://github.com/llvm/llvm-project/pull/220377))
+
+- Fixed `fir::getTypeSizeAndAlignment` omitting **tail padding** from unpacked
+ derived types. The returned size is now rounded up to the record's own ABI
+ alignment, matching the allocation extent used by array element strides, CUDA
+ shared-memory layout, and stack/heap allocation placement. For example,
+ `{i32, i8}` (store size 5 bytes, align 4) now correctly reports 8 bytes
+ instead of 5.
+ ([#220377](https://github.com/llvm/llvm-project/pull/220377))
+
+- Fixed a **`BIND(C)` / `VALUE` argument-passing ABI bug** on SystemZ:
+ derived types whose allocation size fits in a GPR were incorrectly passed
+ indirectly (by reference) instead of as an integer register value, because
+ `getTypeSizeAndAlignment` was returning the unpadded store size rather than
+ the allocation size. For example, `{i32, i8}` (allocation size 8 bytes) is
+ now correctly passed as `i64`, and `{i16, i8}` (4 bytes) as `i32`, matching
+ the C ABI.
+ Fortran programs with `BIND(C)` `VALUE` derived-type arguments of these shapes
+ that interoperate with C were already producing incorrect results; programs
+ compiled entirely in Fortran that relied on the old (incorrect) convention
+ must be recompiled.
+ ([#220377](https://github.com/llvm/llvm-project/pull/220377))
+
+- Fixed a **`BIND(C)` / `VALUE` argument-passing ABI bug** on PPC64le:
+ derived types were classified using the unpadded store size rather than the
+ allocation size, producing the wrong number of GPR slots. The argument was
+ already passed by value; only the slot count was wrong. For example,
+ `{f128, i8}` (allocation size 32 bytes) is now correctly passed as
+ `[4 x i64]` instead of `[3 x i64]`, matching the C ABI.
+ Fortran programs with `BIND(C)` `VALUE` derived-type arguments of these shapes
+ that interoperate with C were already producing incorrect results; programs
+ compiled entirely in Fortran that relied on the old (incorrect) convention
+ must be recompiled.
+ ([#220377](https://github.com/llvm/llvm-project/pull/220377))
+
+- Fixed the `TRANSFER` intrinsic inline path to compare sizes using
+ `getTypeSizeAndAlignment` (which includes tail padding for `RecordType`,
+ matching the allocation extent used by `STORAGE_SIZE` and the runtime
+ `TRANSFER` path). For `RecordType` sources, the inline path now copies
+ the record data via `fir.copy` into result-aligned storage when the source
+ alignment is less than the result alignment, preserving both internal and
+ tail padding bytes and satisfying the F2023 16.9.212 requirement that the
+ result's physical representation be identical to the source's when both have
+ the same length.
+ ([#220377](https://github.com/llvm/llvm-project/pull/220377))
+
+
## Non-comprehensive list of changes in this release
- Added support for the OpenMP implementation-defined extension sentinels
diff --git a/flang/include/flang/Optimizer/Dialect/FIRType.h b/flang/include/flang/Optimizer/Dialect/FIRType.h
index 6684af86a33fa..4fd9638e60d46 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRType.h
+++ b/flang/include/flang/Optimizer/Dialect/FIRType.h
@@ -555,7 +555,34 @@ inline bool isRefOfConstantSizeAggregateType(mlir::Type t) {
std::string getTypeAsString(mlir::Type ty, const KindMapping &kindMap,
llvm::StringRef prefix = "");
-/// Return the size and alignment of FIR types.
+/// Return the allocation extent and ABI alignment of a FIR type.
+///
+/// The returned size includes padding required for allocation and array
+/// element strides.
+///
+/// - **Trivial scalars** (integer, real, complex, logical, character):
+/// size = dl.getTypeSize(), which is the *store* size (data bytes only,
+/// no tail padding). For example, x86 f80 has store size 10 and ABI
+/// alignment 16; the returned size is 10, not 16. Callers that need
+/// the allocation size (e.g. FoldBoxEleSize, array element strides)
+/// must round up: llvm::alignTo(size, alignment).
+///
+/// - **Sequences (fir::SequenceType)**:
+/// Each element size is rounded to its alignment boundary before
+/// multiplying by the element count (allocation-size stride). This
+/// rounding is part of the allocation extent and includes per-element
+/// allocation padding.
+///
+/// - **Packed records (fir::RecordType with isPacked)**:
+/// Fields are laid out back-to-back using each field's allocation size
+/// (llvm::alignTo(fieldStoreSize, fieldAlign)), matching LLVM's packed
+/// StructLayout. No inter-field or tail padding is added.
+///
+/// - **Unpacked records (fir::RecordType, not packed)**:
+/// Fields are laid out with inter-field alignment padding; each field
+/// occupies llvm::alignTo(fieldSize, fieldAlign) bytes. The allocation
+/// extent is rounded up to the record's own alignment (tail-padded).
+///
/// TODO: consider moving this to a DataLayoutTypeInterface implementation
/// for FIR types. It should first be ensured that it is OK to open the gate of
/// target dependent type size inquiries in lowering. It would also not be
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index 9408dc213e2e1..2bdbad156c0b0 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -8999,22 +8999,69 @@ IntrinsicLibrary::genTransfer(mlir::Type resultType,
(fir::isa_trivial(sourceType) ||
mlir::isa<fir::RecordType>(sourceType)) &&
fir::isa_trivial(moldType)) {
+ // Compare sizes from getTypeSizeAndAlignment. For RecordType, this
+ // includes tail padding to match the allocation extent used by
+ // STORAGE_SIZE and the TRANSFER runtime path. Alignment is handled
+ // separately: when the source alignment is less than the result type's
+ // alignment, the RecordType path below copies into a result-aligned
+ // alloca rather than loading directly from the source pointer.
auto sourceSizeAndAlign = fir::getTypeSizeAndAlignment(
loc, sourceType, builder.getDataLayout(), builder.getKindMap());
auto resultSizeAndAlign = fir::getTypeSizeAndAlignment(
loc, resultType, builder.getDataLayout(), builder.getKindMap());
if (sourceSizeAndAlign && resultSizeAndAlign &&
sourceSizeAndAlign->first == resultSizeAndAlign->first) {
- if (sourceType.isSignlessIntOrFloat() &&
- resultType.isSignlessIntOrFloat()) {
- mlir::Value val = fir::LoadOp::create(builder, loc, sourceBase);
- if (sourceType != resultType)
- val = mlir::arith::BitcastOp::create(builder, loc, resultType, val);
- return val;
+ if (fir::isa_trivial(sourceType)) {
+ // Both source and result are trivial scalars of the same store
+ // size. Use arith.bitcast for signless integer/float pairs;
+ // for other trivial types (e.g. unsigned integers) arith.bitcast
+ // is not available, so cast the source address and load.
+ if (sourceType.isSignlessIntOrFloat() &&
+ resultType.isSignlessIntOrFloat()) {
+ mlir::Value val = fir::LoadOp::create(builder, loc, sourceBase);
+ if (sourceType != resultType)
+ val =
+ mlir::arith::BitcastOp::create(builder, loc, resultType, val);
+ return val;
+ }
+ mlir::Type refTy = builder.getRefType(resultType);
+ mlir::Value cast = builder.createConvert(loc, refTy, sourceBase);
+ return fir::LoadOp::create(builder, loc, cast);
}
- mlir::Type refTy = builder.getRefType(resultType);
- mlir::Value cast = builder.createConvert(loc, refTy, sourceBase);
- return fir::LoadOp::create(builder, loc, cast);
+ // The source is a RecordType.
+ //
+ // When sourceAlign >= resultAlign, a direct address cast and load is
+ // safe: the existing source storage satisfies the result type's
+ // alignment requirement.
+ //
+ // When sourceAlign < resultAlign (e.g. {i32,i8} is 4-byte aligned
+ // while integer(8) requires 8-byte alignment), loading resultType
+ // directly from sourceBase would assert an over-aligned address and
+ // produce undefined behaviour. In that case, copy the allocation-size
+ // bytes into a result-typed alloca (which has resultType's natural
+ // alignment) using fir.copy (a non-overlapping byte copy, equivalent
+ // to memcpy), then load from the properly-aligned alloca.
+ //
+ // Note: fir.copy copies exactly sourceSizeAndAlign->first bytes (the
+ // allocation size, including tail padding). Inter-field and tail
+ // padding bytes of the record are preserved, matching the runtime copy
+ // width and satisfying F2023 16.9.212.
+ if (sourceSizeAndAlign->second >= resultSizeAndAlign->second) {
+ mlir::Type refTy = builder.getRefType(resultType);
+ mlir::Value cast = builder.createConvert(loc, refTy, sourceBase);
+ return fir::LoadOp::create(builder, loc, cast);
+ }
+ mlir::Value tmp = fir::AllocaOp::create(builder, loc, resultType);
+ mlir::Type byteType = fir::SequenceType::get(
+ {static_cast<int64_t>(sourceSizeAndAlign->first)},
+ builder.getI8Type());
+ mlir::Type byteRefType = builder.getRefType(byteType);
+ mlir::Value sourceBytes =
+ builder.createConvert(loc, byteRefType, sourceBase);
+ mlir::Value resultBytes = builder.createConvert(loc, byteRefType, tmp);
+ fir::CopyOp::create(builder, loc, sourceBytes, resultBytes,
+ /*noOverlap=*/true);
+ return fir::LoadOp::create(builder, loc, tmp);
}
}
}
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 5409b1c2da090..c0be8756bc592 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -1382,9 +1382,11 @@ struct FoldBoxEleSize : public mlir::OpRewritePattern<fir::BoxEleSizeOp> {
if (!sizeAndAlign)
return mlir::failure();
- // The descriptor stores the byte stride between elements (not the raw
- // natural size), so we must round up to alignment just as
- // fir::computeElementDistance does.
+ // The descriptor stores the byte stride between elements, which is the
+ // allocation size including tail padding. getTypeSizeAndAlignment already
+ // rounds up to alignment for RecordType, so llvm::alignTo here is a no-op
+ // for struct types; it is kept for scalar types (e.g. x86_fp80) where the
+ // rounding is still needed, matching fir::computeElementDistance.
auto [size, alignment] = *sizeAndAlign;
std::int64_t distance = llvm::alignTo(size, alignment);
diff --git a/flang/lib/Optimizer/Dialect/FIRType.cpp b/flang/lib/Optimizer/Dialect/FIRType.cpp
index 5f0dd68aa9396..297b43ed78f84 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1664,6 +1664,25 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
if (auto recTy = mlir::dyn_cast<fir::RecordType>(ty)) {
std::uint64_t size = 0;
unsigned short align = 1;
+ if (recTy.isPacked()) {
+ // LLVM packed structs (<{ ... }>) place fields back-to-back with no
+ // inter-field alignment padding and no tail padding. Each component
+ // still occupies its allocation size (llvm::alignTo(storeSize, ABI
+ // alignment)), because LLVM's packed StructLayout advances by
+ // getTypeAllocSize, not getTypeStoreSize. For example, x86 f80 has
+ // store size 10 bytes but ABI alignment 16 bytes, so its allocation
+ // size is 16 bytes; a packed {f80, i8} therefore occupies 17 bytes,
+ // not 11. The packed struct's own ABI alignment is always 1.
+ for (auto component : recTy.getTypeList()) {
+ auto result =
+ getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
+ if (!result)
+ return result;
+ auto [compSize, compAlign] = *result;
+ size += llvm::alignTo(compSize, compAlign); // alloc size per field
+ }
+ return std::pair{size, static_cast<unsigned short>(1)};
+ }
for (auto component : recTy.getTypeList()) {
auto result = getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
if (!result)
@@ -1673,6 +1692,8 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
llvm::alignTo(size, compAlign) + llvm::alignTo(compSize, compAlign);
align = std::max(align, compAlign);
}
+ // Round up to the record's alignment to include outermost tail padding.
+ size = llvm::alignTo(size, align);
return std::pair{size, align};
}
if (auto logical = mlir::dyn_cast<fir::LogicalType>(ty)) {
diff --git a/flang/test/Fir/CUDA/cuda-constructor-2.f90 b/flang/test/Fir/CUDA/cuda-constructor-2.f90
index 11ab6a5a79ec1..84ada65887e1e 100644
--- a/flang/test/Fir/CUDA/cuda-constructor-2.f90
+++ b/flang/test/Fir/CUDA/cuda-constructor-2.f90
@@ -358,3 +358,75 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<i8 = dense<8> : vector<2xi64>, i
// CHECK-NOT: cuf.register_module
// CHECK-NOT: fir.call @_FortranACUFRegisterVariable
// CHECK: llvm.mlir.global_ctors ctors = [@__cudaFortranConstructor]
+
+// -----
+
+// Tail-padded device global: a record type {i32, i8} has 5 typed bytes but
+// 8 allocation bytes (3 bytes of tail padding for i32 alignment).
+// CUFAddConstructor must register the full allocation size (8), not the raw
+// typed size (5), so the CUDA runtime maps the correct number of bytes.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<!llvm.ptr, dense<64> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<271>, dense<32> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<270>, dense<32> : vector<4xi64>>, #dlti.dl_entry<f128, dense<128> : vector<2xi64>>, #dlti.dl_entry<f64, dense<64> : vector<2xi64>>, #dlti.dl_entry<f80, dense<128> : vector<2xi64>>, #dlti.dl_entry<f16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i32, dense<32> : vector<2xi64>>, #dlti.dl_entry<i16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i128, dense<128> : vector<2xi64>>, #dlti.dl_entry<i8, dense<8> : vector<2xi64>>, #dlti.dl_entry<!llvm.ptr<272>, dense<64> : vector<4xi64>>, #dlti.dl_entry<i64, dense<64> : vector<2xi64>>, #dlti.dl_entry<i1, dense<8> : vector<2xi64>>, #dlti.dl_entry<"dlti.endianness", "little">, #dlti.dl_entry<"dlti.stack_alignment", 128 : i64>>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.target_triple = "x86_64-unknown-linux-gnu"} {
+
+ fir.global @_QMtestEtp_dev {data_attr = #cuf.cuda<device>} : !fir.type<tp{a:i32,b:i8}> {
+ %0 = fir.zero_bits !fir.type<tp{a:i32,b:i8}>
+ fir.has_value %0 : !fir.type<tp{a:i32,b:i8}>
+ }
+
+ gpu.module @cuda_device_mod {
+ gpu.func @_QMtestPkernel() kernel {
+ gpu.return
+ }
+ fir.global @_QMtestEtp_dev {data_attr = #cuf.cuda<device>} : !fir.type<tp{a:i32,b:i8}> {
+ %0 = fir.zero_bits !fir.type<tp{a:i32,b:i8}>
+ fir.has_value %0 : !fir.type<tp{a:i32,b:i8}>
+ }
+ }
+}
+
+// Registered size must be 8 (allocation size including tail padding), not 5
+// (raw typed size). A wrong value of 5 would cause the CUDA runtime to map
+// too few bytes and leave the 3 tail-padding bytes unmapped.
+// NOUNIFIED-LABEL: fir.global @_QMtestEtp_dev
+// NOUNIFIED: llvm.func internal @__cudaFortranConstructor() {
+// NOUNIFIED-DAG: %[[TPDEV:.*]] = fir.address_of(@_QMtestEtp_dev) : !fir.ref<!fir.type<tp{a:i32,b:i8}>>
+// NOUNIFIED-DAG: %[[TPDEV2:.*]] = fir.convert %[[TPDEV]] : (!fir.ref<!fir.type<tp{a:i32,b:i8}>>) -> !fir.ref<i8>
+// NOUNIFIED-DAG: %[[SZ8:.*]] = arith.constant 8 : index
+// NOUNIFIED-DAG: %[[SZ8I64:.*]] = fir.convert %[[SZ8]] : (index) -> i64
+// NOUNIFIED-DAG: fir.call @_FortranACUFRegisterVariable(%{{.*}}, %[[TPDEV2]], %{{.*}}, %[[SZ8I64]])
+// UNIFIED: cuf.register_variable_static @_QMtestEtp_dev("_QMtestEtp_dev", 8) {deviceResident}
+
+// -----
+
+// Packed device global: a packed record type <{i32, f64}> has no alignment
+// gaps and no tail padding; its size is 4+8=12 bytes (not 16, which would be
+// the aligned size of an unpacked {i32, f64}).
+// CUFAddConstructor must register size 12, not 16.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<!llvm.ptr, dense<64> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<271>, dense<32> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<270>, dense<32> : vector<4xi64>>, #dlti.dl_entry<f128, dense<128> : vector<2xi64>>, #dlti.dl_entry<f64, dense<64> : vector<2xi64>>, #dlti.dl_entry<f80, dense<128> : vector<2xi64>>, #dlti.dl_entry<f16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i32, dense<32> : vector<2xi64>>, #dlti.dl_entry<i16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i128, dense<128> : vector<2xi64>>, #dlti.dl_entry<i8, dense<8> : vector<2xi64>>, #dlti.dl_entry<!llvm.ptr<272>, dense<64> : vector<4xi64>>, #dlti.dl_entry<i64, dense<64> : vector<2xi64>>, #dlti.dl_entry<i1, dense<8> : vector<2xi64>>, #dlti.dl_entry<"dlti.endianness", "little">, #dlti.dl_entry<"dlti.stack_alignment", 128 : i64>>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.target_triple = "x86_64-unknown-linux-gnu"} {
+
+ fir.global @_QMtestEtp_packed_dev {data_attr = #cuf.cuda<device>} : !fir.type<tp<{i:i32,d:f64}>> {
+ %0 = fir.zero_bits !fir.type<tp<{i:i32,d:f64}>>
+ fir.has_value %0 : !fir.type<tp<{i:i32,d:f64}>>
+ }
+
+ gpu.module @cuda_device_mod {
+ gpu.func @_QMtestPkernel() kernel {
+ gpu.return
+ }
+ fir.global @_QMtestEtp_packed_dev {data_attr = #cuf.cuda<device>} : !fir.type<tp<{i:i32,d:f64}>> {
+ %0 = fir.zero_bits !fir.type<tp<{i:i32,d:f64}>>
+ fir.has_value %0 : !fir.type<tp<{i:i32,d:f64}>>
+ }
+ }
+}
+
+// Registered size must be 12 (packed: 4+8), not 16 (aligned unpacked size).
+// NOUNIFIED-LABEL: fir.global @_QMtestEtp_packed_dev
+// NOUNIFIED: llvm.func internal @__cudaFortranConstructor() {
+// NOUNIFIED-DAG: %[[TPPKDEV:.*]] = fir.address_of(@_QMtestEtp_packed_dev) : !fir.ref<!fir.type<tp<{i:i32,d:f64}>>>
+// NOUNIFIED-DAG: %[[TPPKDEV2:.*]] = fir.convert %[[TPPKDEV]] : (!fir.ref<!fir.type<tp<{i:i32,d:f64}>>>) -> !fir.ref<i8>
+// NOUNIFIED-DAG: %[[SZ12:.*]] = arith.constant 12 : index
+// NOUNIFIED-DAG: %[[SZ12I64:.*]] = fir.convert %[[SZ12]] : (index) -> i64
+// NOUNIFIED-DAG: fir.call @_FortranACUFRegisterVariable(%{{.*}}, %[[TPPKDEV2]], %{{.*}}, %[[SZ12I64]])
+// UNIFIED: cuf.register_variable_static @_QMtestEtp_packed_dev("_QMtestEtp_packed_dev", 12) {deviceResident}
diff --git a/flang/test/Fir/CUDA/cuda-shared-offset.mlir b/flang/test/Fir/CUDA/cuda-shared-offset.mlir
index 68c31356a278b..8b5234f34619d 100644
--- a/flang/test/Fir/CUDA/cuda-shared-offset.mlir
+++ b/flang/test/Fir/CUDA/cuda-shared-offset.mlir
@@ -235,3 +235,47 @@ module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<!llvm.ptr, dense<
// CHECK-LABEL: gpu.func @_QPt1()
// CHECK: fir.global internal @_QPt1__shared_mem__s {alignment = 4 : i64, data_attr = #cuf.cuda<shared>} : !fir.array<1024xi8>
// CHECK: fir.global external @_QPt1__shared_mem__ {alignment = 4 : i64, data_attr = #cuf.cuda<shared>} : !fir.array<0xi8>
+
+// -----
+
+// Tail-padded record in shared memory: !fir.type<tp{a:i32,b:i8}> has
+// 5 typed bytes but 8 allocation bytes (3 bytes of tail padding).
+// The shared memory global must be allocated as !fir.array<8xi8>, not
+// !fir.array<5xi8>, so subsequent shared variables start at the correct
+// aligned offset.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<!llvm.ptr, dense<64> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<271>, dense<32> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<270>, dense<32> : vector<4xi64>>, #dlti.dl_entry<f128, dense<128> : vector<2xi64>>, #dlti.dl_entry<f64, dense<64> : vector<2xi64>>, #dlti.dl_entry<f80, dense<128> : vector<2xi64>>, #dlti.dl_entry<f16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i32, dense<32> : vector<2xi64>>, #dlti.dl_entry<i16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i128, dense<128> : vector<2xi64>>, #dlti.dl_entry<i8, dense<8> : vector<2xi64>>, #dlti.dl_entry<!llvm.ptr<272>, dense<64> : vector<4xi64>>, #dlti.dl_entry<i64, dense<64> : vector<2xi64>>, #dlti.dl_entry<i1, dense<8> : vector<2xi64>>, #dlti.dl_entry<"dlti.endianness", "little">, #dlti.dl_entry<"dlti.stack_alignment", 128 : i64>>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.target_triple = "x86_64-unknown-linux-gnu"} {
+ gpu.module @cuda_device_mod {
+ gpu.func @_QPshared_tail_padded() attributes {cuf.proc_attr = #cuf.cuda_proc<global>} {
+ %0 = cuf.shared_memory !fir.type<tp{a:i32,b:i8}> {bindc_name = "x", uniq_name = "_QFshared_tail_paddedEx"} -> !fir.ref<!fir.type<tp{a:i32,b:i8}>>
+ %1 = fir.declare %0 {data_attr = #cuf.cuda<shared>, uniq_name = "_QFshared_tail_paddedEx"} : (!fir.ref<!fir.type<tp{a:i32,b:i8}>>) -> !fir.ref<!fir.type<tp{a:i32,b:i8}>>
+ gpu.return
+ }
+ }
+}
+
+// Shared memory global must use the allocation size (8), not the typed
+// size (5). Using 5 would misalign any variable placed after this one.
+// CHECK-LABEL: gpu.func @_QPshared_tail_padded()
+// CHECK: fir.global internal @_QPshared_tail_padded__shared_mem__x {alignment = 4 : i64, data_attr = #cuf.cuda<shared>} : !fir.array<8xi8>
+
+// -----
+
+// Packed record in shared memory: !fir.type<tp<{i:i32,d:f64}>> has no
+// alignment gaps and no tail padding; its size is 4+8=12 bytes (not 16).
+// The shared memory global must be !fir.array<12xi8>, not !fir.array<16xi8>.
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<#dlti.dl_entry<!llvm.ptr, dense<64> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<271>, dense<32> : vector<4xi64>>, #dlti.dl_entry<!llvm.ptr<270>, dense<32> : vector<4xi64>>, #dlti.dl_entry<f128, dense<128> : vector<2xi64>>, #dlti.dl_entry<f64, dense<64> : vector<2xi64>>, #dlti.dl_entry<f80, dense<128> : vector<2xi64>>, #dlti.dl_entry<f16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i32, dense<32> : vector<2xi64>>, #dlti.dl_entry<i16, dense<16> : vector<2xi64>>, #dlti.dl_entry<i128, dense<128> : vector<2xi64>>, #dlti.dl_entry<i8, dense<8> : vector<2xi64>>, #dlti.dl_entry<!llvm.ptr<272>, dense<64> : vector<4xi64>>, #dlti.dl_entry<i64, dense<64> : vector<2xi64>>, #dlti.dl_entry<i1, dense<8> : vector<2xi64>>, #dlti.dl_entry<"dlti.endianness", "little">, #dlti.dl_entry<"dlti.stack_alignment", 128 : i64>>, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", gpu.container_module, llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.target_triple = "x86_64-unknown-linux-gnu"} {
+ gpu.module @cuda_device_mod {
+ gpu.func @_QPshared_packed() attributes {cuf.proc_attr = #cuf.cuda_proc<global>} {
+ %0 = cuf.shared_memory !fir.type<tp<{i:i32,d:f64}>> {bindc_name = "x", uniq_name = "_QFshared_packedEx"} -> !fir.ref<!fir.type<tp<{i:i32,d:f64}>>>
+ %1 = fir.declare %0 {data_attr = #cuf.cuda<shared>, uniq_name = "_QFshared_packedEx"} : (!fir.ref<!fir.type<tp<{i:i32,d:f64}>>>) -> !fir.ref<!fir.type<tp<{i:i32,d:f64}>>>
+ gpu.return
+ }
+ }
+}
+
+// Shared memory global must use the packed size (12), not the aligned
+// unpacked size (16).
+// CHECK-LABEL: gpu.func @_QPshared_packed()
+// CHECK: fir.global internal @_QPshared_packed__shared_mem__x {alignment = 1 : i64, data_attr = #cuf.cuda<shared>} : !fir.array<12xi8>
diff --git a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
index 4f5d9dd2b44cd..9c0395f01d6ef 100644
--- a/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
+++ b/flang/test/Fir/OpenACC/acc-fir-map-info-prep-privatize.mlir
@@ -51,15 +51,16 @@ func.func @private_without_par_dims() {
// -----
-// A record element uses the padded stride: real(8) + real(4) has a size of 12
-// and an alignment of 8, so consecutive elements are 16 bytes apart.
+// A record element uses the padded stride: real(8) + real(4) has a stored
+// representation of 12 bytes and an alignment of 8, so consecutive elements
+// are 16 bytes apart (allocation extent rounded up to alignment).
// CHECK-LABEL: func.func @private_static_record
// CHECK: %[[PRIV:.*]] = acc.privatize
// CHECK: %[[SIZE:.*]] = arith.constant 128 : i64
// CHECK: acc.map_info varPtr(%[[PRIV]]
// CHECK-SAME: size(%[[SIZE]] : i64)
-// CHECK-SAME: elementSize(12)
+// CHECK-SAME: elementSize(16)
// CHECK-SAME: mapFlags(private)
func.func @private_static_record() {
%priv = acc.privatize par_dims(#acc<par_dims[]>)
diff --git a/flang/test/Fir/box-elesize-canonicalize.fir b/flang/test/Fir/box-elesize-canonicalize.fir
index dd2b06ed3ef77..4a6a5532d27a5 100644
--- a/flang/test/Fir/box-elesize-canonicalize.fir
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -102,4 +102,67 @@ module attributes { dlti.dl_spec = #dlti.dl_spec< i1 = dense<8> : vector<2xi64>,
// CHECK: %[[C:.*]] = arith.constant 16 : index
// CHECK: return %[[C]]
// CHECK-NOT: fir.box_elesize
+
+ // Fold: packed derived type -- fir.type<tp<{i:i32,d:f64}>>.
+ // This is the exact example from MattPD's review: on x86-64
+ // (f64 ABI align=8B) the non-packed size is 16B and the packed
+ // size is 12B (i32 allocSize=4B + f64 allocSize=alignTo(8,8)=8B,
+ // no inter-field gap, no tail padding).
+ // The element distance must be 12, not 16.
+ func.func @fold_packed_record(%arg0: !fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp<{i:i32,d:f64}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_packed_record(
+ // CHECK: %[[C:.*]] = arith.constant 12 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
+
+ // Fold: packed derived type with a wider alignment gap.
+ // On x86-64: i32 store=4B, complex<f64> store=16B.
+ // Non-packed: alignTo(4,4)=4 + alignTo(4,8)+alignTo(16,8)=24, tail=24 -> 24B.
+ // Packed: 4 + 16 = 20B, align=1.
+ // The element distance must be 20, not 24.
+ func.func @fold_packed_record2(%arg0: !fir.box<!fir.type<tp2<{i:i32,z:complex<f64>}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp2<{i:i32,z:complex<f64>}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_packed_record2(
+ // CHECK: %[[C:.*]] = arith.constant 20 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
+
+ // Fold: tail-padded record type -- fir.type<tpad{a:i32,b:i8}>.
+ // The field loop gives size=5, align=4; getTypeSizeAndAlignment returns
+ // {8, 4} (alignTo(5,4)=8). FoldBoxEleSize also rounded 5 to 8 before
+ // this fix, so both revisions fold to 8. This check verifies that the
+ // correct 8-byte element stride is produced; the distinction between
+ // allocation size and raw store size is covered by
+ // flang/test/Fir/CUDA/cuda-constructor-2.f90 and
+ // flang/test/Fir/CUDA/cuda-shared-offset.mlir.
+ // Use a distinct type name (tpad) to avoid redefining the packed type (tp)
+ // used above.
+ func.func @fold_tail_padded_record(%arg0: !fir.box<!fir.array<?x!fir.type<tpad{a:i32,b:i8}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.array<?x!fir.type<tpad{a:i32,b:i8}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_tail_padded_record(
+ // CHECK: %[[C:.*]] = arith.constant 8 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
+
+ // Fold: packed derived type with f80 component.
+ // f80 on x86-64: store size = 10B, ABI alignment = 16B,
+ // allocation size = alignTo(10, 16) = 16B.
+ // i8: store size = 1B, ABI alignment = 1B, allocation size = 1B.
+ // Packed size = 16 + 1 = 17B (not 10 + 1 = 11B).
+ // This case distinguishes allocation-size from store-size in the packed path.
+ func.func @fold_packed_f80_i8(%arg0: !fir.box<!fir.type<tp3<{x:f80,b:i8}>>>) -> index {
+ %0 = fir.box_elesize %arg0 : (!fir.box<!fir.type<tp3<{x:f80,b:i8}>>>) -> index
+ return %0 : index
+ }
+ // CHECK-LABEL: func.func @fold_packed_f80_i8(
+ // CHECK: %[[C:.*]] = arith.constant 17 : index
+ // CHECK: return %[[C]]
+ // CHECK-NOT: fir.box_elesize
}
diff --git a/flang/test/Fir/struct-passing-ppc64le-byval.fir b/flang/test/Fir/struct-passing-ppc64le-byval.fir
index b9be67fbe97bf..3717cb7d000d8 100644
--- a/flang/test/Fir/struct-passing-ppc64le-byval.fir
+++ b/flang/test/Fir/struct-passing-ppc64le-byval.fir
@@ -79,4 +79,12 @@ func.func @csub5(%arg0: !fir.type<_QFcsub5Tdt1{xdt0:!fir.type<_QFcsub5Tdt0{f1:co
func.func @csub6(%arg0: !fir.type<_QFcsub6Tdt1{xdt0:!fir.type<_QFcsub6Tdt0{f1:complex<f32>}>,x1:f64}> {fir.bindc_name = "arg"}) attributes {fir.bindc_name = "csub6"} { return }
//CHECK-LABEL: func.func @csub6(%arg0: !fir.array<2xi64> {fir.bindc_name = "arg"}) attributes {fir.bindc_name = "csub6"}
+// tail-padded struct: f128 (16B, align 16) + i8 (1B, align 1).
+// Store size = 17B; allocation size = alignTo(17, 16) = 32B.
+// nElem = ceil(32*8/64) = 4 -> [4 x i64].
+// clang --target=powerpc64le-unknown-linux-gnu confirms [2 x i128] = 4 doublewords.
+// The old unrounded size (17B) gave nElem=3 ([3 x i64]) -- a live C-interop ABI bug.
+func.func @csub_tailpad_f128_i8(%arg0: !fir.type<t{x:f128,c:i8}> {fir.bindc_name = "arg"}) attributes {fir.bindc_name = "csub_tailpad_f128_i8"} { return }
+//CHECK-LABEL: func.func @csub_tailpad_f128_i8(%arg0: !fir.array<4xi64> {fir.bindc_name = "arg"}) attributes {fir.bindc_name = "csub_tailpad_f128_i8"}
+
}
diff --git a/flang/test/Fir/struct-passing-systemz-reference.fir b/flang/test/Fir/struct-passing-systemz-reference.fir
index 353bd8c47292f..1e425ef6e66fd 100644
--- a/flang/test/Fir/struct-passing-systemz-reference.fir
+++ b/flang/test/Fir/struct-passing-systemz-reference.fir
@@ -102,3 +102,37 @@ func.func @test_call_large(%arg0: !fir.ref<!fir.type<t5{i:!fir.array<8xi32>}>>)
return
}
+// BIND(C) records with tail padding are passed in the corresponding GPR size.
+// CHECK-LABEL: func.func private @test_i32_i8(%arg0: i64)
+func.func private @test_i32_i8(%arg0: !fir.type<t11{i:i32,c:i8}>) { return }
+
+// CHECK-LABEL: func.func private @test_i16_i8(%arg0: i32)
+func.func private @test_i16_i8(%arg0: !fir.type<t12{i:i16,c:i8}>) { return }
+
+// CHECK-LABEL: func.func @test_call_i32_i8(
+// CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.type<t11{i:i32,c:i8}>>) {
+func.func @test_call_i32_i8(%arg0: !fir.ref<!fir.type<t11{i:i32,c:i8}>>) {
+ // CHECK: %[[IN:.*]] = fir.load %[[ARG0]] : !fir.ref<!fir.type<t11{i:i32,c:i8}>>
+ %in = fir.load %arg0 : !fir.ref<!fir.type<t11{i:i32,c:i8}>>
+ // CHECK: %[[TMP:.*]] = fir.alloca i64
+ // CHECK: %[[CVT:.*]] = fir.convert %[[TMP]] : (!fir.ref<i64>) -> !fir.ref<!fir.type<t11{i:i32,c:i8}>>
+ // CHECK: fir.store %[[IN]] to %[[CVT]] : !fir.ref<!fir.type<t11{i:i32,c:i8}>>
+ // CHECK: %[[VAL:.*]] = fir.load %[[TMP]] : !fir.ref<i64>
+ // CHECK: fir.call @test_i32_i8(%[[VAL]]) : (i64) -> ()
+ fir.call @test_i32_i8(%in) : (!fir.type<t11{i:i32,c:i8}>) -> ()
+ return
+}
+
+// CHECK-LABEL: func.func @test_call_i16_i8(
+// CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.type<t12{i:i16,c:i8}>>) {
+func.func @test_call_i16_i8(%arg0: !fir.ref<!fir.type<t12{i:i16,c:i8}>>) {
+ // CHECK: %[[IN:.*]] = fir.load %[[ARG0]] : !fir.ref<!fir.type<t12{i:i16,c:i8}>>
+ %in = fir.load %arg0 : !fir.ref<!fir.type<t12{i:i16,c:i8}>>
+ // CHECK: %[[TMP:.*]] = fir.alloca i32
+ // CHECK: %[[CVT:.*]] = fir.convert %[[TMP]] : (!fir.ref<i32>) -> !fir.ref<!fir.type<t12{i:i16,c:i8}>>
+ // CHECK: fir.store %[[IN]] to %[[CVT]] : !fir.ref<!fir.type<t12{i:i16,c:i8}>>
+ // CHECK: %[[VAL:.*]] = fir.load %[[TMP]] : !fir.ref<i32>
+ // CHECK: fir.call @test_i16_i8(%[[VAL]]) : (i32) -> ()
+ fir.call @test_i16_i8(%in) : (!fir.type<t12{i:i16,c:i8}>) -> ()
+ return
+}
diff --git a/flang/test/Lower/Intrinsics/transfer-rec-tail-pad-llvm.f90 b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad-llvm.f90
new file mode 100644
index 0000000000000..989eea5590972
--- /dev/null
+++ b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad-llvm.f90
@@ -0,0 +1,29 @@
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s
+! REQUIRES: x86-registered-target
+
+! Regression test for https://github.com/llvm/llvm-project/pull/220377
+!
+! The source record has a 5-byte stored representation and 8-byte allocation
+! extent (including 3 bytes of tail padding). TRANSFER to integer(8) must copy
+! all 8 bytes into result-aligned storage before loading the result.
+module m
+ type :: t
+ integer(4) :: a
+ integer(1) :: b
+ end type
+end module
+
+subroutine transfer_rec_to_int8(out)
+ use m
+ type(t) :: src
+ integer(8) :: out
+ src%a = 42
+ src%b = 7_1
+ out = transfer(src, out)
+end subroutine
+
+! CHECK-LABEL: define{{.*}} @transfer_rec_to_int8_(
+! CHECK-DAG: %[[TMP:.*]] = alloca i64{{.*}}, align 8
+! CHECK-DAG: %[[SRC:.*]] = alloca %_QMmTt,
+! CHECK: call void @llvm.memcpy.p0.p0.i64(ptr %[[TMP]], ptr %[[SRC]], i64 8, i1 false)
+! CHECK: %[[RESULT:.*]] = load i64, ptr %[[TMP]], align 8
diff --git a/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90 b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
new file mode 100644
index 0000000000000..d7f5b148baaa4
--- /dev/null
+++ b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
@@ -0,0 +1,89 @@
+! RUN: %flang_fc1 -triple x86_64-unknown-linux-gnu -emit-hlfir %s -o - | FileCheck %s
+! REQUIRES: x86-registered-target
+
+! Regression test for https://github.com/llvm/llvm-project/pull/220377
+!
+! The TRANSFER inline gate compares sizes from getTypeSizeAndAlignment.
+! For derived types, this includes tail padding, matching the allocation extent
+! used by STORAGE_SIZE and the TRANSFER runtime path.
+!
+! All checks are at the HLFIR level; LLVM IR lowering is covered by transfer-rec-tail-pad-llvm.f90.
+
+! Shape 1: tail-padded record whose allocation size matches integer(8).
+! t1 fields: integer(4) [4B, align 4] + integer(1) [1B, align 1]
+! Store size = 5B; allocation size = alignTo(5, 4) = 8B.
+! integer(8) store size = 8B, align = 8B.
+! 8 == 8 on allocation size -> INLINES via fir.copy into an 8-byte aligned alloca.
+! fir.copy copies the full 8 bytes (including 3 tail-padding bytes), matching
+! the runtime copy width and preserving physical representation.
+module m1
+ type :: t1
+ integer(4) :: a
+ integer(1) :: b
+ end type
+end module
+
+! Shape 2: tail-padded record whose allocation size (16B) does not match real(10) store size (10B).
+! t2 fields: integer(8) [8B, align 8] + integer(2) [2B, align 2]
+! Store size = 10B; allocation size = alignTo(10, 8) = 16B.
+! real(10) = f80 on x86-64: store size = 10B, align = 16B.
+! 16 != 10 on size comparison -> stays on RUNTIME path (_FortranATransfer).
+module m2
+ type :: t2
+ integer(8) :: a
+ integer(2) :: b
+ end type
+end module
+
+subroutine transfer_rec_to_int8(res)
+ ! CHECK-LABEL: func @_QPtransfer_rec_to_int8(
+ ! CHECK-NOT: fir.call @_FortranATransfer
+ ! CHECK: %[[TMP:.*]] = fir.alloca i64
+ ! CHECK: %[[SRC_BYTES:.*]] = fir.convert {{.*}} : (!fir.ref<!fir.type<{{.*}}>>) -> !fir.ref<!fir.array<8xi8>>
+ ! CHECK: %[[DST_BYTES:.*]] = fir.convert %[[TMP]] : (!fir.ref<i64>) -> !fir.ref<!fir.array<8xi8>>
+ ! CHECK: fir.copy %[[SRC_BYTES]] to %[[DST_BYTES]] no_overlap : !fir.ref<!fir.array<8xi8>>, !fir.ref<!fir.array<8xi8>>
+ ! CHECK: fir.load %[[TMP]] : !fir.ref<i64>
+ ! CHECK: return
+ use m1
+ type(t1) :: x
+ integer(8) :: res
+ x%a = 1
+ x%b = 2_1
+ res = transfer(x, res)
+end subroutine
+
+subroutine transfer_rec_to_real10(out)
+ ! CHECK-LABEL: func @_QPtransfer_rec_to_real10(
+ ! CHECK: fir.call @_FortranATransfer
+ ! CHECK-NOT: fir.load {{.*}} : !fir.ref<f80>
+ ! CHECK: return
+ use m2
+ type(t2) :: src
+ real(10) :: out
+ src%a = 42
+ src%b = 7
+ out = transfer(src, out)
+end subroutine
+
+! A BIND(C) record may contain internal padding. TRANSFER must copy the
+! physical bytes rather than loading and storing the record aggregate, since
+! the latter can replace padding bytes with undef.
+! This test covers HLFIR data flow only: the RUN line emits HLFIR and does
+! not execute the subroutine, so correctness of the byte values is not
+! verified here.
+subroutine transfer_bindc_record_to_int128(res)
+ ! CHECK-LABEL: func @_QPtransfer_bindc_record_to_int128(
+ ! CHECK: %[[TMP128:.*]] = fir.alloca i128
+ ! CHECK: %[[SRC_BYTES128:.*]] = fir.convert {{.*}} : (!fir.ref<!fir.type<{{.*}}>>) -> !fir.ref<!fir.array<16xi8>>
+ ! CHECK: %[[DST_BYTES128:.*]] = fir.convert %[[TMP128]] : (!fir.ref<i128>) -> !fir.ref<!fir.array<16xi8>>
+ ! CHECK: fir.copy %[[SRC_BYTES128]] to %[[DST_BYTES128]] no_overlap : !fir.ref<!fir.array<16xi8>>, !fir.ref<!fir.array<16xi8>>
+ ! CHECK: fir.load %[[TMP128]] : !fir.ref<i128>
+ use iso_c_binding, only: c_int8_t, c_int64_t
+ type, bind(c) :: t
+ integer(c_int8_t) :: first
+ integer(c_int64_t) :: rest
+ end type
+ type(t) :: source
+ integer(16) :: res
+ res = transfer(source, res)
+end subroutine
diff --git a/flang/test/Transforms/allocation-placement.fir b/flang/test/Transforms/allocation-placement.fir
index ec70af24e5d13..aa09d830fd5a5 100644
--- a/flang/test/Transforms/allocation-placement.fir
+++ b/flang/test/Transforms/allocation-placement.fir
@@ -102,4 +102,48 @@ func.func @small_temp_in_acc_parallel() {
return
}
+// Tail-padded record array: !fir.array<3x!fir.type<tpad{a:i32,b:i8}>>
+// Each element is 8 bytes (5 store + 3 tail padding), total = 24 bytes.
+// SequenceType rounds each element to its alignment boundary (alignTo(5,4)=8)
+// before multiplying by the element count, so both the pre-fix and post-fix
+// code produce 24 bytes. This case verifies that per-element SequenceType
+// rounding is correct, not that the tail-padding fix changes the total.
+// With the default 64-byte threshold the 24-byte array is promoted to stack.
+// CHECK-LABEL: func.func @tail_padded_record_placement
+// CHECK: fir.alloca !fir.array<3x!fir.type<tpad{a:i32,b:i8}>>
+// CHECK-NOT: fir.allocmem
+func.func @tail_padded_record_placement() {
+ %0 = fir.allocmem !fir.array<3x!fir.type<tpad{a:i32,b:i8}>>
+ %c0 = arith.constant 0 : index
+ %v = arith.constant 0 : i32
+ %r = fir.convert %0 : (!fir.heap<!fir.array<3x!fir.type<tpad{a:i32,b:i8}>>>) -> !fir.ref<!fir.array<3x!fir.type<tpad{a:i32,b:i8}>>>
+ %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<3x!fir.type<tpad{a:i32,b:i8}>>>, index) -> !fir.ref<!fir.type<tpad{a:i32,b:i8}>>
+ %f = fir.coordinate_of %e, %c0 : (!fir.ref<!fir.type<tpad{a:i32,b:i8}>>, index) -> !fir.ref<i32>
+ fir.store %v to %f : !fir.ref<i32>
+ fir.freemem %0 : !fir.heap<!fir.array<3x!fir.type<tpad{a:i32,b:i8}>>>
+ return
+}
+
+// Packed record array: !fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>
+// Each element is 12 bytes (packed: 4+8, no gaps or tail padding), total=60B.
+// Without the isPacked fix, getTypeSizeAndAlignment returns 16 per element
+// (aligned like an unpacked struct), giving a wrong total of 80 bytes which
+// exceeds the 64-byte threshold and would select heap placement. With the
+// fix the correct total of 60 bytes is below the threshold and the array
+// must be promoted to stack.
+// CHECK-LABEL: func.func @packed_record_placement
+// CHECK: fir.alloca !fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>
+// CHECK-NOT: fir.allocmem
+func.func @packed_record_placement() {
+ %0 = fir.allocmem !fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>
+ %c0 = arith.constant 0 : index
+ %v = arith.constant 0 : i32
+ %r = fir.convert %0 : (!fir.heap<!fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>>) -> !fir.ref<!fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>>
+ %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>>, index) -> !fir.ref<!fir.type<tpk<{i:i32,d:f64}>>>
+ %f = fir.coordinate_of %e, %c0 : (!fir.ref<!fir.type<tpk<{i:i32,d:f64}>>>, index) -> !fir.ref<i32>
+ fir.store %v to %f : !fir.ref<i32>
+ fir.freemem %0 : !fir.heap<!fir.array<5x!fir.type<tpk<{i:i32,d:f64}>>>>
+ return
+}
+
}
More information about the flang-commits
mailing list