[flang-commits] [flang] 3eeb9b2 - [flang] add AllocationPolicy attribute to module and use it in InlineHLFIRCopy (#222013)

via flang-commits flang-commits at lists.llvm.org
Thu Sep 10 05:05:52 PDT 2026


Author: jeanPerier
Date: 2026-09-10T14:05:47+02:00
New Revision: 3eeb9b2bdb2a9ac4020a8ab8729e96ecb283691c

URL: https://github.com/llvm/llvm-project/commit/3eeb9b2bdb2a9ac4020a8ab8729e96ecb283691c
DIFF: https://github.com/llvm/llvm-project/commit/3eeb9b2bdb2a9ac4020a8ab8729e96ecb283691c.diff

LOG: [flang] add AllocationPolicy attribute to module and use it in InlineHLFIRCopy (#222013)

The StackArrays/AllocationPlacement pass cannot move fir.allocmem
created during InlineHLFIRCopy for the copy-in/cop-out buffers because
of there placement in branches.

Moving these allocmem outside of the branches is suboptimal as these
allocations may never occur at runtime (when the data is actually
already contiguous).
Extending the StackArrays pass is doable but very tedious (the data flow
analysis passes used in the pass cannot recognized "same
predicates"/understand that the two fir.if block that
allocate/deallocates will be both reached or never reached (even when
modifying InlineHLFIRCopy to use the same SSA value for both fir.if). So
this requires custom logic, and was adding 200 lines of non trivial code
to audit and I did not like it.

I decided to expose the allocation policy so that other passes can rely
on it. To make it the most flexible and future proof this is done via a
new module attribute instead of threading options in the driver to this
and that pass. This way, it will also be possible to have custom per
function policy if ever needed.

The InlineHLFIRCopy is updated to apply the small array threshold policy
(that decide when constant size arrays should always be placed on the
stack, regardless of the -fstack-array/-fno-stack-array). I also
increased this threshold from 64 to 1024 so that it cover my application
use case. This increase is in line with the industry practice (NV, IFX,
and gfortran will all allocate few Kb temporary copy-in/out buffers on
the stack, regardless of the optimization level). For now, I decided to
not apply the `-fstack-array` aspect as it is on by default with flang
and I want to measure the effect first before doing so (gfortran/ifx
also control dynamic copy-in/out buffer allocations with the stack-array
related flag, so there is a good chance we end-up doing it at some
point).

Added: 
    flang/include/flang/Optimizer/Support/AllocationPolicy.h
    flang/lib/Optimizer/Support/AllocationPolicy.cpp
    flang/test/Driver/allocation-policy.f90
    flang/test/Fir/allocation-policy-attr.fir
    flang/test/Fir/allocation-policy-pipeline.fir
    flang/test/HLFIR/inline-hlfir-copy-stack.fir
    flang/test/Transforms/allocation-policy-precedence.fir

Modified: 
    flang/include/flang/Optimizer/Dialect/FIRAttr.td
    flang/include/flang/Optimizer/Dialect/Support/FIRContext.h
    flang/include/flang/Optimizer/HLFIR/Passes.h
    flang/include/flang/Optimizer/HLFIR/Passes.td
    flang/include/flang/Optimizer/Passes/CommandLineOpts.h
    flang/include/flang/Optimizer/Passes/Pipelines.h
    flang/include/flang/Optimizer/Transforms/Passes.h
    flang/include/flang/Optimizer/Transforms/Passes.td
    flang/lib/Lower/Bridge.cpp
    flang/lib/Optimizer/HLFIR/Transforms/InlineHLFIRCopy.cpp
    flang/lib/Optimizer/Passes/CommandLineOpts.cpp
    flang/lib/Optimizer/Passes/Pipelines.cpp
    flang/lib/Optimizer/Support/CMakeLists.txt
    flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
    flang/test/HLFIR/inline-hlfir-copy.fir
    flang/test/Transforms/allocation-placement.fir

Removed: 
    flang/include/flang/Optimizer/Transforms/AllocationPlacementPolicy.h


################################################################################
diff  --git a/flang/include/flang/Optimizer/Dialect/FIRAttr.td b/flang/include/flang/Optimizer/Dialect/FIRAttr.td
index 070bd72d29df7..88d7964768b5a 100644
--- a/flang/include/flang/Optimizer/Dialect/FIRAttr.td
+++ b/flang/include/flang/Optimizer/Dialect/FIRAttr.td
@@ -307,6 +307,46 @@ def fir_UseRenameAttr : fir_Attr<"UseRename"> {
   let assemblyFormat = "`<` $local_name `,` $symbol `>`";
 }
 
+//===----------------------------------------------------------------------===//
+// Allocation policy
+//===----------------------------------------------------------------------===//
+
+// Module attribute recording where array allocations should be placed. It is
+// set by lowering and read back with fir::getAllocationPolicy, so that
+// policy-aware passes make consistent placement decisions and dumped IR
+// replays with the policy it was compiled with.
+//
+// fir::AllocationPolicy.
+def fir_AllocationPolicyAttr : fir_Attr<"AllocationPolicy"> {
+  let mnemonic = "allocation_policy";
+  let summary = "Records the module-level array allocation policy";
+  let description = [{
+    Records the policy used by policy-aware passes to choose between stack
+    (`fir.alloca`) and heap (`fir.allocmem`) storage for array allocations.
+    Lowering sets this attribute from the compiler options. Passes that create
+    allocations can consult it when selecting the allocation operation, and
+    allocation-placement uses it when reconsidering existing allocations.
+
+    Fields:
+      - stack_arrays: place all arrays on the stack when possible
+        (`-fstack-arrays`).
+      - small_array_threshold: maximum size in bytes of a "small" constant-size
+        array temporary.
+      - total_stack_limit: per-function budget in bytes for small arrays placed
+        on the stack.
+  }];
+  let parameters = (ins
+    "bool":$stack_arrays,
+    "uint64_t":$small_array_threshold,
+    "uint64_t":$total_stack_limit
+  );
+  let assemblyFormat = "`<` struct(params) `>`";
+}
+
+//===----------------------------------------------------------------------===//
+// OpenACC
+//===----------------------------------------------------------------------===//
+
 // Fortran-specific variable information for OpenACC.
 // Carries metadata that cannot be recovered from the FIR type system alone
 // and is required in the FIR implementation of OpenACC type interfaces.

diff  --git a/flang/include/flang/Optimizer/Dialect/Support/FIRContext.h b/flang/include/flang/Optimizer/Dialect/Support/FIRContext.h
index 27eee3a101e29..a7d17ec6f63d1 100644
--- a/flang/include/flang/Optimizer/Dialect/Support/FIRContext.h
+++ b/flang/include/flang/Optimizer/Dialect/Support/FIRContext.h
@@ -135,6 +135,10 @@ CudaHeapAllocMode getCudaHeapAllocMode(mlir::ModuleOp mod);
 void setCudaHeapAllocMode(mlir::Operation *op, CudaHeapAllocMode mode);
 CudaHeapAllocMode getCudaHeapAllocMode(mlir::Operation *op);
 
+/// The array allocation policy is also recorded on the module, but its getter
+/// and setter live in flang/Optimizer/Support/AllocationPolicy.h: they build a
+/// FIR attribute, which this library cannot depend on.
+
 /// Helper for determining the target from the host, etc. Tools may use this
 /// function to provide a consistent interpretation of the `--target=<string>`
 /// command-line option.

diff  --git a/flang/include/flang/Optimizer/HLFIR/Passes.h b/flang/include/flang/Optimizer/HLFIR/Passes.h
index 2ae1ecd51391e..42baa9566c4fe 100644
--- a/flang/include/flang/Optimizer/HLFIR/Passes.h
+++ b/flang/include/flang/Optimizer/HLFIR/Passes.h
@@ -13,6 +13,7 @@
 #ifndef FORTRAN_OPTIMIZER_HLFIR_PASSES_H
 #define FORTRAN_OPTIMIZER_HLFIR_PASSES_H
 
+#include "flang/Optimizer/Support/AllocationPolicy.h"
 #include "flang/Support/FPMaxminBehavior.h"
 #include "mlir/Dialect/Func/IR/FuncOps.h"
 #include "mlir/Pass/Pass.h"

diff  --git a/flang/include/flang/Optimizer/HLFIR/Passes.td b/flang/include/flang/Optimizer/HLFIR/Passes.td
index 9ea49ca265610..ecde8e093c061 100644
--- a/flang/include/flang/Optimizer/HLFIR/Passes.td
+++ b/flang/include/flang/Optimizer/HLFIR/Passes.td
@@ -112,6 +112,26 @@ def InlineHLFIRAssign : Pass<"inline-hlfir-assign"> {
 
 def InlineHLFIRCopy : Pass<"inline-hlfir-copy"> {
   let summary = "Inline hlfir.copy_in and hlfir.copy_out operations";
+  let description = [{
+    Replaces eligible `hlfir.copy_in` and `hlfir.copy_out` operations with an
+    explicit temporary and element-wise copy. The temporary allocation uses the
+    small-array threshold from the module's `fir.allocation_policy` attribute.
+  }];
+  let dependentDialects = [
+    "fir::FIROpsDialect", "mlir::DLTIDialect", "mlir::LLVM::LLVMDialect",
+    "mlir::arith::ArithDialect"
+  ];
+  // Normally taken from the fir.allocation_policy module attribute; this
+  // option overrides it, and only when set explicitly.
+  let options = [
+    Option<"smallArrayThresholdBytes", "small-array-threshold", "uint64_t",
+           /*default=*/"fir::AllocationPolicy::smallArrayThresholdBytesDefault",
+           "When provided, override the small array threshold from the "
+           "fir.allocation_policy module attribute. "
+           "Copy-in buffers whose size is a compile-time constant of at most "
+           "this many bytes are allocated on the stack. Bigger buffers, and "
+           "buffers with a runtime size, are allocated on the heap.">
+  ];
 }
 
 def PropagateFortranVariableAttributes : Pass<"propagate-fortran-attrs"> {

diff  --git a/flang/include/flang/Optimizer/Passes/CommandLineOpts.h b/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
index b2ce29a2fefe0..35eab740a28dd 100644
--- a/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
+++ b/flang/include/flang/Optimizer/Passes/CommandLineOpts.h
@@ -61,14 +61,6 @@ extern llvm::cl::opt<bool> useOldAliasTags;
 /// passes with the unified allocation-placement pass.
 extern llvm::cl::opt<bool> enableAllocationPlacement;
 
-/// Constant-size arrays up to this many bytes are considered "small" and placed
-/// on the stack by the allocation-placement pass.
-extern llvm::cl::opt<std::size_t> allocationPlacementSmallArraySize;
-
-/// Per-function budget (in bytes) for small arrays placed on the stack by the
-/// allocation-placement pass.
-extern llvm::cl::opt<std::size_t> allocationPlacementStackLimit;
-
 /// CodeGen Passes
 extern llvm::cl::opt<bool> disableCodeGenRewrite;
 extern llvm::cl::opt<bool> disableTargetRewrite;

diff  --git a/flang/include/flang/Optimizer/Passes/Pipelines.h b/flang/include/flang/Optimizer/Passes/Pipelines.h
index 744c4c584d46a..ce4af1b6a1f36 100644
--- a/flang/include/flang/Optimizer/Passes/Pipelines.h
+++ b/flang/include/flang/Optimizer/Passes/Pipelines.h
@@ -99,8 +99,6 @@ void addCfgConversionPass(mlir::PassManager &pm,
 
 void addMemoryAllocationOpt(mlir::PassManager &pm);
 
-void addAllocationPlacement(mlir::PassManager &pm, bool stackArrays);
-
 void addCodeGenRewritePass(mlir::PassManager &pm, bool preserveDeclare);
 
 void addTargetRewritePass(mlir::PassManager &pm);

diff  --git a/flang/include/flang/Optimizer/Support/AllocationPolicy.h b/flang/include/flang/Optimizer/Support/AllocationPolicy.h
new file mode 100644
index 0000000000000..cb4e57628744f
--- /dev/null
+++ b/flang/include/flang/Optimizer/Support/AllocationPolicy.h
@@ -0,0 +1,146 @@
+//===-- Optimizer/Support/AllocationPolicy.h --------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
+//
+//===----------------------------------------------------------------------===//
+//
+// The policy controlling where array allocations should live: on the stack
+// (fir.alloca) or on the heap (fir.allocmem). Lowering records it on the module
+// so that policy-aware passes can make consistent decisions and dumped IR
+// replays with the policy it was compiled with.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef FORTRAN_OPTIMIZER_SUPPORT_ALLOCATIONPOLICY_H
+#define FORTRAN_OPTIMIZER_SUPPORT_ALLOCATIONPOLICY_H
+
+#include <cstddef>
+#include <cstdint>
+#include <functional>
+#include <optional>
+
+namespace mlir {
+class ModuleOp;
+class Operation;
+} // namespace mlir
+
+namespace fir {
+
+/// Tunables controlling where array allocations are placed. The static
+/// constants below are the only definition of the defaults. Helpers that build
+/// the fir.allocation_policy attribute and pass options derive their defaults
+/// from them.
+struct AllocationPolicy {
+  static constexpr bool stackArraysDefault = false;
+  static constexpr std::uint64_t smallArrayThresholdBytesDefault = 1024;
+  static constexpr std::uint64_t totalStackLimitBytesDefault =
+      4ull * 1024 * 1024;
+
+  /// Place all array allocations on the stack when possible (-fstack-arrays).
+  /// When false, use the size-based policy described by the fields below.
+  bool stackArrays = stackArraysDefault;
+  /// Constant-size arrays up to this many bytes are considered "small".
+  std::uint64_t smallArrayThresholdBytes = smallArrayThresholdBytesDefault;
+  /// Per-function budget (in bytes) for small arrays placed on the stack.
+  std::uint64_t totalStackLimitBytes = totalStackLimitBytesDefault;
+};
+
+/// Desired placement for an array allocation.
+enum class AllocationPlacement {
+  /// The allocation should live on the stack (fir.alloca).
+  Stack,
+  /// The allocation should live on the heap (fir.allocmem).
+  Heap,
+  /// The allocation should be left where it currently is.
+  Leave,
+};
+
+/// Facts about an array allocation that are known before it is created. This is
+/// the input to shouldAllocateOnStack, which lets code that generates
+/// temporaries pick the right kind of allocation upfront instead of relying on
+/// the allocation-placement pass to fix it up afterwards. Deciding upfront is
+/// preferable when the generator also emits the deallocation, because the
+/// lifetime is then known by construction and does not have to be proven.
+struct PendingAllocationInfo {
+  /// True if the allocation is a compiler temporary (as opposed to a user
+  /// variable).
+  bool isTemporary = false;
+  /// True if the allocation has a runtime-determined size. Note this is not the
+  /// same as !byteSize: a constant-size array may have no computable byteSize
+  /// (e.g. when no data layout is available), in which case it is not dynamic
+  /// but its size is still unknown.
+  bool isDynamic = false;
+  /// The constant size of the allocation in bytes, if it can be determined.
+  std::optional<std::int64_t> byteSize;
+};
+
+/// Facts about a single existing array allocation used to decide its placement.
+struct AllocationInfo : PendingAllocationInfo {
+  /// The allocation operation (fir.alloca or fir.allocmem).
+  mlir::Operation *op = nullptr;
+  /// True if the allocation currently lives on the stack (fir.alloca).
+  bool isCurrentlyOnStack = false;
+};
+
+/// Size-based placement policy, usable before the allocation is created.
+/// Decides whether an allocation described by \p info should live on the stack,
+/// given the \p policy in effect and the per-function stack bytes already
+/// committed to the stack (\p stackBytesUsed).
+bool shouldAllocateOnStack(const PendingAllocationInfo &info,
+                           const AllocationPolicy &policy,
+                           std::size_t stackBytesUsed);
+
+/// Decide where an existing allocation described by \p info should live given
+/// the \p policy in effect and the per-function stack bytes already committed
+/// to the stack (\p stackBytesUsed). The caller is responsible for updating \p
+/// stackBytesUsed based on the returned decision.
+AllocationPlacement decideAllocationPlacement(const AllocationInfo &info,
+                                              const AllocationPolicy &policy,
+                                              std::size_t stackBytesUsed);
+
+/// Let a pass option override one field of the policy recorded on the module.
+/// Only an option that was set explicitly (in a pass pipeline string or on the
+/// command line) overrides it; an option left at its default value does not, so
+/// that the module attribute stays authoritative in a normal compilation and
+/// tests can still pin a single field without restating the whole policy.
+template <typename FieldT, typename OptionT>
+void overrideIfExplicitlySet(FieldT &field, const OptionT &option) {
+  if (option.hasValue())
+    field = static_cast<FieldT>(option);
+}
+
+/// Placement decision hook. Has the same signature as decideAllocationPlacement
+/// so the policy can be fully overridden (e.g. 
diff erent thresholds inside
+/// device routines or parallel regions); a hook may adjust the policy and
+/// delegate to decideAllocationPlacement.
+using AllocationPlacementHook = std::function<AllocationPlacement(
+    const AllocationInfo & /*info*/, const AllocationPolicy & /*policy*/,
+    std::size_t /*stackBytesUsed*/)>;
+
+/// Build the policy described by the command line options above, taking the
+/// -fstack-arrays part of it from \p stackArrays. Lowering records the result
+/// on the module with setAllocationPolicy so that passes consulting the policy
+/// use the same values.
+AllocationPolicy getCommandLineAllocationPolicy(bool stackArrays);
+
+/// Record \p policy on \p mod as a fir.allocation_policy attribute, replacing
+/// any policy already recorded there.
+void setAllocationPolicy(mlir::ModuleOp mod, const AllocationPolicy &policy);
+
+/// Get the policy recorded on \p mod, or the defaults if none was recorded.
+AllocationPolicy getAllocationPolicy(mlir::ModuleOp mod);
+
+/// Get the policy in effect for \p op, which is the one recorded on its
+/// enclosing ModuleOp. Returns the defaults if \p op is not inside a module or
+/// if no policy was recorded.
+AllocationPolicy getAllocationPolicy(mlir::Operation *op);
+
+} // namespace fir
+
+#endif // FORTRAN_OPTIMIZER_SUPPORT_ALLOCATIONPOLICY_H

diff  --git a/flang/include/flang/Optimizer/Transforms/AllocationPlacementPolicy.h b/flang/include/flang/Optimizer/Transforms/AllocationPlacementPolicy.h
deleted file mode 100644
index 001e4cdfe06cc..0000000000000
--- a/flang/include/flang/Optimizer/Transforms/AllocationPlacementPolicy.h
+++ /dev/null
@@ -1,90 +0,0 @@
-//===-- Optimizer/Transforms/AllocationPlacementPolicy.h --------*- C++ -*-===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-//
-// This header defines the policy used by the allocation-placement pass to
-// decide whether an array allocation should live on the stack (fir.alloca) or
-// on the heap (fir.allocmem). The policy is expressed with tunable thresholds
-// and a per-function stack budget, and can be customized through a hook (e.g.
-// with 
diff erent thresholds inside device routines or parallel regions).
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef FORTRAN_OPTIMIZER_TRANSFORMS_ALLOCATIONPLACEMENTPOLICY_H
-#define FORTRAN_OPTIMIZER_TRANSFORMS_ALLOCATIONPLACEMENTPOLICY_H
-
-#include <cstddef>
-#include <cstdint>
-#include <functional>
-#include <optional>
-
-namespace mlir {
-class Operation;
-} // namespace mlir
-
-namespace fir {
-
-/// Desired placement for an array allocation.
-enum class AllocationPlacement {
-  /// The allocation should live on the stack (fir.alloca).
-  Stack,
-  /// The allocation should live on the heap (fir.allocmem).
-  Heap,
-  /// The allocation should be left where it currently is.
-  Leave,
-};
-
-/// Tunable thresholds controlling where array allocations are placed.
-struct AllocationPlacementThresholds {
-  /// Place all array allocations on the stack when possible (-fstack-arrays).
-  /// When false, use the size/kind-based placement policy.
-  bool stackArrays = false;
-  /// Constant-size arrays up to this many bytes are considered "small".
-  std::size_t smallArrayThresholdBytes = 64;
-  /// Per-function budget (in bytes) for small arrays placed on the stack.
-  std::size_t totalStackLimitBytes = 4ull * 1024 * 1024;
-};
-
-/// Facts about a single array allocation used to decide its placement.
-struct AllocationInfo {
-  /// The allocation operation (fir.alloca or fir.allocmem).
-  mlir::Operation *op = nullptr;
-  /// True if the allocation currently lives on the stack (fir.alloca).
-  bool isCurrentlyOnStack = false;
-  /// True if the allocation is a compiler temporary (as opposed to a user
-  /// variable).
-  bool isTemporary = false;
-  /// True if the allocation has a runtime-determined size. Note this is not the
-  /// same as !byteSize: a constant-size array may have no computable byteSize
-  /// (e.g. when no data layout is available), in which case it is not dynamic
-  /// but its size is still unknown.
-  bool isDynamic = false;
-  /// The constant size of the allocation in bytes, if it can be determined.
-  std::optional<std::int64_t> byteSize;
-};
-
-/// Default placement policy. Decides where \p info should live given \p
-/// thresholds and the per-function stack bytes already committed to the stack
-/// (\p stackBytesUsed). The caller is responsible for updating \p
-/// stackBytesUsed based on the returned decision.
-AllocationPlacement
-decideAllocationPlacement(const AllocationInfo &info,
-                          const AllocationPlacementThresholds &thresholds,
-                          std::size_t stackBytesUsed);
-
-/// Placement decision hook. Has the same signature as decideAllocationPlacement
-/// so the policy can be fully overridden (e.g. 
diff erent thresholds inside
-/// device routines or parallel regions); a hook may adjust the thresholds and
-/// delegate to decideAllocationPlacement.
-using AllocationPlacementHook = std::function<AllocationPlacement(
-    const AllocationInfo & /*info*/,
-    const AllocationPlacementThresholds & /*thresholds*/,
-    std::size_t /*stackBytesUsed*/)>;
-
-} // namespace fir
-
-#endif // FORTRAN_OPTIMIZER_TRANSFORMS_ALLOCATIONPLACEMENTPOLICY_H

diff  --git a/flang/include/flang/Optimizer/Transforms/Passes.h b/flang/include/flang/Optimizer/Transforms/Passes.h
index e777ac5da6658..83aef1e8fdb55 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.h
+++ b/flang/include/flang/Optimizer/Transforms/Passes.h
@@ -9,7 +9,7 @@
 #ifndef FORTRAN_OPTIMIZER_TRANSFORMS_PASSES_H
 #define FORTRAN_OPTIMIZER_TRANSFORMS_PASSES_H
 
-#include "flang/Optimizer/Transforms/AllocationPlacementPolicy.h"
+#include "flang/Optimizer/Support/AllocationPolicy.h"
 #include "mlir/Dialect/LLVMIR/LLVMAttrs.h"
 #include "mlir/Pass/Pass.h"
 #include "mlir/Pass/PassRegistry.h"

diff  --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 865b9ccbb359c..a51fffdbc2b07 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -340,20 +340,24 @@ def AllocationPlacement : Pass<"allocation-placement", "mlir::func::FuncOp"> {
     per-function stack budget, and whether the allocation is a user variable or
     a compiler temporary. Heap-to-stack conversions are only performed where the
     allocation is provably freed on all paths through the function.
+
+    The policy normally comes from the fir.allocation_policy module attribute.
+    These options override it field by field, and only when set explicitly.
   }];
   let dependentDialects = [
     "fir::FIROpsDialect", "mlir::DLTIDialect", "mlir::LLVM::LLVMDialect",
     "mlir::arith::ArithDialect"
   ];
   let options = [
-    Option<"stackArrays", "stack-arrays", "bool", /*default=*/"false",
+    Option<"stackArrays", "stack-arrays", "bool",
+           /*default=*/"fir::AllocationPolicy::stackArraysDefault",
            "Place all array allocations on the stack (-fstack-arrays); "
-           "otherwise use the size/kind-based placement policy.">,
-    Option<"smallArrayThresholdBytes", "small-array-threshold", "std::size_t",
-           /*default=*/"64",
+           "otherwise use the size-based placement policy.">,
+    Option<"smallArrayThresholdBytes", "small-array-threshold", "uint64_t",
+           /*default=*/"fir::AllocationPolicy::smallArrayThresholdBytesDefault",
            "Constant-size arrays up to this many bytes are considered small.">,
-    Option<"totalStackLimitBytes", "total-stack-limit", "std::size_t",
-           /*default=*/"4194304",
+    Option<"totalStackLimitBytes", "total-stack-limit", "uint64_t",
+           /*default=*/"fir::AllocationPolicy::totalStackLimitBytesDefault",
            "Per-function budget (bytes) for small arrays placed on the stack.">
   ];
 }

diff  --git a/flang/lib/Lower/Bridge.cpp b/flang/lib/Lower/Bridge.cpp
index 8e33239be0f8b..5ea1850669297 100644
--- a/flang/lib/Lower/Bridge.cpp
+++ b/flang/lib/Lower/Bridge.cpp
@@ -52,6 +52,7 @@
 #include "flang/Optimizer/Dialect/FIROps.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "flang/Optimizer/Support/AllocationPolicy.h"
 #include "flang/Optimizer/Support/DataLayout.h"
 #include "flang/Optimizer/Support/FatalError.h"
 #include "flang/Optimizer/Support/InternalNames.h"
@@ -6944,6 +6945,8 @@ Fortran::lower::LoweringBridge::LoweringBridge(
   fir::setIdent(*module, Fortran::common::getFlangFullVersion());
   fir::setRelocationModel(*module, cgOpts.getRelocationModel());
   fir::setIsPIE(*module, cgOpts.IsPIE);
+  fir::setAllocationPolicy(
+      *module, fir::getCommandLineAllocationPolicy(cgOpts.StackArrays));
   if (cgOpts.RecordCommandLine)
     fir::setCommandline(*module, *cgOpts.RecordCommandLine);
   // Under -gpu=mem:unified|managed, host heap allocations use the matching

diff  --git a/flang/lib/Optimizer/HLFIR/Transforms/InlineHLFIRCopy.cpp b/flang/lib/Optimizer/HLFIR/Transforms/InlineHLFIRCopy.cpp
index f44db65a7a847..4cb7403370037 100644
--- a/flang/lib/Optimizer/HLFIR/Transforms/InlineHLFIRCopy.cpp
+++ b/flang/lib/Optimizer/HLFIR/Transforms/InlineHLFIRCopy.cpp
@@ -19,8 +19,15 @@
 #include "flang/Optimizer/Builder/FIRBuilder.h"
 #include "flang/Optimizer/Builder/HLFIRTools.h"
 #include "flang/Optimizer/Dialect/FIRType.h"
+#include "flang/Optimizer/Dialect/Support/FIRContext.h"
 #include "flang/Optimizer/HLFIR/HLFIROps.h"
+#include "flang/Optimizer/HLFIR/Passes.h"
 #include "flang/Optimizer/OpenMP/Passes.h"
+#include "flang/Optimizer/Support/AllocationPolicy.h"
+#include "flang/Optimizer/Support/DataLayout.h"
+#include "mlir/Dialect/Arith/IR/Arith.h"
+#include "mlir/Dialect/DLTI/DLTI.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
 #include "mlir/IR/PatternMatch.h"
 #include "mlir/Support/LLVM.h"
 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
@@ -38,23 +45,81 @@ static llvm::cl::opt<bool> noInlineHLFIRCopy(
     llvm::cl::init(false));
 
 namespace {
+/// Everything needed to compute the constant byte size of a buffer, gathered
+/// once by the pass since it is module-level information.
+struct SizeContext {
+  std::optional<mlir::DataLayout> dataLayout;
+  std::optional<fir::KindMapping> kindMap;
+};
+
+/// Gather the module level information needed to compute buffer sizes. Without
+/// a data layout no size can be computed, and all the buffers are then left on
+/// the heap.
+static SizeContext getSizeContext(mlir::Operation *op) {
+  auto module = mlir::dyn_cast<mlir::ModuleOp>(op);
+  if (!module)
+    module = op->getParentOfType<mlir::ModuleOp>();
+  if (!module)
+    return SizeContext{std::nullopt, std::nullopt};
+  return SizeContext{fir::support::getOrSetMLIRDataLayout(
+                         module, /*allowDefaultLayout=*/false),
+                     fir::getKindMapping(module)};
+}
+
 class InlineCopyInConversion : public mlir::OpRewritePattern<hlfir::CopyInOp> {
 public:
-  using mlir::OpRewritePattern<hlfir::CopyInOp>::OpRewritePattern;
+  InlineCopyInConversion(mlir::MLIRContext *context,
+                         const fir::AllocationPolicy &policy,
+                         const SizeContext &sizeContext)
+      : mlir::OpRewritePattern<hlfir::CopyInOp>(context), policy(policy),
+        sizeContext(sizeContext) {}
 
   llvm::LogicalResult
   matchAndRewrite(hlfir::CopyInOp copyIn,
                   mlir::PatternRewriter &rewriter) const override;
+
+private:
+  /// Return true if the copy-in buffer of type \p sequenceType should be
+  /// allocated on the stack rather than on the heap.
+  bool shouldUseStack(mlir::Location loc, mlir::Type sequenceType) const;
+
+  fir::AllocationPolicy policy;
+  const SizeContext &sizeContext;
 };
 
+bool InlineCopyInConversion::shouldUseStack(mlir::Location loc,
+                                            mlir::Type sequenceType) const {
+  // Only buffers with a compile-time constant size are considered. A buffer
+  // with a runtime size would need stack save/restore to avoid growing the
+  // stack when the copy-in is inside a loop. There is also little to gain: for
+  // a big buffer the element-per-element copy costs much more than the
+  // allocation itself.
+  if (fir::hasDynamicSize(sequenceType))
+    return false;
+  if (!sizeContext.dataLayout || !sizeContext.kindMap)
+    return false;
+  auto sizeAndAlignment = fir::getTypeSizeAndAlignment(
+      loc, sequenceType, *sizeContext.dataLayout, *sizeContext.kindMap);
+  if (!sizeAndAlignment)
+    return false;
+
+  fir::PendingAllocationInfo info;
+  info.isTemporary = true;
+  info.isDynamic = false;
+  info.byteSize = static_cast<std::int64_t>(sizeAndAlignment->first);
+  // The per-function stack budget is not tracked here: the
+  // allocation-placement pass sees the fir.alloca generated below and can
+  // still move it back to the heap if the budget turns out to be exceeded.
+  return fir::shouldAllocateOnStack(info, policy, /*stackBytesUsed=*/0);
+}
+
 // Inline a copy_out operation (deallocation only — no copy-back).
 // Generates: if (wasCopied) { freemem(temp) }
 static void inlineCopyOut(fir::FirOpBuilder &builder, mlir::Location loc,
                           mlir::Value tempBox, mlir::Value wasCopied,
                           mlir::Type sequenceType) {
   builder.genIfOp(loc, {}, wasCopied, /*withElseRegion=*/false).genThen([&]() {
-    mlir::Value box = fir::LoadOp::create(builder, loc, tempBox);
-    mlir::Value addr = fir::BoxAddrOp::create(builder, loc, box);
+    mlir::Value addr = fir::BoxAddrOp::create(builder, loc, tempBox);
     auto heapType = fir::HeapType::get(sequenceType);
     mlir::Value heapAddr = fir::ConvertOp::create(builder, loc, heapType, addr);
     fir::FreeMemOp::create(builder, loc, heapAddr);
@@ -112,6 +177,10 @@ InlineCopyInConversion::matchAndRewrite(hlfir::CopyInOp copyIn,
   llvm::SmallVector<mlir::Value> extents =
       hlfir::getIndexExtents(loc, builder, shape);
 
+  // Decide where the buffer will live before creating it, so that the matching
+  // kind of allocation and deallocation is generated.
+  const bool useStack = shouldUseStack(loc, sequenceType);
+
   mlir::Value isContiguous =
       fir::IsContiguousBoxOp::create(builder, loc, inputVariable);
   mlir::Operation::result_range results =
@@ -132,8 +201,11 @@ InlineCopyInConversion::matchAndRewrite(hlfir::CopyInOp copyIn,
           .genElse([&] {
             llvm::StringRef tmpName{".tmp.copy_in"};
             llvm::SmallVector<mlir::Value> lenParams;
-            mlir::Value alloc = builder.createHeapTemporary(
-                loc, sequenceType, tmpName, extents, lenParams);
+            mlir::Value alloc =
+                useStack ? builder.createTemporary(loc, sequenceType, tmpName,
+                                                   extents, lenParams)
+                         : builder.createHeapTemporary(
+                               loc, sequenceType, tmpName, extents, lenParams);
 
             auto declareOp = hlfir::DeclareOp::create(builder, loc, alloc,
                                                       tmpName, shape, lenParams,
@@ -174,17 +246,16 @@ InlineCopyInConversion::matchAndRewrite(hlfir::CopyInOp copyIn,
           .getResults();
 
   mlir::OpResult resultBox = results[0];
-  mlir::OpResult needsCleanup = results[1];
-
-  // Inline the corresponding copyOut (deallocation only).
-  // Store the resultBox first since it's a box value.
-  auto alloca = fir::AllocaOp::create(builder, loc, resultBox.getType());
-  fir::StoreOp::create(builder, loc, resultBox, alloca);
+  mlir::OpResult wasCopied = results[1];
 
-  rewriter.setInsertionPoint(copyOut);
-  fir::FirOpBuilder copyOutBuilder(rewriter, copyOut.getOperation());
-  inlineCopyOut(copyOutBuilder, copyOut.getLoc(), alloca, needsCleanup,
-                sequenceType);
+  // Inline the corresponding copyOut. A stack buffer needs no deallocation, so
+  // there is nothing to generate for it.
+  if (!useStack) {
+    rewriter.setInsertionPoint(copyOut);
+    fir::FirOpBuilder copyOutBuilder(rewriter, copyOut.getOperation());
+    inlineCopyOut(copyOutBuilder, copyOut.getLoc(), resultBox, wasCopied,
+                  sequenceType);
+  }
 
   // Erase the copyOut since we've inlined it
   rewriter.eraseOp(copyOut);
@@ -196,6 +267,8 @@ InlineCopyInConversion::matchAndRewrite(hlfir::CopyInOp copyIn,
 class InlineHLFIRCopyPass
     : public hlfir::impl::InlineHLFIRCopyBase<InlineHLFIRCopyPass> {
 public:
+  using InlineHLFIRCopyBase<InlineHLFIRCopyPass>::InlineHLFIRCopyBase;
+
   void runOnOperation() override {
     mlir::MLIRContext *context = &getContext();
 
@@ -204,9 +277,22 @@ class InlineHLFIRCopyPass
     config.setRegionSimplificationLevel(
         mlir::GreedySimplifyRegionLevel::Disabled);
 
+    // Gather allocation policy for created temporary buffers (heap vs stack).
+    // This is done here because StackArrays cannot promote the heap allocations
+    // created here to stack allocations because of the branches.
+    // StackArrays is not honored yet: copy-in buffers are often unused at
+    // runtime, so it is unclear whether putting large buffers on the stack is
+    // beneficial. Runtime-sized buffers additionally need stack save/restore
+    // to avoid growing the stack when the copy-in sits in a loop.
+    fir::AllocationPolicy policy = fir::getAllocationPolicy(getOperation());
+    policy.stackArrays = false;
+    fir::overrideIfExplicitlySet(policy.smallArrayThresholdBytes,
+                                 smallArrayThresholdBytes);
+    const SizeContext sizeContext = getSizeContext(getOperation());
+
     mlir::RewritePatternSet patterns(context);
     if (!noInlineHLFIRCopy) {
-      patterns.insert<InlineCopyInConversion>(context);
+      patterns.insert<InlineCopyInConversion>(context, policy, sizeContext);
     }
 
     if (mlir::failed(mlir::applyPatternsGreedily(

diff  --git a/flang/lib/Optimizer/Passes/CommandLineOpts.cpp b/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
index 151b01d29774a..bdaa12c13f475 100644
--- a/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
+++ b/flang/lib/Optimizer/Passes/CommandLineOpts.cpp
@@ -65,19 +65,6 @@ EnableOption(AllocationPlacement, "allocation-placement",
              "unified array allocation placement (experimental; replaces "
              "stack-arrays and memory-allocation-opt)");
 
-cl::opt<std::size_t> allocationPlacementSmallArraySize(
-    "allocation-placement-small-array-size",
-    cl::desc("constant-size arrays up to <size> bytes are placed on the stack "
-             "by the allocation-placement pass"),
-    cl::init(64), cl::Hidden);
-
-cl::opt<std::size_t> allocationPlacementStackLimit(
-    "allocation-placement-stack-limit",
-    cl::desc(
-        "per-function budget in bytes for small arrays placed on the stack "
-        "by the allocation-placement pass"),
-    cl::init(4ull * 1024 * 1024), cl::Hidden);
-
 /// CodeGen Passes
 DisableOption(CodeGenRewrite, "codegen-rewrite", "rewrite FIR for codegen");
 DisableOption(TargetRewrite, "target-rewrite", "rewrite FIR for target");

diff  --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 0621b64bed871..ebddd7cdd73f4 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -52,14 +52,6 @@ void addMemoryAllocationOpt(mlir::PassManager &pm) {
   });
 }
 
-void addAllocationPlacement(mlir::PassManager &pm, bool stackArrays) {
-  fir::AllocationPlacementOptions options;
-  options.stackArrays = stackArrays;
-  options.smallArrayThresholdBytes = allocationPlacementSmallArraySize;
-  options.totalStackLimitBytes = allocationPlacementStackLimit;
-  pm.addPass(fir::createAllocationPlacement(options));
-}
-
 void addCodeGenRewritePass(mlir::PassManager &pm, bool preserveDeclare) {
   fir::CodeGenRewriteOptions options;
   options.preserveDeclare = preserveDeclare;
@@ -205,7 +197,7 @@ void createDefaultFIRPreCFGOptimizerPassPipeline(
       fir::CudaHeapAllocPromotionOptions{pc.StackArrays}));
 
   if (enableAllocationPlacement)
-    fir::addAllocationPlacement(pm, pc.StackArrays);
+    pm.addPass(fir::createAllocationPlacement());
   else if (pc.StackArrays)
     pm.addPass(fir::createStackArrays());
   else

diff  --git a/flang/lib/Optimizer/Support/AllocationPolicy.cpp b/flang/lib/Optimizer/Support/AllocationPolicy.cpp
new file mode 100644
index 0000000000000..ad8eca26f8473
--- /dev/null
+++ b/flang/lib/Optimizer/Support/AllocationPolicy.cpp
@@ -0,0 +1,119 @@
+//===-- AllocationPolicy.cpp ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
+//
+//===----------------------------------------------------------------------===//
+
+#include "flang/Optimizer/Support/AllocationPolicy.h"
+#include "flang/Optimizer/Dialect/FIRAttr.h"
+#include "mlir/IR/BuiltinOps.h"
+#include "llvm/Support/CommandLine.h"
+
+static constexpr const char *allocationPolicyName = "fir.allocation_policy";
+
+static llvm::cl::opt<std::uint64_t> allocationPlacementSmallArraySize(
+    "allocation-placement-small-array-size",
+    llvm::cl::desc(
+        "constant-size arrays up to <size> bytes are placed on the stack "
+        "by the allocation-placement pass and by the copy-in inlining"),
+    llvm::cl::init(fir::AllocationPolicy::smallArrayThresholdBytesDefault),
+    llvm::cl::Hidden);
+
+static llvm::cl::opt<std::uint64_t> allocationPlacementStackLimit(
+    "allocation-placement-stack-limit",
+    llvm::cl::desc(
+        "per-function budget in bytes for small arrays placed on the stack "
+        "by the allocation-placement pass"),
+    llvm::cl::init(fir::AllocationPolicy::totalStackLimitBytesDefault),
+    llvm::cl::Hidden);
+
+bool fir::shouldAllocateOnStack(const PendingAllocationInfo &info,
+                                const AllocationPolicy &policy,
+                                std::size_t stackBytesUsed) {
+  // -fstack-arrays: put everything on the stack (best effort). For existing
+  // allocations, the heap-to-stack conversion still only happens where it is
+  // provably safe.
+  if (policy.stackArrays)
+    return true;
+
+  // Runtime-sized arrays (automatic arrays, dynamic temporaries) go on the
+  // heap.
+  if (info.isDynamic)
+    return false;
+
+  // Without a known constant size we cannot reason about thresholds: stay on
+  // the heap, which is always valid.
+  if (!info.byteSize)
+    return false;
+
+  // Constant-size user variables always go on the stack.
+  if (!info.isTemporary)
+    return true;
+
+  auto size = static_cast<std::size_t>(*info.byteSize);
+  // Small array temporaries go on the stack while the per-function budget
+  // allows it; bigger ones go on the heap.
+  return size <= policy.smallArrayThresholdBytes &&
+         stackBytesUsed + size <= policy.totalStackLimitBytes;
+}
+
+fir::AllocationPlacement
+fir::decideAllocationPlacement(const AllocationInfo &info,
+                               const AllocationPolicy &policy,
+                               std::size_t stackBytesUsed) {
+  using P = fir::AllocationPlacement;
+
+  // An allocation that is not known to be dynamic but whose size cannot be
+  // determined cannot be reasoned about: leave it where it is instead of
+  // moving it based on a size that is not available.
+  if (!policy.stackArrays && !info.isDynamic && !info.byteSize)
+    return P::Leave;
+
+  // Translate the "should this be on the stack" decision into a placement,
+  // accounting for where the allocation currently lives.
+  bool wantStack = fir::shouldAllocateOnStack(info, policy, stackBytesUsed);
+  if (wantStack)
+    return info.isCurrentlyOnStack ? P::Leave : P::Stack;
+  return info.isCurrentlyOnStack ? P::Heap : P::Leave;
+}
+
+fir::AllocationPolicy fir::getCommandLineAllocationPolicy(bool stackArrays) {
+  fir::AllocationPolicy policy;
+  policy.stackArrays = stackArrays;
+  policy.smallArrayThresholdBytes = allocationPlacementSmallArraySize;
+  policy.totalStackLimitBytes = allocationPlacementStackLimit;
+  return policy;
+}
+
+void fir::setAllocationPolicy(mlir::ModuleOp mod,
+                              const fir::AllocationPolicy &policy) {
+  mod->setAttr(allocationPolicyName, fir::AllocationPolicyAttr::get(
+                                         mod.getContext(), policy.stackArrays,
+                                         policy.smallArrayThresholdBytes,
+                                         policy.totalStackLimitBytes));
+}
+
+fir::AllocationPolicy fir::getAllocationPolicy(mlir::ModuleOp mod) {
+  auto attr =
+      mod->getAttrOfType<fir::AllocationPolicyAttr>(allocationPolicyName);
+  if (!attr)
+    return fir::AllocationPolicy{};
+  return fir::AllocationPolicy{attr.getStackArrays(),
+                               attr.getSmallArrayThreshold(),
+                               attr.getTotalStackLimit()};
+}
+
+fir::AllocationPolicy fir::getAllocationPolicy(mlir::Operation *op) {
+  auto mod = mlir::dyn_cast<mlir::ModuleOp>(op);
+  if (!mod)
+    mod = op->getParentOfType<mlir::ModuleOp>();
+  if (!mod)
+    return fir::AllocationPolicy{};
+  return getAllocationPolicy(mod);
+}

diff  --git a/flang/lib/Optimizer/Support/CMakeLists.txt b/flang/lib/Optimizer/Support/CMakeLists.txt
index 9f6069be6323d..bdd193906c51f 100644
--- a/flang/lib/Optimizer/Support/CMakeLists.txt
+++ b/flang/lib/Optimizer/Support/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_flang_library(FIRSupport
+  AllocationPolicy.cpp
   DataLayout.cpp
   InitFIR.cpp
   InternalNames.cpp

diff  --git a/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp b/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
index 1c4e4fadde39d..428b408c5fc5f 100644
--- a/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
+++ b/flang/lib/Optimizer/Transforms/AllocationPlacement.cpp
@@ -9,7 +9,7 @@
 // This pass decides, for each array allocation in a function, whether it should
 // live on the stack (fir.alloca) or on the heap (fir.allocmem), and rewrites it
 // accordingly. The decision is delegated to the policy in
-// AllocationPlacementPolicy.h. Two rewrite engines are reused:
+// flang/Optimizer/Support/AllocationPolicy.h. Two rewrite engines are reused:
 //   - stack-to-heap uses fir::replaceAllocas (MemoryUtils);
 //   - heap-to-stack reuses the StackArrays analysis and rewrite, which only
 //     stackifies fir.allocmem that are provably freed on all paths.
@@ -23,8 +23,8 @@
 #include "flang/Optimizer/Dialect/FIROpsSupport.h"
 #include "flang/Optimizer/Dialect/FIRType.h"
 #include "flang/Optimizer/Dialect/Support/FIRContext.h"
+#include "flang/Optimizer/Support/AllocationPolicy.h"
 #include "flang/Optimizer/Support/DataLayout.h"
-#include "flang/Optimizer/Transforms/AllocationPlacementPolicy.h"
 #include "flang/Optimizer/Transforms/MemoryUtils.h"
 #include "flang/Optimizer/Transforms/Passes.h"
 #include "mlir/Dialect/Arith/IR/Arith.h"
@@ -46,52 +46,6 @@ namespace fir {
 
 #define DEBUG_TYPE "allocation-placement"
 
-//===----------------------------------------------------------------------===//
-// Default placement policy
-//===----------------------------------------------------------------------===//
-
-fir::AllocationPlacement
-fir::decideAllocationPlacement(const AllocationInfo &info,
-                               const AllocationPlacementThresholds &thresholds,
-                               std::size_t stackBytesUsed) {
-  using P = fir::AllocationPlacement;
-
-  // Translate a "should this be on the stack" decision into a placement,
-  // accounting for where the allocation currently lives.
-  auto place = [&](bool wantStack) -> P {
-    if (wantStack)
-      return info.isCurrentlyOnStack ? P::Leave : P::Stack;
-    return info.isCurrentlyOnStack ? P::Heap : P::Leave;
-  };
-
-  // -fstack-arrays: put everything on the stack (best effort). The
-  // heap-to-stack conversion still only happens where it is provably safe.
-  if (thresholds.stackArrays)
-    return place(/*wantStack=*/true);
-
-  // Runtime-sized arrays (automatic arrays, dynamic temporaries) go on the
-  // heap.
-  if (info.isDynamic)
-    return place(/*wantStack=*/false);
-
-  // Without a known constant size we cannot reason about thresholds.
-  if (!info.byteSize)
-    return P::Leave;
-
-  // Constant-size user variables always go on the stack.
-  if (!info.isTemporary)
-    return place(/*wantStack=*/true);
-
-  auto size = static_cast<std::size_t>(*info.byteSize);
-  if (size <= thresholds.smallArrayThresholdBytes)
-    // Small arrays go on the stack while the per-function budget allows it.
-    return place(/*wantStack=*/stackBytesUsed + size <=
-                 thresholds.totalStackLimitBytes);
-
-  // Big array temporaries go on the heap.
-  return place(/*wantStack=*/false);
-}
-
 namespace {
 
 /// Return true if the allocation is a compiler temporary, i.e. it has no
@@ -189,10 +143,14 @@ void AllocationPlacementPass::runOnOperation() {
   if (func.empty())
     return;
 
-  fir::AllocationPlacementThresholds baseThresholds;
-  baseThresholds.stackArrays = stackArrays;
-  baseThresholds.smallArrayThresholdBytes = smallArrayThresholdBytes;
-  baseThresholds.totalStackLimitBytes = totalStackLimitBytes;
+  // Start from the policy recorded on the module. A pass option overrides it
+  // only where it was set explicitly.
+  fir::AllocationPolicy basePolicy = fir::getAllocationPolicy(func);
+  fir::overrideIfExplicitlySet(basePolicy.stackArrays, stackArrays);
+  fir::overrideIfExplicitlySet(basePolicy.smallArrayThresholdBytes,
+                               smallArrayThresholdBytes);
+  fir::overrideIfExplicitlySet(basePolicy.totalStackLimitBytes,
+                               totalStackLimitBytes);
 
   auto module = func->getParentOfType<mlir::ModuleOp>();
   std::optional<mlir::DataLayout> dl =
@@ -249,11 +207,11 @@ void AllocationPlacementPass::runOnOperation() {
     info.byteSize = getConstantByteSize(op, dl, kindMap);
 
     // A hook, if provided, fully overrides the default policy; it may delegate
-    // back to decideAllocationPlacement after adjusting the thresholds.
+    // back to decideAllocationPlacement after adjusting the policy.
     fir::AllocationPlacement placement =
-        placementHook ? placementHook(info, baseThresholds, stackBytesUsed)
-                      : fir::decideAllocationPlacement(info, baseThresholds,
-                                                       stackBytesUsed);
+        placementHook
+            ? 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)

diff  --git a/flang/test/Driver/allocation-policy.f90 b/flang/test/Driver/allocation-policy.f90
new file mode 100644
index 0000000000000..027795a348e12
--- /dev/null
+++ b/flang/test/Driver/allocation-policy.f90
@@ -0,0 +1,35 @@
+! Test that lowering records the array allocation policy on the module.
+! Policy-aware passes read it from there, and emitted FIR records the concrete
+! policy values with which it was compiled.
+
+! RUN: %flang_fc1 -emit-fir -o - %s | FileCheck %s --check-prefix=DEFAULT
+! RUN: %flang_fc1 -emit-fir -mllvm -allocation-placement-small-array-size=2048 \
+! RUN:   -mllvm -allocation-placement-stack-limit=8192 -o - %s \
+! RUN:   | FileCheck %s --check-prefix=TUNED
+! RUN: %flang_fc1 -emit-fir -fstack-arrays -o - %s \
+! RUN:   | FileCheck %s --check-prefix=STACK
+
+! The policy is recorded by the lowering bridge, so every tool that lowers
+! Fortran gets it, not just the frontend driver.
+! RUN: bbc -emit-fir %s -o - | FileCheck %s --check-prefix=DEFAULT
+! RUN: bbc -emit-fir -allocation-placement-small-array-size=2048 \
+! RUN:   -allocation-placement-stack-limit=8192 %s -o - \
+! RUN:   | FileCheck %s --check-prefix=TUNED
+
+! Default values are written explicitly so that a tool reading this FIR does
+! not silently pick up 
diff erent defaults.
+! DEFAULT: fir.allocation_policy = #fir.allocation_policy<stack_arrays = false,
+! DEFAULT-SAME: small_array_threshold = 1024,
+! DEFAULT-SAME: total_stack_limit = 4194304>
+
+! TUNED: fir.allocation_policy = #fir.allocation_policy<stack_arrays = false,
+! TUNED-SAME: small_array_threshold = 2048, total_stack_limit = 8192>
+
+! STACK: fir.allocation_policy = #fir.allocation_policy<stack_arrays = true,
+! STACK-SAME: small_array_threshold = 1024,
+! STACK-SAME: total_stack_limit = 4194304>
+
+subroutine s(a)
+  real :: a(10)
+  a = 1.0
+end subroutine s

diff  --git a/flang/test/Fir/allocation-policy-attr.fir b/flang/test/Fir/allocation-policy-attr.fir
new file mode 100644
index 0000000000000..dbe7c11ba1ce2
--- /dev/null
+++ b/flang/test/Fir/allocation-policy-attr.fir
@@ -0,0 +1,24 @@
+// Test the printing and parsing of the fir.allocation_policy module attribute.
+// All parameters are mandatory so that the concrete policy is preserved across
+// an IR round trip.
+
+// RUN: fir-opt %s | fir-opt | FileCheck %s
+
+// CHECK-LABEL: module @all_fields
+// CHECK-SAME:  fir.allocation_policy = #fir.allocation_policy<stack_arrays = true,
+// CHECK-SAME:  small_array_threshold = 64, total_stack_limit = 128>
+module @all_fields attributes {fir.allocation_policy =
+    #fir.allocation_policy<stack_arrays = true, small_array_threshold = 64,
+                           total_stack_limit = 128>} {
+}
+
+// Check a policy containing the values used by the construction helpers when
+// no options override them.
+// CHECK-LABEL: module @default_values
+// CHECK-SAME:  fir.allocation_policy = #fir.allocation_policy<stack_arrays = false,
+// CHECK-SAME:  small_array_threshold = 1024, total_stack_limit = 4194304>
+module @default_values attributes {fir.allocation_policy =
+    #fir.allocation_policy<stack_arrays = false,
+                           small_array_threshold = 1024,
+                           total_stack_limit = 4194304>} {
+}

diff  --git a/flang/test/Fir/allocation-policy-pipeline.fir b/flang/test/Fir/allocation-policy-pipeline.fir
new file mode 100644
index 0000000000000..585a671cf1ca0
--- /dev/null
+++ b/flang/test/Fir/allocation-policy-pipeline.fir
@@ -0,0 +1,50 @@
+// Test that the pass creating a copy-in buffer and the pass placing existing
+// allocations agree, because both read the policy recorded on the module. If
+// they disagreed, a buffer put on the stack by the first would be moved back to
+// the heap by the second (or the other way round), and the placement would
+// depend on the order the passes happen to run in.
+
+// A 64 byte threshold puts the 800 byte buffer on the heap, and it must stay
+// there.
+// RUN: fir-opt --inline-hlfir-copy --allocation-placement %s \
+// RUN:   | FileCheck %s --check-prefix=SMALL
+
+// A 4096 byte threshold puts it on the stack, and it must stay there.
+// RUN: fir-opt --inline-hlfir-copy="small-array-threshold=4096" \
+// RUN:   --allocation-placement="small-array-threshold=4096" %s \
+// RUN:   | FileCheck %s --check-prefix=BIG
+
+module attributes {fir.allocation_policy =
+                       #fir.allocation_policy<stack_arrays = false,
+                           small_array_threshold = 64,
+                           total_stack_limit = 4194304>,
+                   fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "",
+                   llvm.data_layout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"} {
+
+func.func private @callee100(!fir.ref<!fir.array<100xf64>>) -> ()
+
+// SMALL-LABEL:   func.func @copy_in_buffer
+// SMALL:           fir.allocmem !fir.array<100xf64> {bindc_name = ".tmp.copy_in"
+// SMALL:           fir.freemem
+// SMALL-NOT:       fir.alloca !fir.array<100xf64>
+
+// BIG-LABEL:     func.func @copy_in_buffer
+// BIG:             fir.alloca !fir.array<100xf64> {bindc_name = ".tmp.copy_in"
+// BIG-NOT:         fir.allocmem
+// BIG-NOT:         fir.freemem
+func.func @copy_in_buffer(%arg0: !fir.box<!fir.array<?x?xf64>>, %arg1: !fir.ref<i32>) {
+  %0 = fir.alloca !fir.box<!fir.heap<!fir.array<100xf64>>>
+  %c1 = arith.constant 1 : index
+  %c100 = arith.constant 100 : index
+  %1 = fir.load %arg1 : !fir.ref<i32>
+  %2 = fir.convert %1 : (i32) -> i64
+  %3 = fir.shape %c100 : (index) -> !fir.shape<1>
+  %4 = hlfir.designate %arg0 (%2, %c1:%c100:%c1)  shape %3 : (!fir.box<!fir.array<?x?xf64>>, i64, index, index, index, !fir.shape<1>) -> !fir.box<!fir.array<100xf64>>
+  %5:2 = hlfir.copy_in %4 to %0 : (!fir.box<!fir.array<100xf64>>, !fir.ref<!fir.box<!fir.heap<!fir.array<100xf64>>>>) -> (!fir.box<!fir.array<100xf64>>, i1)
+  %6 = fir.box_addr %5#0 : (!fir.box<!fir.array<100xf64>>) -> !fir.ref<!fir.array<100xf64>>
+  fir.call @callee100(%6) : (!fir.ref<!fir.array<100xf64>>) -> ()
+  hlfir.copy_out %0, %5#1 : (!fir.ref<!fir.box<!fir.heap<!fir.array<100xf64>>>>, i1) -> ()
+  return
+}
+
+}

diff  --git a/flang/test/HLFIR/inline-hlfir-copy-stack.fir b/flang/test/HLFIR/inline-hlfir-copy-stack.fir
new file mode 100644
index 0000000000000..16b5ced6d0ed1
--- /dev/null
+++ b/flang/test/HLFIR/inline-hlfir-copy-stack.fir
@@ -0,0 +1,130 @@
+// Test the stack/heap placement of the buffer created when inlining
+// hlfir.copy_in. Only buffers whose size is a compile-time constant not
+// exceeding the threshold are placed on the stack.
+
+// RUN: fir-opt --inline-hlfir-copy="small-array-threshold=64" %s \
+// RUN:   | FileCheck %s
+// RUN: fir-opt --inline-hlfir-copy="small-array-threshold=1024" %s \
+// RUN:   | FileCheck %s --check-prefix=BIGTHRESHOLD
+
+module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", llvm.data_layout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"} {
+
+func.func private @callee8(!fir.ref<!fir.array<8xf64>>) -> ()
+func.func private @callee100(!fir.ref<!fir.array<100xf64>>) -> ()
+func.func private @calleeDyn(!fir.ref<!fir.array<?xf64>>) -> ()
+
+// 8 * 8 = 64 bytes, at the threshold: the buffer goes on the stack.
+func.func @small_constant_buffer(%arg0: !fir.box<!fir.array<?x?xf64>>, %arg1: !fir.ref<i32>) {
+  %0 = fir.alloca !fir.box<!fir.heap<!fir.array<8xf64>>>
+  %c1 = arith.constant 1 : index
+  %c8 = arith.constant 8 : index
+  %1 = fir.load %arg1 : !fir.ref<i32>
+  %2 = fir.convert %1 : (i32) -> i64
+  %3 = fir.shape %c8 : (index) -> !fir.shape<1>
+  %4 = hlfir.designate %arg0 (%2, %c1:%c8:%c1)  shape %3 : (!fir.box<!fir.array<?x?xf64>>, i64, index, index, index, !fir.shape<1>) -> !fir.box<!fir.array<8xf64>>
+  %5:2 = hlfir.copy_in %4 to %0 : (!fir.box<!fir.array<8xf64>>, !fir.ref<!fir.box<!fir.heap<!fir.array<8xf64>>>>) -> (!fir.box<!fir.array<8xf64>>, i1)
+  %6 = fir.box_addr %5#0 : (!fir.box<!fir.array<8xf64>>) -> !fir.ref<!fir.array<8xf64>>
+  fir.call @callee8(%6) : (!fir.ref<!fir.array<8xf64>>) -> ()
+  hlfir.copy_out %0, %5#1 : (!fir.ref<!fir.box<!fir.heap<!fir.array<8xf64>>>>, i1) -> ()
+  return
+}
+
+// 100 * 8 = 800 bytes, above the 64 byte threshold: the buffer stays on the
+// heap and is freed by the inlined copy-out.
+func.func @big_constant_buffer(%arg0: !fir.box<!fir.array<?x?xf64>>, %arg1: !fir.ref<i32>) {
+  %0 = fir.alloca !fir.box<!fir.heap<!fir.array<100xf64>>>
+  %c1 = arith.constant 1 : index
+  %c100 = arith.constant 100 : index
+  %1 = fir.load %arg1 : !fir.ref<i32>
+  %2 = fir.convert %1 : (i32) -> i64
+  %3 = fir.shape %c100 : (index) -> !fir.shape<1>
+  %4 = hlfir.designate %arg0 (%2, %c1:%c100:%c1)  shape %3 : (!fir.box<!fir.array<?x?xf64>>, i64, index, index, index, !fir.shape<1>) -> !fir.box<!fir.array<100xf64>>
+  %5:2 = hlfir.copy_in %4 to %0 : (!fir.box<!fir.array<100xf64>>, !fir.ref<!fir.box<!fir.heap<!fir.array<100xf64>>>>) -> (!fir.box<!fir.array<100xf64>>, i1)
+  %6 = fir.box_addr %5#0 : (!fir.box<!fir.array<100xf64>>) -> !fir.ref<!fir.array<100xf64>>
+  fir.call @callee100(%6) : (!fir.ref<!fir.array<100xf64>>) -> ()
+  hlfir.copy_out %0, %5#1 : (!fir.ref<!fir.box<!fir.heap<!fir.array<100xf64>>>>, i1) -> ()
+  return
+}
+
+// Runtime extent: the buffer stays on the heap whatever the threshold is,
+// because a dynamically sized fir.alloca cannot be hoisted out of the copy-in
+// branch and would grow the stack if the copy-in were inside a loop.
+func.func @dynamic_buffer(%arg0: !fir.box<!fir.array<?x?xf64>>, %arg1: !fir.ref<i32>, %n: index) {
+  %0 = fir.alloca !fir.box<!fir.heap<!fir.array<?xf64>>>
+  %c1 = arith.constant 1 : index
+  %1 = fir.load %arg1 : !fir.ref<i32>
+  %2 = fir.convert %1 : (i32) -> i64
+  %3 = fir.shape %n : (index) -> !fir.shape<1>
+  %4 = hlfir.designate %arg0 (%2, %c1:%n:%c1)  shape %3 : (!fir.box<!fir.array<?x?xf64>>, i64, index, index, index, !fir.shape<1>) -> !fir.box<!fir.array<?xf64>>
+  %5:2 = hlfir.copy_in %4 to %0 : (!fir.box<!fir.array<?xf64>>, !fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>) -> (!fir.box<!fir.array<?xf64>>, i1)
+  %6 = fir.box_addr %5#0 : (!fir.box<!fir.array<?xf64>>) -> !fir.ref<!fir.array<?xf64>>
+  fir.call @calleeDyn(%6) : (!fir.ref<!fir.array<?xf64>>) -> ()
+  hlfir.copy_out %0, %5#1 : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf64>>>>, i1) -> ()
+  return
+}
+
+}
+
+// The buffer is created in the copy-in branch, but being a constant size
+// fir.alloca it is hoisted to the entry block, and no deallocation code is
+// generated for the copy-out.
+
+// CHECK-LABEL:   func.func @small_constant_buffer(
+// CHECK:           %[[BUF:.*]] = fir.alloca !fir.array<8xf64> {bindc_name = ".tmp.copy_in"}
+// CHECK:           %[[SECTION:.*]] = hlfir.designate %{{.*}} shape
+// CHECK:           %[[CONTIG:.*]] = fir.is_contiguous_box %[[SECTION]]
+// CHECK:           %[[RES:.*]]:2 = fir.if %[[CONTIG]] -> (!fir.box<!fir.array<8xf64>>, i1) {
+// CHECK:             fir.result %[[SECTION]], %false : !fir.box<!fir.array<8xf64>>, i1
+// CHECK:           } else {
+// CHECK:             %[[DECL:.*]]:2 = hlfir.declare %[[BUF]]
+// CHECK:             fir.do_loop
+// CHECK:             %[[EMBOX:.*]] = fir.embox %[[DECL]]#0
+// CHECK:             fir.result %[[EMBOX]], %true : !fir.box<!fir.array<8xf64>>, i1
+// CHECK:           }
+// CHECK:           %[[ADDR:.*]] = fir.box_addr %[[RES]]#0
+// CHECK:           fir.call @callee8(%[[ADDR]])
+// CHECK-NOT:       fir.allocmem
+// CHECK-NOT:       fir.freemem
+
+// Above the threshold: the buffer is allocated in the copy-in branch and freed
+// under the flag yielded by the fir.if. The free reads the address from the box
+// yielded by the fir.if, which needs no spill to memory.
+
+// CHECK-LABEL:   func.func @big_constant_buffer(
+// CHECK:           %[[RES:.*]]:2 = fir.if %{{.*}} -> (!fir.box<!fir.array<100xf64>>, i1) {
+// CHECK:             fir.result %{{.*}}, %false : !fir.box<!fir.array<100xf64>>, i1
+// CHECK:           } else {
+// CHECK:             fir.allocmem !fir.array<100xf64> {bindc_name = ".tmp.copy_in"
+// CHECK:             fir.result %{{.*}}, %true : !fir.box<!fir.array<100xf64>>, i1
+// CHECK:           }
+// CHECK:           fir.call @callee100
+// CHECK:           fir.if %[[RES]]#1 {
+// CHECK:             %[[FREE_ADDR:.*]] = fir.box_addr %[[RES]]#0
+// CHECK:             %[[FREE_HEAP:.*]] = fir.convert %[[FREE_ADDR]]
+// CHECK:             fir.freemem %[[FREE_HEAP]]
+
+// A runtime extent always stays on the heap.
+
+// CHECK-LABEL:   func.func @dynamic_buffer(
+// CHECK:           %[[RES:.*]]:2 = fir.if %{{.*}} -> (!fir.box<!fir.array<?xf64>>, i1) {
+// CHECK:           } else {
+// CHECK:             fir.allocmem !fir.array<?xf64>, %{{.*}} {bindc_name = ".tmp.copy_in"
+// CHECK:           }
+// CHECK:           fir.call @calleeDyn
+// CHECK:           fir.if %[[RES]]#1 {
+// CHECK:             fir.freemem
+
+// With a threshold that covers it, the 800 byte buffer is also placed on the
+// stack, while the runtime-sized one is not.
+
+// BIGTHRESHOLD-LABEL:   func.func @big_constant_buffer(
+// BIGTHRESHOLD:           %[[BUF:.*]] = fir.alloca !fir.array<100xf64> {bindc_name = ".tmp.copy_in"}
+// BIGTHRESHOLD:           fir.if %{{.*}} -> (!fir.box<!fir.array<100xf64>>, i1) {
+// BIGTHRESHOLD:             hlfir.declare %[[BUF]]
+// BIGTHRESHOLD:           fir.call @callee100
+// BIGTHRESHOLD-NOT:       fir.allocmem
+// BIGTHRESHOLD-NOT:       fir.freemem
+
+// BIGTHRESHOLD-LABEL:   func.func @dynamic_buffer(
+// BIGTHRESHOLD:           fir.allocmem !fir.array<?xf64>, %{{.*}} {bindc_name = ".tmp.copy_in"
+// BIGTHRESHOLD:           fir.freemem

diff  --git a/flang/test/HLFIR/inline-hlfir-copy.fir b/flang/test/HLFIR/inline-hlfir-copy.fir
index d8a96ca2c0b04..672fe34434c47 100644
--- a/flang/test/HLFIR/inline-hlfir-copy.fir
+++ b/flang/test/HLFIR/inline-hlfir-copy.fir
@@ -72,14 +72,11 @@ func.func private @_test_inline_copy_in(%arg0: !fir.box<!fir.array<?x?x?xf64>> {
 // CHECK:      }
 // CHECK:      fir.result %[[VAL_25:.*]]#0, %[[VAL_3:.*]] : !fir.box<!fir.array<?xf64>>, i1
 // CHECK:    }
-// CHECK:    %[[VAL_ALLOCA:.*]] = fir.alloca !fir.box<!fir.array<?xf64>>
-// CHECK:    fir.store %[[VAL_21:.*]]#0 to %[[VAL_ALLOCA:.*]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
 // CHECK:    %[[VAL_22:.*]] = fir.box_addr %[[VAL_21:.*]]#0 : (!fir.box<!fir.array<?xf64>>) -> !fir.ref<!fir.array<?xf64>>
 // CHECK:    %[[VAL_23:.*]]:3 = hlfir.associate %[[VAL_5:.*]] {adapt.valuebyref} : (i32) -> (!fir.ref<i32>, !fir.ref<i32>, i1)
 // CHECK:    fir.call @_QFPsb(%[[VAL_22:.*]], %[[VAL_23:.*]]#0) fastmath<contract> : (!fir.ref<!fir.array<?xf64>>, !fir.ref<i32>) -> ()
 // CHECK:    fir.if %[[VAL_21:.*]]#1 {
-// CHECK:      %[[VAL_BOX:.*]] = fir.load %[[VAL_ALLOCA:.*]] : !fir.ref<!fir.box<!fir.array<?xf64>>>
-// CHECK:      %[[VAL_ADDR:.*]] = fir.box_addr %[[VAL_BOX:.*]] : (!fir.box<!fir.array<?xf64>>) -> !fir.ref<!fir.array<?xf64>>
+// CHECK:      %[[VAL_ADDR:.*]] = fir.box_addr %[[VAL_21:.*]]#0 : (!fir.box<!fir.array<?xf64>>) -> !fir.ref<!fir.array<?xf64>>
 // CHECK:      %[[VAL_HEAP:.*]] = fir.convert %[[VAL_ADDR:.*]] : (!fir.ref<!fir.array<?xf64>>) -> !fir.heap<!fir.array<?xf64>>
 // CHECK:      fir.freemem %[[VAL_HEAP:.*]] : !fir.heap<!fir.array<?xf64>>
 // CHECK:    }

diff  --git a/flang/test/Transforms/allocation-placement.fir b/flang/test/Transforms/allocation-placement.fir
index 8b0a1acb74122..ec70af24e5d13 100644
--- a/flang/test/Transforms/allocation-placement.fir
+++ b/flang/test/Transforms/allocation-placement.fir
@@ -5,9 +5,9 @@
 //  - runtime-sized arrays go on the heap.
 // A user variable is identified by a non-empty uniq_name; a temporary has none.
 // i32 is 4 bytes, so <10xi32> = 40 bytes (small) and <100xi32> = 400 bytes (big)
-// with the default 64-byte small threshold.
+// with the 64-byte small threshold pinned below.
 
-// RUN: fir-opt --allocation-placement %s | FileCheck %s
+// RUN: fir-opt --allocation-placement="small-array-threshold=64" %s | FileCheck %s
 
 module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", 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"} {
 

diff  --git a/flang/test/Transforms/allocation-policy-precedence.fir b/flang/test/Transforms/allocation-policy-precedence.fir
new file mode 100644
index 0000000000000..7f3a9803b8758
--- /dev/null
+++ b/flang/test/Transforms/allocation-policy-precedence.fir
@@ -0,0 +1,62 @@
+// Test where allocation-placement takes its policy from. The policy recorded
+// on the module is the baseline; a pass option overrides it, but only when it
+// is set explicitly, so that pinning one field in a test does not silently
+// reset the others.
+//
+// i32 is 4 bytes, so <100xi32> is 400 bytes: on the stack under the default
+// 1024 byte threshold, on the heap under a 64 byte one.
+
+// RUN: fir-opt --allocation-placement -split-input-file %s | FileCheck %s
+// RUN: fir-opt --allocation-placement="small-array-threshold=4096" \
+// RUN:   -split-input-file %s | FileCheck %s --check-prefix=OPTION
+
+// The policy recorded on the module is used when no option is given.
+
+// CHECK-LABEL:   func.func @with_policy_attribute
+// CHECK:           fir.allocmem !fir.array<100xi32>
+// OPTION-LABEL:  func.func @with_policy_attribute
+// OPTION:          fir.alloca !fir.array<100xi32>
+// OPTION-NOT:      fir.allocmem
+module attributes {fir.allocation_policy =
+                       #fir.allocation_policy<stack_arrays = false,
+                           small_array_threshold = 64,
+                           total_stack_limit = 4194304>,
+                   fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "",
+                   llvm.data_layout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"} {
+func.func @with_policy_attribute() {
+  %0 = fir.allocmem !fir.array<100xi32>
+  %c0 = arith.constant 0 : index
+  %v = arith.constant 0 : i32
+  %r = fir.convert %0 : (!fir.heap<!fir.array<100xi32>>) -> !fir.ref<!fir.array<100xi32>>
+  %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<100xi32>>, index) -> !fir.ref<i32>
+  fir.store %v to %e : !fir.ref<i32>
+  fir.freemem %0 : !fir.heap<!fir.array<100xi32>>
+  return
+}
+}
+
+// -----
+
+// Without a policy on the module the defaults apply, which put a 400 byte
+// temporary on the stack. Both runs agree here, which is the point: the
+// default in the option and the default in the policy are the same value.
+
+// CHECK-LABEL:   func.func @without_policy_attribute
+// CHECK:           fir.alloca !fir.array<100xi32>
+// CHECK-NOT:       fir.allocmem
+// OPTION-LABEL:  func.func @without_policy_attribute
+// OPTION:          fir.alloca !fir.array<100xi32>
+// OPTION-NOT:      fir.allocmem
+module attributes {fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "",
+                   llvm.data_layout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"} {
+func.func @without_policy_attribute() {
+  %0 = fir.allocmem !fir.array<100xi32>
+  %c0 = arith.constant 0 : index
+  %v = arith.constant 0 : i32
+  %r = fir.convert %0 : (!fir.heap<!fir.array<100xi32>>) -> !fir.ref<!fir.array<100xi32>>
+  %e = fir.coordinate_of %r, %c0 : (!fir.ref<!fir.array<100xi32>>, index) -> !fir.ref<i32>
+  fir.store %v to %e : !fir.ref<i32>
+  fir.freemem %0 : !fir.heap<!fir.array<100xi32>>
+  return
+}
+}


        


More information about the flang-commits mailing list