[flang-commits] [flang] [flang] Fix `getTypeSizeAndAlignment` for packed and tail-padded RecordType (PR #220377)

Daniel Chen via flang-commits flang-commits at lists.llvm.org
Tue Sep 1 13:47:00 PDT 2026


https://github.com/DanielCChen created https://github.com/llvm/llvm-project/pull/220377


`fir::getTypeSizeAndAlignment` had two bugs in its RecordType branch:

1. Packed records: `isPacked()` was ignored.  LLVM packed structs place fields back-to-back with no inter-field padding and no tail padding; each component occupies its raw store size with no alignment rounding and the struct ABI alignment is 1.  The old code applied the same aligned field-offset arithmetic as ordinary structs, returning the non-packed size (e.g. 16 B instead of 12 B for <{i32, f64}> on x86-64).

2. Tail padding: the field loop accumulated raw typed size without rounding up to struct alignment after the loop.  For example, {i32, i8} (typed=5 B, align=4) was returned as 5 B instead of the correct allocation size 8 B.

Fix: add an isPacked() branch that sums per-component store sizes with no rounding and returns alignment 1.  Add size = alignTo(size, align) after the unpacked field loop to include tail padding.

Callers affected by both bugs: `fir.box_elesize` folding, the allocation-placement pass, and CUDA device-global registration and shared-memory slot sizing.  Add tests in:
- flang/test/Fir/box-elesize-canonicalize.fir
- flang/test/Transforms/allocation-placement.fir
- flang/test/Fir/CUDA/cuda-constructor-2.f90
- flang/test/Fir/CUDA/cuda-shared-offset.mlir

Assisted-by: IBM Bob

>From c42620f7a2c3f62ae3fb543b615c9b5418cd7817 Mon Sep 17 00:00:00 2001
From: Daniel Chen <cdchen at ca.ibm.com>
Date: Tue, 1 Sep 2026 16:41:30 -0400
Subject: [PATCH] [flang] Fix getTypeSizeAndAlignment for packed and
 tail-padded RecordType

fir::getTypeSizeAndAlignment had two bugs in its RecordType branch:

1. Packed records: isPacked() was ignored.  LLVM packed structs place
   fields back-to-back with no inter-field padding and no tail padding;
   each component occupies its raw store size with no alignment rounding
   and the struct ABI alignment is 1.  The old code applied the same
   aligned field-offset arithmetic as ordinary structs, returning the
   non-packed size (e.g. 16 B instead of 12 B for <{i32, f64}> on
   x86-64).

2. Tail padding: the field loop accumulated raw typed size without
   rounding up to struct alignment after the loop.  For example,
   {i32, i8} (typed=5 B, align=4) was returned as 5 B instead of the
   correct allocation size 8 B.

Fix: add an isPacked() branch that sums per-component store sizes with
no rounding and returns alignment 1.  Add size = alignTo(size, align)
after the unpacked field loop to include tail padding.

Callers affected by both bugs: fir.box_elesize folding, the
allocation-placement pass, and CUDA device-global registration and
shared-memory slot sizing.  Add tests in:
- flang/test/Fir/box-elesize-canonicalize.fir
- flang/test/Transforms/allocation-placement.fir
- flang/test/Fir/CUDA/cuda-constructor-2.f90
- flang/test/Fir/CUDA/cuda-shared-offset.mlir
---
 flang/lib/Optimizer/Dialect/FIROps.cpp        |  8 ++-
 flang/lib/Optimizer/Dialect/FIRType.cpp       | 19 ++++++
 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   | 47 ++++++++++++++
 .../test/Transforms/allocation-placement.fir  | 40 ++++++++++++
 6 files changed, 217 insertions(+), 3 deletions(-)

diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 108f7fc793c61..2431fe8573af8 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..54df660ba3bc3 100644
--- a/flang/lib/Optimizer/Dialect/FIRType.cpp
+++ b/flang/lib/Optimizer/Dialect/FIRType.cpp
@@ -1661,6 +1661,23 @@ 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
+      // occupies exactly its store size (the first element of the pair
+      // returned by the recursive getTypeSizeAndAlignment call, which equals
+      // dl.getTypeSizeInBits()/8 for scalar types).  Unlike ordinary structs,
+      // there is no per-component rounding to ABI alignment.
+      // The packed struct ABI alignment is always 1.
+      for (auto component : recTy.getTypeList()) {
+        auto result =
+            getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
+        if (!result)
+          return result;
+        size += result->first; // store size only; no alignment rounding
+      }
+      return std::pair{size, static_cast<unsigned short>(1)};
+    }
     for (auto component : recTy.getTypeList()) {
       auto result = getTypeSizeAndAlignment(loc, component.second, dl, kindMap);
       if (!result)
@@ -1670,6 +1687,8 @@ 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.
+    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 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..5ecd25743525c 100644
--- a/flang/test/Fir/box-elesize-canonicalize.fir
+++ b/flang/test/Fir/box-elesize-canonicalize.fir
@@ -102,4 +102,51 @@ 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 store=4B + f64 store=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 field loop (size = alignTo(size,compAlign) + alignTo(compSize,compAlign)):
+  //   after i32:          alignTo(0,4)+alignTo(4,4)   =  0+ 4 =  4, align=4
+  //   after complex<f64>: alignTo(4,8)+alignTo(16,8)  =  8+16 = 24, align=8
+  //   tail pad:           alignTo(24,8) = 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; after tail-padding,
+  // getTypeSizeAndAlignment returns {8, 4}.  The element stride of a
+  // fir.box<fir.array<?x...>> must be the allocation size (8), not the
+  // raw typed size (5).
+  // 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
 }
diff --git a/flang/test/Transforms/allocation-placement.fir b/flang/test/Transforms/allocation-placement.fir
index 8b0a1acb74122..f19066c6f5c27 100644
--- a/flang/test/Transforms/allocation-placement.fir
+++ b/flang/test/Transforms/allocation-placement.fir
@@ -102,4 +102,44 @@ 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 typed + 3 tail padding), total = 24 bytes.
+// getTypeSizeAndAlignment must return 8 per element so the pass computes the
+// correct total of 24 bytes.  With the default 64-byte threshold this
+// 24-byte temporary array is small and must be 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<3x!fir.type<tpk<{i:i32,d:f64}>>>
+// Each element is 12 bytes (packed: 4+8, no gaps or tail padding), total=36B.
+// getTypeSizeAndAlignment must return 12 per element (not 16) so the pass
+// computes the correct total of 36 bytes.  With the default 64-byte threshold
+// this 36-byte temporary array is small and must be promoted to stack.
+// CHECK-LABEL: func.func @packed_record_placement
+// CHECK: fir.alloca !fir.array<3x!fir.type<tpk<{i:i32,d:f64}>>>
+// CHECK-NOT: fir.allocmem
+func.func @packed_record_placement() {
+  %0 = fir.allocmem !fir.array<3x!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<3x!fir.type<tpk<{i:i32,d:f64}>>>>) -> !fir.ref<!fir.array<3x!fir.type<tpk<{i:i32,d:f64}>>>>
+  %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<3x!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<3x!fir.type<tpk<{i:i32,d:f64}>>>>
+  return
+}
+
 }



More information about the flang-commits mailing list