[flang-commits] [flang] [flang] Carry a requested alignment on fir.declare and hlfir.declare (PR #234899)

Eugene Epshteyn via flang-commits flang-commits at lists.llvm.org
Fri Oct 23 03:25:59 PDT 2026


https://github.com/eugeneepshteyn created https://github.com/llvm/llvm-project/pull/234899

Add an optional `alignment` attribute to `fir.declare` and `hlfir.declare`, printed as `alignment(N)` before the attribute dictionary, and preserve it through hlfir-to-fir conversion. Two helpers, `hlfir::getDeclaredAlignment` and `hlfir::setDeclaredAlignment`, read and set it on either op, looking through `hlfir.designate` to the variable's declare.

The declare op is the durable per-variable carrier that later passes see: the temporary-creation helpers and bufferization work on SSA values, not on semantics symbols, so this is where a variable's requested alignment has to live for temporaries copied from it to inherit it (next patch).

Stacked on #234897 and #234898.


>From c86795ec5ba55102235a4a28876baa7cb5955a4e Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Thu, 22 Oct 2026 19:27:48 -0700
Subject: [PATCH 1/3] [flang] Add an alignment attribute to fir.alloca

`fir.allocmem` and `fir.global` can carry a requested alignment, but
`fir.alloca` could not, so there was no way to represent an aligned
stack object in FIR. Add an optional `alignment` attribute to
`fir.alloca`, verified to be a positive power of two like the one on
`fir.allocmem`, and forward it to `llvm.alloca` in codegen.

The requested value is a minimum: codegen raises it to the ABI alignment
of the allocated type when it is smaller, so an alloca never ends up
less aligned than one without the attribute. The same clamp is applied
to `fir.global`, which until now forwarded the attribute verbatim.

This is the first step towards a directive that sets the alignment of
local, automatic and global variables; passes that convert between
stack and heap allocations learn to preserve the attribute in the
following change.
---
 .../include/flang/Optimizer/Dialect/FIROps.td |  8 ++++-
 flang/lib/Optimizer/CodeGen/CodeGen.cpp       | 15 +++++++--
 flang/lib/Optimizer/Dialect/FIROps.cpp        | 16 ++++++----
 flang/test/Fir/convert-to-llvm.fir            | 32 +++++++++++++++++++
 flang/test/Fir/fir-ops.fir                    |  2 ++
 flang/test/Fir/invalid.fir                    |  8 +++++
 6 files changed, 72 insertions(+), 9 deletions(-)

diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index fc00daac1695b1..0b727b46dc4ce4 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -205,6 +205,11 @@ def fir_AllocaOp : fir_Op<"alloca", [
 
     The pinned attribute is used to flag fir.alloca operation in a specific
     region and avoid them being hoisted in an alloca hoisting pass.
+
+    The optional alignment attribute requests a minimum byte alignment for
+    the allocation, as `fir.allocmem` and `fir.global` do. It must be a
+    positive power of two. Code generation never lowers the alignment below
+    the ABI alignment of the allocated type.
   }];
 
   let arguments = (ins
@@ -213,7 +218,8 @@ def fir_AllocaOp : fir_Op<"alloca", [
     OptionalAttr<StrAttr>:$bindc_name,
     UnitAttr:$pinned,
     Variadic<AnyIntegerType>:$typeparams,
-    Variadic<AnyIntegerType>:$shape
+    Variadic<AnyIntegerType>:$shape,
+    OptionalAttr<I64Attr>:$alignment
   );
 
   let results =
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index d5097aa6f76035..63cc61193d1f65 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -480,13 +480,21 @@ struct AllocaOpConversion : public fir::FIROpConversion<fir::AllocaOp> {
       rewriter.setInsertionPointAfter(size.getDefiningOp());
     }
 
+    // A requested alignment is a minimum: never go below the ABI alignment of
+    // the allocated type, which is what an alloca without an explicit
+    // alignment gets.
+    unsigned alignment = 0;
+    if (std::optional<std::int64_t> requested = alloc.getAlignment())
+      alignment = static_cast<unsigned>(std::max<std::uint64_t>(
+          *requested, getDataLayout().getTypeABIAlignment(llvmObjectType)));
+
     // NOTE: we used to pass alloc->getAttrs() in the builder for non opaque
     // pointers! Only propagate pinned and bindc_name to help debugging, but
     // this should have no functional purpose (and passing the operand segment
     // attribute like before is certainly bad).
     auto llvmAlloc = mlir::LLVM::AllocaOp::create(
         rewriter, loc, ::getLlvmPtrType(alloc.getContext(), allocaAs),
-        llvmObjectType, size);
+        llvmObjectType, size, alignment);
     if (alloc.getPinned())
       llvmAlloc->setDiscardableAttr(alloc.getPinnedAttrName(),
                                     alloc.getPinnedAttr());
@@ -3848,8 +3856,11 @@ struct GlobalOpConversion : public fir::FIROpConversion<fir::GlobalOp> {
         0, getGlobalAddressSpace(rewriter), false,
         mlir::LLVM::ThreadLocalMode::NotThreadLocal, comdat, attrs, dbgExprs);
 
+    // A requested alignment is a minimum: never go below the ABI alignment of
+    // the global's type.
     if (global.getAlignment() && *global.getAlignment() > 0)
-      g.setAlignment(*global.getAlignment());
+      g.setAlignment(std::max<std::uint64_t>(
+          *global.getAlignment(), getDataLayout().getTypeABIAlignment(tyAttr)));
     if (auto section = global.getSection())
       g.setSection(*section);
 
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 3d079afa0d1a51..97bca969e96b7b 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -408,7 +408,7 @@ void fir::AllocaOp::build(mlir::OpBuilder &builder,
                           llvm::ArrayRef<mlir::NamedAttribute> attributes) {
   auto nameAttr = builder.getStringAttr(uniqName);
   build(builder, result, wrapAllocaResultType(inType), inType, nameAttr, {},
-        /*pinned=*/false, typeparams, shape);
+        /*pinned=*/false, typeparams, shape, /*alignment=*/{});
   result.addAttributes(attributes);
 }
 
@@ -419,7 +419,7 @@ void fir::AllocaOp::build(mlir::OpBuilder &builder,
                           llvm::ArrayRef<mlir::NamedAttribute> attributes) {
   auto nameAttr = builder.getStringAttr(uniqName);
   build(builder, result, wrapAllocaResultType(inType), inType, nameAttr, {},
-        pinned, typeparams, shape);
+        pinned, typeparams, shape, /*alignment=*/{});
   result.addAttributes(attributes);
 }
 
@@ -433,7 +433,7 @@ void fir::AllocaOp::build(mlir::OpBuilder &builder,
   auto bindcAttr =
       bindcName.empty() ? mlir::StringAttr{} : builder.getStringAttr(bindcName);
   build(builder, result, wrapAllocaResultType(inType), inType, nameAttr,
-        bindcAttr, /*pinned=*/false, typeparams, shape);
+        bindcAttr, /*pinned=*/false, typeparams, shape, /*alignment=*/{});
   result.addAttributes(attributes);
 }
 
@@ -448,7 +448,7 @@ void fir::AllocaOp::build(mlir::OpBuilder &builder,
   auto bindcAttr =
       bindcName.empty() ? mlir::StringAttr{} : builder.getStringAttr(bindcName);
   build(builder, result, wrapAllocaResultType(inType), inType, nameAttr,
-        bindcAttr, pinned, typeparams, shape);
+        bindcAttr, pinned, typeparams, shape, /*alignment=*/{});
   result.addAttributes(attributes);
 }
 
@@ -457,7 +457,7 @@ void fir::AllocaOp::build(mlir::OpBuilder &builder,
                           mlir::ValueRange typeparams, mlir::ValueRange shape,
                           llvm::ArrayRef<mlir::NamedAttribute> attributes) {
   build(builder, result, wrapAllocaResultType(inType), inType, {}, {},
-        /*pinned=*/false, typeparams, shape);
+        /*pinned=*/false, typeparams, shape, /*alignment=*/{});
   result.addAttributes(attributes);
 }
 
@@ -467,7 +467,7 @@ void fir::AllocaOp::build(mlir::OpBuilder &builder,
                           mlir::ValueRange shape,
                           llvm::ArrayRef<mlir::NamedAttribute> attributes) {
   build(builder, result, wrapAllocaResultType(inType), inType, {}, {}, pinned,
-        typeparams, shape);
+        typeparams, shape, /*alignment=*/{});
   result.addAttributes(attributes);
 }
 
@@ -492,6 +492,10 @@ llvm::LogicalResult fir::AllocaOp::verify() {
   mlir::Type outType = getType();
   if (!mlir::isa<fir::ReferenceType>(outType))
     return emitOpError("must be a !fir.ref type");
+  if (std::optional<std::int64_t> alignment = getAlignment()) {
+    if (*alignment <= 0 || (*alignment & (*alignment - 1)) != 0)
+      return emitOpError("alignment must be a positive power of two");
+  }
   return mlir::success();
 }
 
diff --git a/flang/test/Fir/convert-to-llvm.fir b/flang/test/Fir/convert-to-llvm.fir
index 73b930b3803285..a0c4d0f6db0f46 100644
--- a/flang/test/Fir/convert-to-llvm.fir
+++ b/flang/test/Fir/convert-to-llvm.fir
@@ -1115,6 +1115,38 @@ func.func @alloca_several() -> !fir.ref<i32> {
 
 // -----
 
+// Test fir.alloca with a requested alignment
+
+func.func @alloca_aligned() -> !fir.ref<!fir.array<10xf32>> {
+  %1 = fir.alloca !fir.array<10xf32> {alignment = 64 : i64}
+  return %1 : !fir.ref<!fir.array<10xf32>>
+}
+
+// CHECK-LABEL: llvm.func @alloca_aligned() -> !llvm.ptr
+// CHECK: [[N:%.*]] = llvm.mlir.constant(1 : i64) : i64
+// GENERIC: [[A:%.*]] = llvm.alloca [[N]] x !llvm.array<10 x f32> {alignment = 64 : i64}
+// AMDGPU: [[AA:%.*]] = llvm.alloca [[N]] x !llvm.array<10 x f32> {alignment = 64 : i64} : (i64) -> !llvm.ptr<5>
+// AMDGPU: [[A:%.*]] = llvm.addrspacecast [[AA]] : !llvm.ptr<5> to !llvm.ptr
+// CHECK: llvm.return [[A]] : !llvm.ptr
+
+// -----
+
+// A requested alignment below the ABI alignment of the type is raised to it.
+
+func.func @alloca_underaligned() -> !fir.ref<i32> {
+  %1 = fir.alloca i32 {alignment = 2 : i64}
+  return %1 : !fir.ref<i32>
+}
+
+// CHECK-LABEL: llvm.func @alloca_underaligned() -> !llvm.ptr
+// CHECK: [[N:%.*]] = llvm.mlir.constant(1 : i64) : i64
+// GENERIC: [[A:%.*]] = llvm.alloca [[N]] x i32 {alignment = 4 : i64}
+// AMDGPU: [[AA:%.*]] = llvm.alloca [[N]] x i32 {alignment = 4 : i64} : (i64) -> !llvm.ptr<5>
+// AMDGPU: [[A:%.*]] = llvm.addrspacecast [[AA]] : !llvm.ptr<5> to !llvm.ptr
+// CHECK: llvm.return [[A]] : !llvm.ptr
+
+// -----
+
 // Test fir.alloca of pointer to array
 
 func.func @alloca_ptr_to_array() -> !fir.ref<!fir.ptr<!fir.array<?xi32>>> {
diff --git a/flang/test/Fir/fir-ops.fir b/flang/test/Fir/fir-ops.fir
index 704cd2ab217fa6..bf42a9c1ae79e1 100644
--- a/flang/test/Fir/fir-ops.fir
+++ b/flang/test/Fir/fir-ops.fir
@@ -37,10 +37,12 @@ func.func @instructions() {
 // CHECK: [[VAL_0:%.*]] = fir.alloca !fir.array<10xi32>
 // CHECK: [[VAL_1:%.*]] = fir.load [[VAL_0]] : !fir.ref<!fir.array<10xi32>>
 // CHECK: [[VAL_2:%.*]] = fir.alloca i32
+// CHECK: [[VAL_ALIGNED:%.*]] = fir.alloca !fir.array<10xf32> <{alignment = 64 : i64}>
 // CHECK: [[VAL_3:%.*]] = arith.constant 22 : i32
   %0 = fir.alloca !fir.array<10xi32>
   %1 = fir.load %0 : !fir.ref<!fir.array<10xi32>>
   %2 = fir.alloca i32
+  %aligned = fir.alloca !fir.array<10xf32> {alignment = 64 : i64}
   %3 = arith.constant 22 : i32
 
 // CHECK: fir.store [[VAL_3]] to [[VAL_2]] : !fir.ref<i32>
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
index 8964df1d2e93e6..8bf414f86173f9 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -1400,3 +1400,11 @@ func.func @allocmem_bad_alignment() -> !fir.heap<!fir.array<10xf32>> {
   %0 = fir.allocmem !fir.array<10xf32> {alignment = 3 : i64}
   return %0 : !fir.heap<!fir.array<10xf32>>
 }
+
+// -----
+
+func.func @alloca_bad_alignment() -> !fir.ref<!fir.array<10xf32>> {
+  // expected-error at +1 {{'fir.alloca' op alignment must be a positive power of two}}
+  %0 = fir.alloca !fir.array<10xf32> {alignment = 24 : i64}
+  return %0 : !fir.ref<!fir.array<10xf32>>
+}

>From ef50769176c1ba9d520ae422a551ef49e6e3ffb3 Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Thu, 22 Oct 2026 19:27:49 -0700
Subject: [PATCH 2/3] [flang] Preserve the alignment attribute across
 stack/heap conversions

The two helpers that convert allocations between the stack and the heap
rebuilt the new allocation from the type, names and operands only, so a
requested alignment was lost in both directions:

- `fir::createAllocMemFromAlloca` (used by MemoryAllocation,
  AllocationPlacement and the CUDA heap promotion) now copies the
  `fir.alloca` alignment onto the `fir.allocmem` it creates;
- `AllocMemConversion::insertAlloca` (StackArrays, also reused by
  AllocationPlacement) now copies the `fir.allocmem` alignment onto the
  `fir.alloca` it creates.

AllocationPlacement's stack budget also accounts for the worst-case
padding an over-aligned stack object may need.

Today the only producer of the `fir.allocmem` alignment attribute is
the allocatable/pointer allocation path, which StackArrays does not
touch by design, so this fixes a latent rather than an observable drop;
it becomes live as soon as temporaries and user variables carry an
alignment.
---
 .../Transforms/AllocationPlacement.cpp        | 10 ++++++--
 .../lib/Optimizer/Transforms/MemoryUtils.cpp  | 12 ++++++----
 .../lib/Optimizer/Transforms/StackArrays.cpp  |  3 +++
 flang/test/Fir/memory-allocation-opt.fir      | 14 +++++++++++
 .../test/Transforms/allocation-placement.fir  | 24 +++++++++++++++++++
 flang/test/Transforms/stack-arrays.fir        | 15 ++++++++++++
 6 files changed, 72 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp b/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
index 89783d78148316..a59c7181a1fef4 100644
--- a/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
+++ b/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
@@ -215,9 +215,15 @@ void AllocationPlacementPass::runOnOperation() {
             ? placementHook(info, basePolicy, stackBytesUsed)
             : fir::decideAllocationPlacement(info, basePolicy, stackBytesUsed);
 
-    // Account for the decision in the running stack budget.
-    if (endsUpOnStack(placement, info.isCurrentlyOnStack) && info.byteSize)
+    // Account for the decision in the running stack budget, including the
+    // worst-case padding a requested alignment may need.
+    if (endsUpOnStack(placement, info.isCurrentlyOnStack) && info.byteSize) {
       stackBytesUsed += static_cast<std::size_t>(*info.byteSize);
+      std::optional<std::int64_t> alignment =
+          alloca ? alloca.getAlignment() : allocmem.getAlignment();
+      if (alignment && *alignment > 1)
+        stackBytesUsed += static_cast<std::size_t>(*alignment - 1);
+    }
 
     LLVM_DEBUG({
       llvm::dbgs() << "allocation-placement: "
diff --git a/flang/lib/Optimizer/Transforms/MemoryUtils.cpp b/flang/lib/Optimizer/Transforms/MemoryUtils.cpp
index 00a25a7326f2dd..6da0c3141b8c44 100644
--- a/flang/lib/Optimizer/Transforms/MemoryUtils.cpp
+++ b/flang/lib/Optimizer/Transforms/MemoryUtils.cpp
@@ -322,10 +322,14 @@ fir::AllocMemOp fir::createAllocMemFromAlloca(mlir::OpBuilder &builder,
       return *opt;
     return {};
   };
-  return fir::AllocMemOp::create(builder, alloca.getLoc(), alloca.getInType(),
-                                 unpackName(alloca.getUniqName()),
-                                 unpackName(alloca.getBindcName()),
-                                 alloca.getTypeparams(), alloca.getShape());
+  auto heap = fir::AllocMemOp::create(
+      builder, alloca.getLoc(), alloca.getInType(),
+      unpackName(alloca.getUniqName()), unpackName(alloca.getBindcName()),
+      alloca.getTypeparams(), alloca.getShape());
+  // Keep a requested alignment across the stack-to-heap conversion.
+  if (std::optional<std::int64_t> alignment = alloca.getAlignment())
+    heap.setAlignment(*alignment);
+  return heap;
 }
 
 /// Device code keeps its stack allocations: the unified/managed entry points
diff --git a/flang/lib/Optimizer/Transforms/StackArrays.cpp b/flang/lib/Optimizer/Transforms/StackArrays.cpp
index 0674cc24319606..6b8edde0c2d927 100644
--- a/flang/lib/Optimizer/Transforms/StackArrays.cpp
+++ b/flang/lib/Optimizer/Transforms/StackArrays.cpp
@@ -711,6 +711,9 @@ fir::AllocMemConversion::insertAlloca(fir::AllocMemOp &oldAlloc,
   auto alloca =
       fir::AllocaOp::create(rewriter, loc, varTy, uniqName, bindcName,
                             oldAlloc.getTypeparams(), oldAlloc.getShape());
+  // Keep a requested alignment across the heap-to-stack conversion.
+  if (std::optional<std::int64_t> alignment = oldAlloc.getAlignment())
+    alloca.setAlignment(*alignment);
   if (emitLifetimeMarkers)
     insertLifetimeMarkers(oldAlloc, alloca, rewriter);
 
diff --git a/flang/test/Fir/memory-allocation-opt.fir b/flang/test/Fir/memory-allocation-opt.fir
index 610a3f0b3072a6..9993ebc6b7e502 100644
--- a/flang/test/Fir/memory-allocation-opt.fir
+++ b/flang/test/Fir/memory-allocation-opt.fir
@@ -32,3 +32,17 @@ func.func @_QPs2(%arg0: !fir.ref<i32>) {
 }
 func.func private @_QPs3(!fir.ref<!fir.array<1000123xi32>>)
 
+// A requested alignment survives the move to the heap.
+
+// CHECK-LABEL: func @_QPs4(
+// CHECK: %[[mem:.*]] = fir.allocmem !fir.array<1000123xi32> <{alignment = 64 : i64, bindc_name = "array", uniq_name = "_QFs4Earray"}>
+// CHECK: fir.call @_QPs3(
+// CHECK: fir.freemem %[[mem]]
+// CHECK-NEXT: return
+
+func.func @_QPs4() {
+  %0 = fir.alloca !fir.array<1000123xi32> {alignment = 64 : i64, bindc_name = "array", uniq_name = "_QFs4Earray"}
+  fir.call @_QPs3(%0) : (!fir.ref<!fir.array<1000123xi32>>) -> ()
+  return
+}
+
diff --git a/flang/test/Transforms/allocation-placement.fir b/flang/test/Transforms/allocation-placement.fir
index aa09d830fd5a5f..c9c8f6d65e6374 100644
--- a/flang/test/Transforms/allocation-placement.fir
+++ b/flang/test/Transforms/allocation-placement.fir
@@ -63,6 +63,30 @@ func.func @big_temp_alloca() {
   return
 }
 
+// A requested alignment survives heap-to-stack ...
+// CHECK-LABEL: func.func @small_temp_aligned
+// CHECK: fir.alloca !fir.array<10xi32> <{alignment = 64 : i64}>
+// CHECK-NOT: fir.allocmem
+func.func @small_temp_aligned() {
+  %0 = fir.allocmem !fir.array<10xi32> {alignment = 64 : i64}
+  %c0 = arith.constant 0 : index
+  %v = arith.constant 0 : i32
+  %r = fir.convert %0 : (!fir.heap<!fir.array<10xi32>>) -> !fir.ref<!fir.array<10xi32>>
+  %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<10xi32>>, index) -> !fir.ref<i32>
+  fir.store %v to %e : !fir.ref<i32>
+  fir.freemem %0 : !fir.heap<!fir.array<10xi32>>
+  return
+}
+
+// ... and stack-to-heap.
+// CHECK-LABEL: func.func @big_temp_alloca_aligned
+// CHECK: fir.allocmem !fir.array<100xi32> <{alignment = 64 : i64
+// CHECK: fir.freemem
+func.func @big_temp_alloca_aligned() {
+  %0 = fir.alloca !fir.array<100xi32> {alignment = 64 : i64}
+  return
+}
+
 // Runtime-sized user variable (automatic array) -> heap.
 // CHECK-LABEL: func.func @dyn_user
 // CHECK: fir.allocmem !fir.array<?xi32>
diff --git a/flang/test/Transforms/stack-arrays.fir b/flang/test/Transforms/stack-arrays.fir
index 5b149bfd8b6216..08d893d5679ea3 100644
--- a/flang/test/Transforms/stack-arrays.fir
+++ b/flang/test/Transforms/stack-arrays.fir
@@ -15,6 +15,21 @@ func.func @simple() {
 // CHECK: fir.alloca !fir.array<42xi32>
 // CHECK: return
 
+// A requested alignment survives the move to the stack
+func.func @aligned() {
+  %0 = fir.allocmem !fir.array<42xi32> {alignment = 64 : i64}
+  %c0_a = arith.constant 0 : index
+  %c0_i32_a = arith.constant 0 : i32
+  %ref_a = fir.convert %0 : (!fir.heap<!fir.array<42xi32>>) -> !fir.ref<!fir.array<42xi32>>
+  %elt_a = fir.coordinate_of %ref_a, %c0_a : (!fir.ref<!fir.array<42xi32>>, index) -> !fir.ref<i32>
+  fir.store %c0_i32_a to %elt_a : !fir.ref<i32>
+  fir.freemem %0 : !fir.heap<!fir.array<42xi32>>
+  return
+}
+// CHECK-LABEL: func.func @aligned()
+// CHECK: fir.alloca !fir.array<42xi32> <{alignment = 64 : i64}>
+// CHECK: return
+
 // Check fir.must_be_heap allocations are not moved
 func.func @must_be_heap() {
   %0 = fir.allocmem !fir.array<42xi32> {fir.must_be_heap = true}

>From 7e82d41da1abbe5cd038aa5ebd45e127bce8edac Mon Sep 17 00:00:00 2001
From: Eugene Epshteyn <eepshteyn at nvidia.com>
Date: Thu, 22 Oct 2026 19:36:47 -0700
Subject: [PATCH 3/3] [flang] Carry a requested alignment on fir.declare and
 hlfir.declare

Add an optional `alignment` attribute to `fir.declare` and
`hlfir.declare`, printed as `alignment(N)`, and propagate it from the
HLFIR op to the FIR op in the HLFIR-to-FIR conversion.

The attribute is the durable per-variable carrier for an alignment the
user requested on a variable: temporaries created from the variable and
the HLFIR rewrites that re-allocate storage see SSA values, not front-end
symbols, so they need the information on the declare operation. For a
variable whose storage is allocated by the compiler the value is also a
fact about that storage; for a descriptor-based variable it is only the
policy for future allocations, and the op documentation says so.

Two helpers, `hlfir::getDeclaredAlignment` and
`hlfir::setDeclaredAlignment`, read and record the attribute through
either declare op; the getter looks through `hlfir.designate` to the
designated variable.
---
 .../flang/Optimizer/Builder/HLFIRTools.h      |  9 +++++++
 .../include/flang/Optimizer/Dialect/FIROps.td | 10 +++++++-
 .../include/flang/Optimizer/HLFIR/HLFIROps.td |  4 ++-
 flang/lib/Optimizer/Builder/FIRBuilder.cpp    |  2 +-
 flang/lib/Optimizer/Builder/HLFIRTools.cpp    | 25 +++++++++++++++++++
 flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp     |  3 ++-
 .../HLFIR/Transforms/ConvertToFIR.cpp         |  2 +-
 flang/test/Fir/declare.fir                    |  8 ++++++
 flang/test/HLFIR/declare-codegen.fir          |  8 ++++++
 flang/test/HLFIR/declare.fir                  |  8 ++++++
 .../Optimizer/FortranVariableTest.cpp         | 11 ++++----
 11 files changed, 80 insertions(+), 10 deletions(-)

diff --git a/flang/include/flang/Optimizer/Builder/HLFIRTools.h b/flang/include/flang/Optimizer/Builder/HLFIRTools.h
index ce99a370526213..59f6e13d3594d4 100644
--- a/flang/include/flang/Optimizer/Builder/HLFIRTools.h
+++ b/flang/include/flang/Optimizer/Builder/HLFIRTools.h
@@ -555,6 +555,15 @@ hlfir::Entity createStackTempFromMold(mlir::Location loc,
                                       fir::FirOpBuilder &builder,
                                       hlfir::Entity mold);
 
+/// Return the alignment recorded on the hlfir.declare or fir.declare that
+/// defines \p var, if any. A designator of a variable (hlfir.designate) is
+/// looked through to the variable it designates.
+std::optional<std::int64_t> getDeclaredAlignment(mlir::Value var);
+
+/// Record \p alignment on the hlfir.declare or fir.declare defining \p var.
+/// No-op when \p var is not defined by a declare operation.
+void setDeclaredAlignment(mlir::Value var, std::int64_t alignment);
+
 hlfir::EntityWithAttributes convertCharacterKind(mlir::Location loc,
                                                  fir::FirOpBuilder &builder,
                                                  hlfir::Entity scalarChar,
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 0b727b46dc4ce4..8d6f6419221bfe 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -3393,6 +3393,12 @@ def fir_DeclareOp
     If these operands are absent, then the storage of the declared variable
     is only known to start where the memref operand points to.
 
+    The optional alignment attribute records the byte alignment requested
+    for the variable (e.g. by a compiler directive). For a variable whose
+    storage is allocated by the compiler it is a fact about that storage;
+    for a variable represented by a descriptor it is only the policy for
+    future allocations and must not be turned into an alignment assumption.
+
     Example:
 
     CHARACTER(n), OPTIONAL, TARGET :: c(10:, 20:)
@@ -3419,7 +3425,8 @@ def fir_DeclareOp
       Builtin_StringAttr:$uniq_name,
       OptionalAttr<fir_FortranVariableFlagsAttr>:$fortran_attrs,
       OptionalAttr<cuf_DataAttributeAttr>:$data_attr,
-      OptionalAttr<UI32Attr>:$dummy_arg_no);
+      OptionalAttr<UI32Attr>:$dummy_arg_no,
+      OptionalAttr<I64Attr>:$alignment);
 
   let results = (outs AnyRefOrBox);
 
@@ -3430,6 +3437,7 @@ def fir_DeclareOp
     `uniq_name` `(` $uniq_name `)`
     (`fortran_attrs` `` $fortran_attrs^)?
     (`data_attr` `(` qualified($data_attr)^ `)`)?
+    (`alignment` `(` $alignment^ `)`)?
     attr-dict `:` functional-type(operands, results)
   }];
 
diff --git a/flang/include/flang/Optimizer/HLFIR/HLFIROps.td b/flang/include/flang/Optimizer/HLFIR/HLFIROps.td
index 4c531c5bddf0b8..fee42bcaad5d16 100644
--- a/flang/include/flang/Optimizer/HLFIR/HLFIROps.td
+++ b/flang/include/flang/Optimizer/HLFIR/HLFIROps.td
@@ -104,7 +104,8 @@ def hlfir_DeclareOp
       Builtin_StringAttr:$uniq_name,
       OptionalAttr<fir_FortranVariableFlagsAttr>:$fortran_attrs,
       OptionalAttr<cuf_DataAttributeAttr>:$data_attr,
-      OptionalAttr<UnitAttr>:$skip_rebox, OptionalAttr<UI32Attr>:$dummy_arg_no);
+      OptionalAttr<UnitAttr>:$skip_rebox, OptionalAttr<UI32Attr>:$dummy_arg_no,
+      OptionalAttr<I64Attr>:$alignment);
 
   let results = (outs AnyFortranVariable, AnyRefOrBoxLike);
 
@@ -116,6 +117,7 @@ def hlfir_DeclareOp
     `uniq_name` `(` $uniq_name `)`
     (`fortran_attrs` `` $fortran_attrs^)?
     (`data_attr` `(` qualified($data_attr)^ `)`)?
+    (`alignment` `(` $alignment^ `)`)?
     attr-dict `:` functional-type(operands, results)
   }];
 
diff --git a/flang/lib/Optimizer/Builder/FIRBuilder.cpp b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
index 7f03f3f050d5fd..2386a94a0374d4 100644
--- a/flang/lib/Optimizer/Builder/FIRBuilder.cpp
+++ b/flang/lib/Optimizer/Builder/FIRBuilder.cpp
@@ -463,7 +463,7 @@ mlir::Value fir::FirOpBuilder::genTempDeclareOp(
       /*dummy_scope=*/nullptr,
       /*storage=*/nullptr,
       /*storage_offset=*/0, nameAttr, fortranAttrs, cuf::DataAttributeAttr{},
-      /*dummy_arg_no=*/mlir::IntegerAttr{});
+      /*dummy_arg_no=*/mlir::IntegerAttr{}, /*alignment=*/mlir::IntegerAttr{});
 }
 
 mlir::Value fir::FirOpBuilder::genStackSave(mlir::Location loc) {
diff --git a/flang/lib/Optimizer/Builder/HLFIRTools.cpp b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
index 231c8f282cdcb3..03e5bf9c163660 100644
--- a/flang/lib/Optimizer/Builder/HLFIRTools.cpp
+++ b/flang/lib/Optimizer/Builder/HLFIRTools.cpp
@@ -1536,6 +1536,31 @@ hlfir::Entity hlfir::createStackTempFromMold(mlir::Location loc,
   return hlfir::Entity{declareOp.getBase()};
 }
 
+std::optional<std::int64_t> hlfir::getDeclaredAlignment(mlir::Value var) {
+  while (var) {
+    mlir::Operation *defOp = var.getDefiningOp();
+    if (!defOp)
+      return std::nullopt;
+    if (auto declare = mlir::dyn_cast<hlfir::DeclareOp>(defOp))
+      return declare.getAlignment();
+    if (auto declare = mlir::dyn_cast<fir::DeclareOp>(defOp))
+      return declare.getAlignment();
+    if (auto designate = mlir::dyn_cast<hlfir::DesignateOp>(defOp)) {
+      var = designate.getMemref();
+      continue;
+    }
+    return std::nullopt;
+  }
+  return std::nullopt;
+}
+
+void hlfir::setDeclaredAlignment(mlir::Value var, std::int64_t alignment) {
+  if (auto declare = var.getDefiningOp<hlfir::DeclareOp>())
+    declare.setAlignment(alignment);
+  else if (auto declare = var.getDefiningOp<fir::DeclareOp>())
+    declare.setAlignment(alignment);
+}
+
 hlfir::EntityWithAttributes
 hlfir::convertCharacterKind(mlir::Location loc, fir::FirOpBuilder &builder,
                             hlfir::Entity scalarChar, int toKind) {
diff --git a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
index 9503d64bd5d374..710409d2b038f1 100644
--- a/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
+++ b/flang/lib/Optimizer/HLFIR/IR/HLFIROps.cpp
@@ -281,7 +281,8 @@ void hlfir::DeclareOp::build(
     argNoAttr = builder.getUI32IntegerAttr(dummy_arg_no);
   build(builder, result, {hlfirVariableType, firVarType}, memref, shape,
         typeparams, dummy_scope, storage, storage_offset, nameAttr,
-        fortran_attrs, data_attr, /*skip_rebox=*/mlir::UnitAttr{}, argNoAttr);
+        fortran_attrs, data_attr, /*skip_rebox=*/mlir::UnitAttr{}, argNoAttr,
+        /*alignment=*/mlir::IntegerAttr{});
 }
 
 llvm::LogicalResult hlfir::DeclareOp::verify() {
diff --git a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
index c223b07c4bf583..11d9bc3a3377c6 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/ConvertToFIR.cpp
@@ -435,7 +435,7 @@ class DeclareOpConversion : public mlir::OpRewritePattern<hlfir::DeclareOp> {
         /*storage=*/declareOp.getStorage(),
         /*storage_offset=*/declareOp.getStorageOffset(),
         declareOp.getUniqName(), fortranAttrs, dataAttr,
-        declareOp.getDummyArgNoAttr());
+        declareOp.getDummyArgNoAttr(), declareOp.getAlignmentAttr());
 
     // Propagate other attributes from hlfir.declare to fir.declare.
     // OpenACC's acc.declare is one example. Right now, the propagation
diff --git a/flang/test/Fir/declare.fir b/flang/test/Fir/declare.fir
index b30c38d7b66e31..75ddde6ef3942e 100644
--- a/flang/test/Fir/declare.fir
+++ b/flang/test/Fir/declare.fir
@@ -162,3 +162,11 @@ func.func @vars_within_physical_storage() {
   %8 = fir.declare %7 storage (%1[4]) uniq_name("b") : (!fir.ref<f32>, !fir.ref<!fir.array<8xi8>>) -> !fir.ref<f32>
   return
 }
+
+func.func @aligned_declare(%arg0: !fir.ref<f32>) {
+  %0 = fir.declare %arg0 uniq_name("a") alignment(64) : (!fir.ref<f32>) -> !fir.ref<f32>
+  return
+}
+// CHECK-LABEL:   func.func @aligned_declare(
+// CHECK-SAME:    %[[VAL_0:.*]]: !fir.ref<f32>) {
+// CHECK:  %[[VAL_1:.*]] = fir.declare %[[VAL_0]] uniq_name("a") alignment(64) : (!fir.ref<f32>) -> !fir.ref<f32>
diff --git a/flang/test/HLFIR/declare-codegen.fir b/flang/test/HLFIR/declare-codegen.fir
index 4908a19d488c51..b535bc971a4137 100644
--- a/flang/test/HLFIR/declare-codegen.fir
+++ b/flang/test/HLFIR/declare-codegen.fir
@@ -294,3 +294,11 @@ func.func @vars_within_physical_storage() {
 // CHECK:           %[[VAL_9:.*]] = fir.declare %[[VAL_8]] storage(%[[VAL_2]][4]) uniq_name("b") : (!fir.ref<f32>, !fir.ref<!fir.array<8xi8>>) -> !fir.ref<f32>
 // CHECK:           return
 // CHECK:         }
+
+func.func @aligned_declare(%arg0: !fir.ref<f32>) {
+  %0:2 = hlfir.declare %arg0 uniq_name("a") alignment(64) : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  return
+}
+// CHECK-LABEL:   func.func @aligned_declare(
+// CHECK-SAME:    %[[VAL_0:.*]]: !fir.ref<f32>) {
+// CHECK:         %[[VAL_1:.*]] = fir.declare %[[VAL_0]] uniq_name("a") alignment(64) : (!fir.ref<f32>) -> !fir.ref<f32>
diff --git a/flang/test/HLFIR/declare.fir b/flang/test/HLFIR/declare.fir
index f7c5a5c3c52fae..6603dd53a0bdd6 100644
--- a/flang/test/HLFIR/declare.fir
+++ b/flang/test/HLFIR/declare.fir
@@ -179,3 +179,11 @@ func.func @vars_within_physical_storage() {
 // CHECK:           %[[VAL_2:.*]] = fir.address_of(@block_) : !fir.ref<!fir.array<8xi8>>
 // CHECK:           %[[VAL_6:.*]]:2 = hlfir.declare %{{.*}} storage(%[[VAL_2]][0]) uniq_name("a") : (!fir.ref<f32>, !fir.ref<!fir.array<8xi8>>) -> (!fir.ref<f32>, !fir.ref<f32>)
 // CHECK:           %[[VAL_9:.*]]:2 = hlfir.declare %{{.*}} storage(%[[VAL_2]][4]) uniq_name("b") : (!fir.ref<f32>, !fir.ref<!fir.array<8xi8>>) -> (!fir.ref<f32>, !fir.ref<f32>)
+
+func.func @aligned_declare(%arg0: !fir.ref<f32>) {
+  %0:2 = hlfir.declare %arg0 uniq_name("a") alignment(64) : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
+  return
+}
+// CHECK-LABEL:   func.func @aligned_declare(
+// CHECK-SAME:    %[[VAL_0:.*]]: !fir.ref<f32>) {
+// CHECK:  %[[VAL_1:.*]]:2 = hlfir.declare %[[VAL_0]] uniq_name("a") alignment(64) : (!fir.ref<f32>) -> (!fir.ref<f32>, !fir.ref<f32>)
diff --git a/flang/unittests/Optimizer/FortranVariableTest.cpp b/flang/unittests/Optimizer/FortranVariableTest.cpp
index d2ed5547357622..7b31b6ec1766ec 100644
--- a/flang/unittests/Optimizer/FortranVariableTest.cpp
+++ b/flang/unittests/Optimizer/FortranVariableTest.cpp
@@ -53,7 +53,7 @@ TEST_F(FortranVariableTest, SimpleScalar) {
       /*dummy_scope=*/nullptr, /*storage=*/nullptr, /*storage_offset=*/0, name,
       /*fortran_attrs=*/fir::FortranVariableFlagsAttr{},
       /*data_attr=*/cuf::DataAttributeAttr{},
-      /*dummy_arg_no=*/mlir::IntegerAttr{});
+      /*dummy_arg_no=*/mlir::IntegerAttr{}, /*alignment=*/mlir::IntegerAttr{});
 
   fir::FortranVariableOpInterface fortranVariable = declare;
   EXPECT_FALSE(fortranVariable.isArray());
@@ -81,7 +81,7 @@ TEST_F(FortranVariableTest, CharacterScalar) {
       /*storage=*/nullptr, /*storage_offset=*/0, name,
       /*fortran_attrs=*/fir::FortranVariableFlagsAttr{},
       /*data_attr=*/cuf::DataAttributeAttr{},
-      /*dummy_arg_no=*/mlir::IntegerAttr{});
+      /*dummy_arg_no=*/mlir::IntegerAttr{}, /*alignment=*/mlir::IntegerAttr{});
 
   fir::FortranVariableOpInterface fortranVariable = declare;
   EXPECT_FALSE(fortranVariable.isArray());
@@ -114,7 +114,7 @@ TEST_F(FortranVariableTest, SimpleArray) {
       /*storage=*/nullptr, /*storage_offset=*/0, name,
       /*fortran_attrs=*/fir::FortranVariableFlagsAttr{},
       /*data_attr=*/cuf::DataAttributeAttr{},
-      /*dummy_arg_no=*/mlir::IntegerAttr{});
+      /*dummy_arg_no=*/mlir::IntegerAttr{}, /*alignment=*/mlir::IntegerAttr{});
 
   fir::FortranVariableOpInterface fortranVariable = declare;
   EXPECT_TRUE(fortranVariable.isArray());
@@ -147,7 +147,7 @@ TEST_F(FortranVariableTest, CharacterArray) {
       /*storage_offset=*/0, name,
       /*fortran_attrs=*/fir::FortranVariableFlagsAttr{},
       /*data_attr=*/cuf::DataAttributeAttr{},
-      /*dummy_arg_no=*/mlir::IntegerAttr{});
+      /*dummy_arg_no=*/mlir::IntegerAttr{}, /*alignment=*/mlir::IntegerAttr{});
 
   fir::FortranVariableOpInterface fortranVariable = declare;
   EXPECT_TRUE(fortranVariable.isArray());
@@ -219,7 +219,8 @@ TEST_F(FortranVariableTest, OptionalBoxThroughAssert) {
         builder->getStringAttr("x"),
         fir::FortranVariableFlagsAttr::get(&context, flags),
         /*data_attr=*/cuf::DataAttributeAttr{},
-        /*dummy_arg_no=*/mlir::IntegerAttr{});
+        /*dummy_arg_no=*/mlir::IntegerAttr{},
+        /*alignment=*/mlir::IntegerAttr{});
     auto guard = fir::AssertOp::create(*builder, loc, condition,
         fir::RuntimeCheckKind::conformance, "mismatch", mlir::ValueRange{},
         mlir::ValueRange{declare});



More information about the flang-commits mailing list