[flang-commits] [flang] [flang] Fix `getTypeSizeAndAlignment` for packed/tail-padded `RecordType` and `TRANSFER` gate (PR #220377)
Daniel Chen via flang-commits
flang-commits at lists.llvm.org
Thu Sep 3 18:02:35 PDT 2026
https://github.com/DanielCChen updated https://github.com/llvm/llvm-project/pull/220377
>From 1cb6fea4e527fd2021e78f79d59d44dc965524b0 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 3 Sep 2026 17:22:35 -0400
Subject: [PATCH 1/2] [flang] Fix getTypeSizeAndAlignment for RecordType
Handle packed records and tail padding correctly when computing allocation extents. Keep the stored-representation width used by TRANSFER distinct from allocation size, and use aligned temporary storage when a record source is less aligned than the trivial result type.
Update affected callers and tests, including FIR box element sizing, allocation placement, CUDA device-global and shared-memory sizing, PPC64le and SystemZ argument passing, and TRANSFER lowering.
---
.../include/flang/Optimizer/Dialect/FIRType.h | 44 ++++++++++-
flang/lib/Optimizer/Builder/IntrinsicCall.cpp | 64 +++++++++++++---
flang/lib/Optimizer/Dialect/FIROps.cpp | 8 +-
flang/lib/Optimizer/Dialect/FIRType.cpp | 40 ++++++++--
flang/test/Fir/CUDA/cuda-constructor-2.f90 | 62 ++++++++++++++++
flang/test/Fir/CUDA/cuda-shared-offset.mlir | 44 +++++++++++
flang/test/Fir/box-elesize-canonicalize.fir | 63 ++++++++++++++++
.../test/Fir/struct-passing-ppc64le-byval.fir | 8 ++
.../Intrinsics/transfer-rec-tail-pad.f90 | 74 +++++++++++++++++++
.../test/Transforms/allocation-placement.fir | 44 +++++++++++
10 files changed, 426 insertions(+), 25 deletions(-)
create mode 100644 flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
diff --git a/flang/include/flang/Optimizer/Dialect/FIRType.h b/flang/include/flang/Optimizer/Dialect/FIRType.h
index 6684af86a33fa..a01e6ddcb5c97 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRType.h
+++ b/flang/include/flang/Optimizer/Dialect/FIRType.h
@@ -556,6 +556,41 @@ std::string getTypeAsString(mlir::Type ty, const KindMapping &kindMap,
llvm::StringRef prefix = "");
/// Return the size and alignment of FIR types.
+///
+/// The returned size and the effect of \p storeSizeOnly differ by type
+/// category:
+///
+/// - **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). storeSizeOnly has
+/// no effect on this category.
+///
+/// - **Sequences (fir::SequenceType)**:
+/// Each element size is rounded to its alignment boundary before
+/// multiplying by the element count (allocation-size stride). This
+/// rounding is applied regardless of storeSizeOnly, so the result
+/// 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. storeSizeOnly
+/// has no effect on this category.
+///
+/// - **Unpacked records (fir::RecordType, not packed)**:
+/// Fields are laid out with inter-field alignment padding; each field
+/// occupies llvm::alignTo(fieldSize, fieldAlign) bytes. By default
+/// (storeSizeOnly=false) the total size is rounded up to the record's
+/// own alignment, giving the allocation size (tail-padded). When
+/// storeSizeOnly=true, that final tail-rounding is skipped, returning
+/// the sum of field allocation sizes without outer tail padding. This
+/// is the *only* category where storeSizeOnly has an effect. Use
+/// storeSizeOnly=true when comparing Fortran STORAGE_SIZE data-bit
+/// widths (e.g. the TRANSFER inline gate).
+///
/// 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
@@ -569,10 +604,11 @@ getTypeSizeAndAlignmentOrCrash(mlir::Location loc, mlir::Type ty,
const fir::KindMapping &kindMap);
/// This variant returns std::nullopt if an unsupported type is passed.
-std::optional<std::pair<uint64_t, unsigned short>>
-getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
- const mlir::DataLayout &dl,
- const fir::KindMapping &kindMap);
+/// See the block comment above for per-category size and storeSizeOnly
+/// semantics.
+std::optional<std::pair<uint64_t, unsigned short>> getTypeSizeAndAlignment(
+ mlir::Location loc, mlir::Type ty, const mlir::DataLayout &dl,
+ const fir::KindMapping &kindMap, bool storeSizeOnly = false);
} // namespace fir
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index a139af9a29e57..6a71a3d9a14b9 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -8972,22 +8972,64 @@ IntrinsicLibrary::genTransfer(mlir::Type resultType,
(fir::isa_trivial(sourceType) ||
mlir::isa<fir::RecordType>(sourceType)) &&
fir::isa_trivial(moldType)) {
+ // Compare store sizes (data bytes, no tail padding) because Fortran
+ // TRANSFER is defined over STORAGE_SIZE data bits. For a RecordType
+ // source, storeSizeOnly=true strips the tail padding that the compiler
+ // adds for alignment but that is not part of the type's data.
+ // Note: the runtime (TransferImpl) copies source.ElementBytes() bytes,
+ // which is the allocation size and may include tail padding. The store-
+ // size comparison here is therefore *not* about matching the runtime's
+ // copy width; it is about selecting the correct inline path according to
+ // the standard's data-bit definition.
+ //
+ // Alignment is a separate concern: 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());
+ loc, sourceType, builder.getDataLayout(), builder.getKindMap(),
+ /*storeSizeOnly=*/true);
auto resultSizeAndAlign = fir::getTypeSizeAndAlignment(
- loc, resultType, builder.getDataLayout(), builder.getKindMap());
+ loc, resultType, builder.getDataLayout(), builder.getKindMap(),
+ /*storeSizeOnly=*/true);
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);
+ }
+ // The source is a RecordType. When the source ABI alignment is
+ // sufficient for resultType, a direct address cast and load is safe.
+ // When sourceAlign < resultAlign (e.g. {i64,i16} is 8-byte aligned
+ // while x86_fp80 requires 16-byte alignment), loading resultType
+ // directly from sourceBase would claim an over-aligned address,
+ // producing undefined behaviour. In that case copy the bytes into
+ // a result-typed alloca (which receives resultType's natural
+ // alignment) and load from there.
+ 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::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::Value srcLoad = fir::LoadOp::create(builder, loc, sourceBase);
+ mlir::Type srcRefTy = builder.getRefType(sourceType);
+ mlir::Value tmpAsSrc = builder.createConvert(loc, srcRefTy, tmp);
+ fir::StoreOp::create(builder, loc, srcLoad, tmpAsSrc);
+ return fir::LoadOp::create(builder, loc, tmp);
}
}
}
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 0aa5fd60e2b52..f6cfb16acece6 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -1380,9 +1380,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 178860239e17d..6a9fe55ca3869 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1638,10 +1638,9 @@ void FIROpsDialect::registerTypes() {
OpenMPPointerLikeModel<fir::LLVMPointerType>>(*getContext());
}
-std::optional<std::pair<uint64_t, unsigned short>>
-fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
- const mlir::DataLayout &dl,
- const fir::KindMapping &kindMap) {
+std::optional<std::pair<uint64_t, unsigned short>> fir::getTypeSizeAndAlignment(
+ mlir::Location loc, mlir::Type ty, const mlir::DataLayout &dl,
+ const fir::KindMapping &kindMap, bool storeSizeOnly) {
if (ty.isIntOrIndexOrFloat() ||
mlir::isa<mlir::ComplexType, mlir::VectorType,
mlir::DataLayoutTypeInterface>(ty)) {
@@ -1650,7 +1649,8 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
return std::pair{size, alignment};
}
if (auto seqTy = mlir::dyn_cast<fir::SequenceType>(ty)) {
- auto result = getTypeSizeAndAlignment(loc, seqTy.getEleTy(), dl, kindMap);
+ auto result = getTypeSizeAndAlignment(loc, seqTy.getEleTy(), dl, kindMap,
+ storeSizeOnly);
if (!result)
return result;
auto [eleSize, eleAlign] = *result;
@@ -1661,8 +1661,29 @@ 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); // allocation size per field
+ }
+ // Packed structs have no tail padding regardless of storeSizeOnly.
+ return std::pair{size, static_cast<unsigned short>(1)};
+ }
for (auto component : recTy.getTypeList()) {
- auto result = getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
+ auto result = getTypeSizeAndAlignment(loc, component.second, dl, kindMap,
+ storeSizeOnly);
if (!result)
return result;
auto [compSize, compAlign] = *result;
@@ -1670,6 +1691,10 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
llvm::alignTo(size, compAlign) + llvm::alignTo(compSize, compAlign);
align = std::max(align, compAlign);
}
+ // Include tail padding so the size matches the allocation size,
+ // unless the caller only wants the store size (no tail padding).
+ if (!storeSizeOnly)
+ size = llvm::alignTo(size, align);
return std::pair{size, align};
}
if (auto logical = mlir::dyn_cast<fir::LogicalType>(ty)) {
@@ -1681,7 +1706,8 @@ fir::getTypeSizeAndAlignment(mlir::Location loc, mlir::Type ty,
mlir::Type intTy = mlir::IntegerType::get(
character.getContext(),
kindMap.getCharacterBitsize(character.getFKind()));
- auto result = getTypeSizeAndAlignment(loc, intTy, dl, kindMap);
+ auto result =
+ getTypeSizeAndAlignment(loc, intTy, dl, kindMap, storeSizeOnly);
if (!result)
return result;
auto [compSize, compAlign] = *result;
diff --git a/flang/test/Fir/CUDA/cuda-constructor-2.f90 b/flang/test/Fir/CUDA/cuda-constructor-2.f90
index e93a721a192f0..b97a9aee0eba8 100644
--- a/flang/test/Fir/CUDA/cuda-constructor-2.f90
+++ b/flang/test/Fir/CUDA/cuda-constructor-2.f90
@@ -351,3 +351,65 @@ 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: arith.constant 8 : index
+// NOUNIFIED: fir.call @_FortranACUFRegisterVariable
+// 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: arith.constant 12 : index
+// NOUNIFIED: fir.call @_FortranACUFRegisterVariable
+// 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/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/Lower/Intrinsics/transfer-rec-tail-pad.f90 b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
new file mode 100644
index 0000000000000..ac7569778b64b
--- /dev/null
+++ b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
@@ -0,0 +1,74 @@
+! 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 store sizes, not allocation sizes.
+! Tail padding must not be included in the comparison.
+!
+! Types declared at module scope as a workaround for a pre-existing
+! FIR-to-LLVM lowering failure: bare %flang_fc1 aborts with a missing
+! type-descriptor error when lowering boxes for derived types. The abort
+! occurs in FIR-to-LLVM lowering regardless of declaration scope; it can
+! be suppressed with -mmlir --ignore-missing-type-desc (a debug flag) but
+! that is not appropriate for a regression test. Placing the types in a
+! module avoids boxing them at the HLFIR stage exercised here.
+
+! Shape 1: tail-padded record whose store size matches real(10).
+! t1 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.
+! 10 == 10 on store size -> must INLINE (fir.load), not call _FortranATransfer.
+! Without the storeSizeOnly fix the allocation size (16) != 10 and this
+! would wrongly fall through to the runtime path.
+module m1
+ type :: t1
+ integer(8) :: a
+ integer(2) :: b
+ end type
+end module
+
+! Shape 2: tail-padded record whose allocation size matches integer(8) but
+! whose store size does not.
+! t2 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.
+! Without the storeSizeOnly fix: 8 == 8 on allocation size -> would inline,
+! but that reads 3 bytes of uninitialized tail padding into the result.
+! With the fix: 5 != 8 on store size -> correctly stays on RUNTIME path.
+module m2
+ type :: t2
+ integer(4) :: a
+ integer(1) :: b
+ end type
+end module
+
+subroutine transfer_rec_to_real10(out)
+ ! CHECK-LABEL: func @_QPtransfer_rec_to_real10(
+ ! CHECK-NOT: fir.call @_FortranATransfer
+ ! CHECK: %[[TMP:.*]] = fir.alloca f80
+ ! CHECK: %[[REC:.*]] = fir.load {{.*}} : !fir.ref<!fir.type<{{.*}}>>
+ ! CHECK: %[[TMPSRC:.*]] = fir.convert %[[TMP]] : (!fir.ref<f80>) -> !fir.ref<!fir.type<{{.*}}>>
+ ! CHECK: fir.store %[[REC]] to %[[TMPSRC]] : !fir.ref<!fir.type<{{.*}}>>
+ ! CHECK: fir.load %[[TMP]] : !fir.ref<f80>
+ ! CHECK: return
+ use m1
+ type(t1) :: src
+ real(10) :: out
+ src%a = 42
+ src%b = 7
+ out = transfer(src, out)
+end subroutine
+
+subroutine transfer_rec_to_int8(res)
+ ! CHECK-LABEL: func @_QPtransfer_rec_to_int8(
+ ! CHECK: fir.call @_FortranATransfer
+ ! CHECK-NOT: fir.load {{.*}} : !fir.ref<i64>
+ ! CHECK: return
+ use m2
+ type(t2) :: x
+ integer(8) :: res
+ x%a = 1
+ x%b = 2_1
+ res = transfer(x, res)
+end subroutine
diff --git a/flang/test/Transforms/allocation-placement.fir b/flang/test/Transforms/allocation-placement.fir
index 8b0a1acb74122..bccab6f713f21 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
+}
+
}
>From 3be2c99e853fdfc1b32951796ae121886f143a9d Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Thu, 3 Sep 2026 21:02:24 -0400
Subject: [PATCH 2/2] [flang] Preserve TRANSFER padding bytes
---
flang/lib/Optimizer/Builder/IntrinsicCall.cpp | 13 ++++---
.../Fir/struct-passing-systemz-reference.fir | 34 +++++++++++++++++++
.../Intrinsics/transfer-rec-tail-pad-llvm.f90 | 28 +++++++++++++++
.../Intrinsics/transfer-rec-tail-pad.f90 | 26 ++++++++++++++
4 files changed, 97 insertions(+), 4 deletions(-)
create mode 100644 flang/test/Lower/Intrinsics/transfer-rec-tail-pad-llvm.f90
diff --git a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
index 6a71a3d9a14b9..47ad593af29bc 100644
--- a/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
+++ b/flang/lib/Optimizer/Builder/IntrinsicCall.cpp
@@ -9025,10 +9025,15 @@ IntrinsicLibrary::genTransfer(mlir::Type resultType,
return fir::LoadOp::create(builder, loc, cast);
}
mlir::Value tmp = fir::AllocaOp::create(builder, loc, resultType);
- mlir::Value srcLoad = fir::LoadOp::create(builder, loc, sourceBase);
- mlir::Type srcRefTy = builder.getRefType(sourceType);
- mlir::Value tmpAsSrc = builder.createConvert(loc, srcRefTy, tmp);
- fir::StoreOp::create(builder, loc, srcLoad, tmpAsSrc);
+ 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/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..273acd4b9cb53
--- /dev/null
+++ b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad-llvm.f90
@@ -0,0 +1,28 @@
+! 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 10-byte stored representation and 16-byte allocation
+! extent. TRANSFER to real(10) must copy the 10 bytes into result-aligned
+! storage before loading the result.
+module m
+ type :: t
+ integer(8) :: a
+ integer(2) :: b
+ end type
+end module
+
+subroutine transfer_rec_to_real10(out)
+ use m
+ type(t) :: src
+ real(10) :: out
+ src%a = 42
+ src%b = 7
+ out = transfer(src, out)
+end subroutine
+
+! CHECK-LABEL: define{{.*}} @_QPtransfer_rec_to_real10(
+! CHECK: %[[TMP:.*]] = alloca x86_fp80
+! CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 16 %[[TMP]], ptr align 8 {{.*}}, i64 10, i1 false)
+! CHECK: %[[RESULT:.*]] = load x86_fp80, ptr %[[TMP]], align 16
diff --git a/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90 b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
index ac7569778b64b..bf061e2f8225e 100644
--- a/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
+++ b/flang/test/Lower/Intrinsics/transfer-rec-tail-pad.f90
@@ -72,3 +72,29 @@ subroutine transfer_rec_to_int8(res)
x%b = 2_1
res = transfer(x, res)
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.
+subroutine transfer_bindc_record_to_int128(res)
+ ! CHECK-LABEL: func @_QPtransfer_bindc_record_to_int128(
+ ! CHECK: fir.alloca i128
+ ! CHECK: fir.convert {{.*}} -> !fir.ref<!fir.array<16xi8>>
+ ! CHECK: fir.convert {{.*}} -> !fir.ref<!fir.array<16xi8>>
+ ! CHECK: fir.copy {{.*}} : (!fir.ref<!fir.array<16xi8>>, !fir.ref<!fir.array<16xi8>>)
+ ! CHECK: fir.load {{.*}} : !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
+ integer(c_int8_t) :: bytes(16) = [ &
+ 1_c_int8_t, 2_c_int8_t, 3_c_int8_t, 4_c_int8_t, &
+ 5_c_int8_t, 6_c_int8_t, 7_c_int8_t, 8_c_int8_t, &
+ 9_c_int8_t, 10_c_int8_t, 11_c_int8_t, 12_c_int8_t, &
+ 13_c_int8_t, 14_c_int8_t, 15_c_int8_t, 16_c_int8_t]
+ ! Initialize all 16 bytes, including the seven internal padding bytes.
+ source = transfer(bytes, source)
+ res = transfer(source, res)
+end subroutine
More information about the flang-commits
mailing list