[flang-commits] [flang] a70234b - [flang] Propagate INTENT(IN) dummy arguments as readonly (#207732)
via flang-commits
flang-commits at lists.llvm.org
Mon Jul 13 21:50:20 PDT 2026
Author: Sergey Shcherbinin
Date: 2026-07-14T10:20:15+05:30
New Revision: a70234b00d12aaa7723eb2ae5e8efe3348aeeb37
URL: https://github.com/llvm/llvm-project/commit/a70234b00d12aaa7723eb2ae5e8efe3348aeeb37
DIFF: https://github.com/llvm/llvm-project/commit/a70234b00d12aaa7723eb2ae5e8efe3348aeeb37.diff
LOG: [flang] Propagate INTENT(IN) dummy arguments as readonly (#207732)
Mark eligible `INTENT(IN)` dummy data objects with a `fir.read_only`
argument attribute during lowering, and teach `FunctionAttr` to
translate the marker to LLVM `readonly` for reference arguments when
optimizing for speed.
LLVM FunctionAttrs can infer `readonly` later in the pipeline, but this
happens too late for IPSCCP function specialization.
`FunctionSpecializer::promoteConstantStackValues` requires
`onlyReadsMemory()` before promoting constant by-reference scalar
arguments. Providing `readonly` satisfies this check and allows these
arguments to be promoted, enabling function specialization, including in
non-LTO builds.
For direct-data arguments, restrict propagation to by-reference
non-character intrinsic scalars. Ordinary arrays and derived-type
arguments are excluded because compiler-generated copy-out may write
back through a forwarded dummy argument. LLVM `readonly` does not permit
such writes, even when the stored value is unchanged.
`INTENT(IN)` `POINTER` and `ALLOCATABLE` dummies are supported with
shallow descriptor semantics: `readonly` protects the descriptor
storage, but does not imply that data addressed by the descriptor is
read-only. In particular, a `POINTER` target may still be modified.
Keep `readonly` handling independent from `noalias` and `nocapture`.
`TARGET` arguments may receive `readonly`, but must not receive
`noalias` or `nocapture`. Apply `readonly` to `BIND(C)` definitions
while leaving external C declarations unannotated. `VALUE`,
`ASYNCHRONOUS`, and `VOLATILE` dummy arguments remain excluded.
In our measurements, the enabled function specialization gives a 1.5x
speedup on 548.exchange2 on NVIDIA Neoverse V2 in a non-LTO
configuration.
Update existing lowering tests to account for the new `fir.read_only`
argument attribute, and add focused coverage:
* `dummy-argument-readonly.f90` checks marker emission for supported
scalar and descriptor arguments, excluded cases, and shallow pointer
semantics.
* `function-attrs-readonly.fir` checks translation from `fir.read_only`
to LLVM `readonly`, including `TARGET`, `BIND(C)`, and descriptor
references.
* `function-attr-readonly.f90` checks the optimization-level pipeline
gate and verifies that `readonly` reaches final LLVM IR.
* `call-copy-in-out.f90` checks that ordinary array copy-out cases
remain unmarked and that pointer-target copy-out is compatible with
shallow descriptor `readonly`.
Added:
flang/test/Driver/function-attr-readonly.f90
flang/test/Lower/dummy-argument-readonly.f90
flang/test/Transforms/function-attrs-readonly.fir
Modified:
flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
flang/include/flang/Optimizer/Transforms/Passes.td
flang/lib/Lower/CallInterface.cpp
flang/lib/Optimizer/Passes/Pipelines.cpp
flang/lib/Optimizer/Transforms/FunctionAttr.cpp
flang/test/Lower/HLFIR/structure-constructor.f90
flang/test/Lower/Intrinsics/dconjg.f90
flang/test/Lower/Intrinsics/dimag.f90
flang/test/Lower/Intrinsics/dreal.f90
flang/test/Lower/OpenMP/optional-argument-map.f90
flang/test/Lower/OpenMP/wsloop-unstructured.f90
flang/test/Lower/achar.f90
flang/test/Lower/arguments.f90
flang/test/Lower/call-copy-in-out.f90
flang/test/Lower/dispatch.f90
flang/test/Lower/polymorphic-temp.f90
flang/test/Lower/polymorphic.f90
flang/test/Lower/select-type.f90
flang/test/Lower/unsigned-ops.f90
flang/test/Lower/volatile1.f90
Removed:
################################################################################
diff --git a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
index 9e168ffe90d0d..944b9926387d8 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
+++ b/flang/include/flang/Optimizer/Dialect/FIROpsSupport.h
@@ -98,6 +98,16 @@ static constexpr llvm::StringRef getVolatileAttrName() {
return "fir.volatile";
}
+/// Attribute to mark a dummy data object argument as read-only, i.e. the callee
+/// does not write through this argument. Lowered to the LLVM `readonly`
+/// argument attribute by the FunctionAttr pass. CallInterface currently emits
+/// it for a conservative subset of by-reference INTENT(IN) dummies. The
+/// attribute is shallow: on a reference to a descriptor, it only protects the
+/// descriptor storage and does not imply that the described data is read-only.
+static constexpr llvm::StringRef getReadOnlyAttrName() {
+ return "fir.read_only";
+}
+
/// Attribute to mark that a function argument is a character dummy procedure.
/// Character dummy procedure have special ABI constraints.
static constexpr llvm::StringRef getCharacterProcedureDummyAttrName() {
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 482260f26751d..ae7977d0670c7 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -477,6 +477,9 @@ def FunctionAttr : Pass<"function-attr", "mlir::func::FuncOp"> {
Option<"setNoAlias", "set-noalias", "bool", /*default=*/"false",
"Set LLVM noalias attribute on function arguments, "
"if possible">,
+ Option<"setReadOnly", "set-readonly", "bool", /*default=*/"false",
+ "Set LLVM readonly attribute on INTENT(IN) by-reference "
+ "function arguments, if possible">,
];
}
diff --git a/flang/lib/Lower/CallInterface.cpp b/flang/lib/Lower/CallInterface.cpp
index e28a16305cb98..64960b5e09a98 100644
--- a/flang/lib/Lower/CallInterface.cpp
+++ b/flang/lib/Lower/CallInterface.cpp
@@ -221,6 +221,36 @@ asImplicitArg(Fortran::evaluate::characteristics::DummyDataObject &&dummy) {
std::move(shape)));
}
+/// Return true when this dummy is in the subset for which CallInterface may
+/// safely emit the FIR marker that is later translated to LLVM `readonly`.
+///
+/// For non-descriptor arguments, limit this to non-character intrinsic
+/// scalars. Array and derived-type arguments are excluded because
+/// compiler-generated copy-out may write back through a forwarded dummy
+/// argument without an INTENT contract. INTENT(IN) POINTER and ALLOCATABLE
+/// dummies are also supported: in their case, `readonly` only protects the
+/// descriptor storage, not data addressed by the descriptor. ASYNCHRONOUS
+/// memory may change underneath the callee; VOLATILE+INTENT(IN) is prohibited
+/// by the standard (C870) but is excluded defensively. TARGET does not
+/// invalidate `readonly`, which only constrains writes through the argument.
+static bool dummyArgCanUseLLVMReadonly(
+ const Fortran::evaluate::characteristics::DummyDataObject &obj) {
+ using Attrs = Fortran::evaluate::characteristics::DummyDataObject::Attr;
+ using TypeAttrs = Fortran::evaluate::characteristics::TypeAndShape::Attr;
+ const Fortran::common::TypeCategory category = obj.type.type().category();
+ const bool isSupportedIntrinsicScalar =
+ obj.type.Rank() == 0 && !obj.type.attrs().test(TypeAttrs::AssumedRank) &&
+ category != Fortran::common::TypeCategory::Character &&
+ category != Fortran::common::TypeCategory::Derived;
+ const bool isDescriptorDummy =
+ obj.attrs.test(Attrs::Pointer) || obj.attrs.test(Attrs::Allocatable);
+ return obj.intent == Fortran::common::Intent::In &&
+ (isSupportedIntrinsicScalar || isDescriptorDummy) &&
+ !obj.attrs.test(Attrs::Value) &&
+ !obj.attrs.test(Attrs::Asynchronous) &&
+ !obj.attrs.test(Attrs::Volatile);
+}
+
static Fortran::evaluate::characteristics::DummyArgument
asImplicitArg(Fortran::evaluate::characteristics::DummyArgument &&dummy) {
return Fortran::common::visit(
@@ -1122,8 +1152,15 @@ class Fortran::lower::CallInterfaceImpl {
} else {
// non-PDT derived type allowed in implicit interface.
mlir::Type refType = getRefType(dynamicType, obj);
+ llvm::SmallVector<mlir::NamedAttribute> attrs = dummyNameAttr(entity);
+ // Propagate the INTENT(IN) contract for by-reference dummies handled by
+ // the implicit-interface path.
+ if (dummyArgCanUseLLVMReadonly(obj))
+ attrs.emplace_back(
+ mlir::StringAttr::get(&mlirContext, fir::getReadOnlyAttrName()),
+ mlir::UnitAttr::get(&mlirContext));
addFirOperand(refType, nextPassedArgPosition(), Property::BaseAddress,
- dummyNameAttr(entity));
+ attrs);
addPassedArg(PassEntityBy::BaseAddress, entity, characteristics);
}
}
@@ -1182,6 +1219,11 @@ class Fortran::lower::CallInterfaceImpl {
mlir::StringAttr::get(&mlirContext, cuf::getDataAttrName()),
cuf::getDataAttribute(&mlirContext, obj.cudaDataAttr));
+ // Mark the supported subset of INTENT(IN) by-reference dummies readonly
+ // (see dummyArgCanUseLLVMReadonly).
+ if (dummyArgCanUseLLVMReadonly(obj))
+ addMLIRAttr(fir::getReadOnlyAttrName());
+
// TODO: intents that require special care (e.g finalization)
if (obj.type.corank() > 0)
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index 70ef9e9bd17bb..16c2cda5d6d93 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -447,13 +447,14 @@ void createDefaultFIRCodeGenPassPipeline(mlir::PassManager &pm,
// function specialization is fixed.
bool setNoAlias = forceNoAlias;
bool setNoCapture = config.OptLevel.isOptimizingForSpeed();
+ bool setReadOnly = config.OptLevel.isOptimizingForSpeed();
pm.addPass(fir::createFunctionAttr(
{framePointerKind, config.InstrumentFunctionEntry,
config.InstrumentFunctionExit, config.NoInfsFPMath, config.NoNaNsFPMath,
config.ApproxFuncFPMath, config.NoSignedZerosFPMath, config.UnsafeFPMath,
config.Reciprocals, config.PreferVectorWidth, config.UseSampleProfile,
- /*tuneCPU=*/"", setNoCapture, setNoAlias}));
+ /*tuneCPU=*/"", setNoCapture, setNoAlias, setReadOnly}));
if (config.EnableOpenMP) {
pm.addNestedPass<mlir::func::FuncOp>(
diff --git a/flang/lib/Optimizer/Transforms/FunctionAttr.cpp b/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
index b49803e989265..45b32d13ad62e 100644
--- a/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
+++ b/flang/lib/Optimizer/Transforms/FunctionAttr.cpp
@@ -54,31 +54,46 @@ void FunctionAttrPass::runOnOperation() {
auto deconstructed = fir::NameUniquer::deconstruct(name);
bool isFromModule = !deconstructed.second.modules.empty();
- if ((isFromModule || !func.isDeclaration()) &&
- !fir::hasBindcAttr(func.getOperation())) {
+ if (isFromModule || !func.isDeclaration()) {
+ const bool isBindC = fir::hasBindcAttr(func.getOperation());
+ const bool isDefinition = !func.isDeclaration();
llvm::StringRef nocapture = mlir::LLVM::LLVMDialect::getNoCaptureAttrName();
llvm::StringRef noalias = mlir::LLVM::LLVMDialect::getNoAliasAttrName();
+ llvm::StringRef readonly = mlir::LLVM::LLVMDialect::getReadonlyAttrName();
mlir::UnitAttr unitAttr = mlir::UnitAttr::get(func.getContext());
for (auto [index, argType] : llvm::enumerate(func.getArgumentTypes())) {
bool isNoCapture = false;
bool isNoAlias = false;
- if (mlir::isa<fir::ReferenceType>(argType) &&
- !func.getArgAttr(index, fir::getTargetAttrName()) &&
- !func.getArgAttr(index, fir::getAsynchronousAttrName()) &&
- !func.getArgAttr(index, fir::getVolatileAttrName())) {
- isNoCapture = true;
- isNoAlias = !fir::isPointerType(argType);
+ bool isReadOnly = false;
+ if (mlir::isa<fir::ReferenceType>(argType)) {
+ // The read-only marker is attached to by-reference dummies the callee
+ // is guaranteed not to write through (see CallInterface). Unlike
+ // noalias/nocapture, it is valid even for TARGET arguments, so it is
+ // computed independently of the target/asynchronous/volatile gating.
+ isReadOnly = static_cast<bool>(
+ func.getArgAttr(index, fir::getReadOnlyAttrName()));
+ if (!func.getArgAttr(index, fir::getTargetAttrName()) &&
+ !func.getArgAttr(index, fir::getAsynchronousAttrName()) &&
+ !func.getArgAttr(index, fir::getVolatileAttrName())) {
+ isNoCapture = true;
+ isNoAlias = !fir::isPointerType(argType);
+ }
} else if (mlir::isa<fir::BaseBoxType>(argType)) {
// !fir.box arguments will be passed as descriptor pointers
// at LLVM IR dialect level - they cannot be captured,
// and cannot alias with anything within the function.
isNoCapture = isNoAlias = true;
}
- if (isNoCapture && setNoCapture)
+ // nocapture/noalias are not applied to BIND(C) interfaces (C ABI).
+ if (isNoCapture && setNoCapture && !isBindC)
func.setArgAttr(index, nocapture, unitAttr);
- if (isNoAlias && setNoAlias)
+ if (isNoAlias && setNoAlias && !isBindC)
func.setArgAttr(index, noalias, unitAttr);
+ // readonly is also valid for BIND(C) definitions (the Fortran body must
+ // honor INTENT(IN)), but not for declarations of external C functions.
+ if (isReadOnly && setReadOnly && (!isBindC || isDefinition))
+ func.setArgAttr(index, readonly, unitAttr);
}
}
diff --git a/flang/test/Driver/function-attr-readonly.f90 b/flang/test/Driver/function-attr-readonly.f90
new file mode 100644
index 0000000000000..0f7b1b0589a3c
--- /dev/null
+++ b/flang/test/Driver/function-attr-readonly.f90
@@ -0,0 +1,48 @@
+! Verify that the default pipeline translates the lowering marker only at
+! optimization levels that optimize for speed, and that llvm.readonly reaches
+! the final LLVM IR.
+! force-no-alias currently defaults to true, so llvm.noalias is expected at
+! both levels; llvm.nocapture and llvm.readonly are the attributes gated here.
+!
+! RUN: %flang_fc1 -emit-llvm -O0 \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=function-attr \
+! RUN: -mmlir --mlir-print-ir-module-scope %s -o /dev/null 2>&1 \
+! RUN: | FileCheck %s --check-prefix=O0
+! RUN: %flang_fc1 -emit-llvm -O1 \
+! RUN: -mmlir --mlir-disable-threading \
+! RUN: -mmlir --mlir-print-ir-after=function-attr \
+! RUN: -mmlir --mlir-print-ir-module-scope %s -o /dev/null 2>&1 \
+! RUN: | FileCheck %s --check-prefix=O1
+! RUN: %flang_fc1 -emit-llvm -O1 %s -o - | FileCheck %s --check-prefix=LLVM
+
+module readonly_pipeline_mod
+contains
+ subroutine readonly_pipeline(x, y)
+ integer, intent(in) :: x
+ integer, intent(inout) :: y
+ y = x
+ end subroutine
+
+ subroutine pointer_descriptor_readonly(p)
+ integer, intent(in), pointer :: p
+ p = 42
+ end subroutine
+end module
+
+! O0-LABEL: func.func @_QMreadonly_pipeline_modPreadonly_pipeline(
+! O0-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.read_only, llvm.noalias},
+! O0-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "y", llvm.noalias}) {
+
+! O1-LABEL: func.func @_QMreadonly_pipeline_modPreadonly_pipeline(
+! O1-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.read_only, llvm.noalias, llvm.nocapture, llvm.readonly},
+! O1-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "y", llvm.noalias, llvm.nocapture}) {
+
+! LLVM-LABEL: define void @_QMreadonly_pipeline_modPreadonly_pipeline(
+! LLVM-SAME: ptr {{.*}}readonly{{.*}} %0,
+
+! O1-LABEL: func.func @_QMreadonly_pipeline_modPpointer_descriptor_readonly(%{{.*}}: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.bindc_name = "p", fir.read_only, llvm.nocapture, llvm.readonly}) {
+
+! LLVM-LABEL: define void @_QMreadonly_pipeline_modPpointer_descriptor_readonly(
+! LLVM-SAME: ptr {{.*}}readonly{{.*}} %0
+! LLVM: store i32 42
diff --git a/flang/test/Lower/HLFIR/structure-constructor.f90 b/flang/test/Lower/HLFIR/structure-constructor.f90
index f2d2c094198b3..f937dc0b23d0e 100644
--- a/flang/test/Lower/HLFIR/structure-constructor.f90
+++ b/flang/test/Lower/HLFIR/structure-constructor.f90
@@ -314,7 +314,7 @@ subroutine test7(n)
x = t7(n)
end subroutine test7
! CHECK-LABEL: func.func @_QPtest7(
-! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "n"}) {
+! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "n", fir.read_only}) {
! CHECK: %[[VAL_1:.*]] = fir.alloca !fir.type<_QMtypesTt7{c1:i32,c2:!fir.box<!fir.heap<!fir.array<?xf32>>>}>
! CHECK: %[[VAL_2:.*]]:2 = hlfir.declare %[[VAL_0]] dummy_scope %{{[0-9]+}} arg {{[0-9]+}} {fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QFtest7En"} : (!fir.ref<i32>, !fir.dscope) -> (!fir.ref<i32>, !fir.ref<i32>)
! CHECK: %[[VAL_3:.*]] = fir.alloca !fir.type<_QMtypesTt7{c1:i32,c2:!fir.box<!fir.heap<!fir.array<?xf32>>>}> {bindc_name = "x", uniq_name = "_QFtest7Ex"}
diff --git a/flang/test/Lower/Intrinsics/dconjg.f90 b/flang/test/Lower/Intrinsics/dconjg.f90
index 0a219565f5bb5..b223259a39feb 100644
--- a/flang/test/Lower/Intrinsics/dconjg.f90
+++ b/flang/test/Lower/Intrinsics/dconjg.f90
@@ -6,7 +6,7 @@ subroutine test_dconjg(r, c)
! CHECK-LABEL: func @_QPtest_dconjg(
! CHECK-SAME: %[[ARG_0:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "r"},
-! CHECK-SAME: %[[ARG_1:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "c"}) {
+! CHECK-SAME: %[[ARG_1:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "c", fir.read_only}) {
! CHECK: %[[DS:.*]] = fir.dummy_scope : !fir.dscope
! CHECK: %[[C_DECL:.*]]:2 = hlfir.declare %[[ARG_1]] dummy_scope %[[DS]] {{.*}}
! CHECK: %[[R_DECL:.*]]:2 = hlfir.declare %[[ARG_0]] dummy_scope %[[DS]] {{.*}}
diff --git a/flang/test/Lower/Intrinsics/dimag.f90 b/flang/test/Lower/Intrinsics/dimag.f90
index a90fb31e5864b..7826d441b2e9c 100644
--- a/flang/test/Lower/Intrinsics/dimag.f90
+++ b/flang/test/Lower/Intrinsics/dimag.f90
@@ -6,7 +6,7 @@ subroutine test_dimag(r, c)
! CHECK-LABEL: func @_QPtest_dimag(
! CHECK-SAME: %[[ARG_0:.*]]: !fir.ref<f64> {fir.bindc_name = "r"},
-! CHECK-SAME: %[[ARG_1:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "c"}) {
+! CHECK-SAME: %[[ARG_1:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "c", fir.read_only}) {
! CHECK: %[[DS:.*]] = fir.dummy_scope : !fir.dscope
! CHECK: %[[C_DECL:.*]]:2 = hlfir.declare %[[ARG_1]] dummy_scope %[[DS]] {{.*}}
! CHECK: %[[R_DECL:.*]]:2 = hlfir.declare %[[ARG_0]] dummy_scope %[[DS]] {{.*}}
diff --git a/flang/test/Lower/Intrinsics/dreal.f90 b/flang/test/Lower/Intrinsics/dreal.f90
index 13d8cabba9e64..3f72aa7d3d630 100644
--- a/flang/test/Lower/Intrinsics/dreal.f90
+++ b/flang/test/Lower/Intrinsics/dreal.f90
@@ -6,7 +6,7 @@ subroutine test_dreal(r, c)
! CHECK-LABEL: func.func @_QPtest_dreal(
! CHECK-SAME: %[[ARG_0:.*]]: !fir.ref<f64> {fir.bindc_name = "r"},
-! CHECK-SAME: %[[ARG_1:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "c"}) {
+! CHECK-SAME: %[[ARG_1:.*]]: !fir.ref<complex<f64>> {fir.bindc_name = "c", fir.read_only}) {
! CHECK: %[[DS:.*]] = fir.dummy_scope : !fir.dscope
! CHECK: %[[C:.*]]:2 = hlfir.declare %[[ARG_1]] dummy_scope %[[DS]]
! CHECK: %[[R:.*]]:2 = hlfir.declare %[[ARG_0]] dummy_scope %[[DS]]
diff --git a/flang/test/Lower/OpenMP/optional-argument-map.f90 b/flang/test/Lower/OpenMP/optional-argument-map.f90
index 863742ce67927..84c2cc76495ae 100644
--- a/flang/test/Lower/OpenMP/optional-argument-map.f90
+++ b/flang/test/Lower/OpenMP/optional-argument-map.f90
@@ -15,7 +15,7 @@ end subroutine test
end module foo
! CHECK-LABEL: func.func @_QMfooPtest(
-! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "i"},
+! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only},
! CHECK-SAME: %[[VAL_1:.*]]: !fir.box<!fir.array<?xf32>> {fir.bindc_name = "a", fir.optional}) {
! CHECK: %[[VAL_2:.*]] = fir.alloca !fir.box<!fir.array<?xf32>>
! CHECK: %[[VAL_3:.*]]:2 = hlfir.declare %[[VAL_1]] dummy_scope %{{.*}} {fortran_attrs = #fir.var_attrs<intent_inout, optional>, uniq_name = "_QMfooFtestEa"} : (!fir.box<!fir.array<?xf32>>, !fir.dscope) -> (!fir.box<!fir.array<?xf32>>, !fir.box<!fir.array<?xf32>>)
diff --git a/flang/test/Lower/OpenMP/wsloop-unstructured.f90 b/flang/test/Lower/OpenMP/wsloop-unstructured.f90
index 6174718c08758..fb79bd1c17d77 100644
--- a/flang/test/Lower/OpenMP/wsloop-unstructured.f90
+++ b/flang/test/Lower/OpenMP/wsloop-unstructured.f90
@@ -24,8 +24,8 @@ end subroutine sub
! that all blocks are terminated
! CHECK-LABEL: func.func @_QPsub(
-! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "imax"},
-! CHECK-SAME: %[[VAL_1:.*]]: !fir.ref<i32> {fir.bindc_name = "jmax"},
+! CHECK-SAME: %[[VAL_0:.*]]: !fir.ref<i32> {fir.bindc_name = "imax", fir.read_only},
+! CHECK-SAME: %[[VAL_1:.*]]: !fir.ref<i32> {fir.bindc_name = "jmax", fir.read_only},
! CHECK-SAME: %[[VAL_2:.*]]: !fir.ref<!fir.array<?x?xf32>> {fir.bindc_name = "x"},
! CHECK-SAME: %[[VAL_3:.*]]: !fir.ref<!fir.array<?x?xf32>> {fir.bindc_name = "y"}) {
! [...]
diff --git a/flang/test/Lower/achar.f90 b/flang/test/Lower/achar.f90
index 63bea0076c597..ae35db348a33d 100644
--- a/flang/test/Lower/achar.f90
+++ b/flang/test/Lower/achar.f90
@@ -12,7 +12,7 @@ subroutine achar_test1(a)
end subroutine achar_test1
! CHECK-LABEL: func.func @_QPachar_test1(
-! CHECK-SAME: %[[ARG:.*]]: !fir.ref<i32> {fir.bindc_name = "a"}) {
+! CHECK-SAME: %[[ARG:.*]]: !fir.ref<i32> {fir.bindc_name = "a", fir.read_only}) {
! CHECK: %[[TMP:.*]] = fir.alloca !fir.char<1>
! CHECK: %[[DSCOPE:.*]] = fir.dummy_scope : !fir.dscope
! CHECK: %[[A:.*]]:2 = hlfir.declare %[[ARG]] dummy_scope %[[DSCOPE]] arg 1 {fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QFachar_test1Ea"}
diff --git a/flang/test/Lower/arguments.f90 b/flang/test/Lower/arguments.f90
index f54fa6868d554..9667494f0f011 100644
--- a/flang/test/Lower/arguments.f90
+++ b/flang/test/Lower/arguments.f90
@@ -7,7 +7,7 @@ subroutine sub1(a, b)
! Check that arguments are correctly set and no local allocation is happening.
! CHECK-LABEL: func @_QPsub1(
-! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "a"}, %{{.*}}: !fir.ref<!fir.logical<4>> {fir.bindc_name = "b"})
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "a", fir.read_only}, %{{.*}}: !fir.ref<!fir.logical<4>> {fir.bindc_name = "b"})
! CHECK-NOT: fir.alloc
! CHECK: return
@@ -31,7 +31,7 @@ integer function fct1(a, b)
end
! CHECK-LABEL: func @_QPfct1(
-! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "a"}, %{{.*}}: !fir.ref<!fir.logical<4>> {fir.bindc_name = "b"}) -> i32
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "a", fir.read_only}, %{{.*}}: !fir.ref<!fir.logical<4>> {fir.bindc_name = "b"}) -> i32
real function fct2(i)
integer :: i(2, 5)
diff --git a/flang/test/Lower/call-copy-in-out.f90 b/flang/test/Lower/call-copy-in-out.f90
index 15feb390171bf..c869923e75e96 100644
--- a/flang/test/Lower/call-copy-in-out.f90
+++ b/flang/test/Lower/call-copy-in-out.f90
@@ -98,6 +98,29 @@ subroutine test_actual_arg_intent_in(x)
call bar(x)
end subroutine
+! Test the transitive copy-out case. The outer INTENT(IN) array is forwarded
+! to a dummy without INTENT, which then passes a noncontiguous section to an
+! implicit-interface procedure. Lowering cannot recognize the outer contract
+! at the inner call site, so the inner procedure still generates copy-back.
+! The outer array must therefore not be marked fir.read_only.
+! CHECK-LABEL: func.func @_QPtest_forwarded_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.array<4xf32>> {fir.bindc_name = "x"}) {
+subroutine test_forwarded_intent_in(x)
+ real, intent(in) :: x(4)
+ call test_forwarded_without_intent(x)
+end subroutine
+
+! CHECK-LABEL: func.func @_QPtest_forwarded_without_intent(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.array<4xf32>> {fir.bindc_name = "x"}) {
+subroutine test_forwarded_without_intent(x)
+ real :: x(4)
+! CHECK: hlfir.copy_in
+! CHECK: fir.call @_QPbar
+! CHECK: hlfir.copy_out
+! CHECK-SAME: to
+ call bar(x(1:4:2))
+end subroutine
+
! Test copy-out is NOT skipped when passing a section of a pointer component
! of an INTENT(IN) dummy: the pointer target is not a subobject of the dummy
! (F2023 9.4.2 p5), so the callee may define it and copy-out is required.
@@ -118,7 +141,8 @@ subroutine test_actual_arg_intent_in_ptr_component(x)
! pointer dummy: INTENT(IN) on a pointer restricts pointer association, not
! the target's contents, so the callee may define the target and copy-out is
! required.
-! CHECK-LABEL: func @_QPtest_actual_intent_in_pointer(
+! CHECK-LABEL: func.func @_QPtest_actual_intent_in_pointer(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>> {fir.bindc_name = "pi", fir.read_only}) {
subroutine test_actual_intent_in_pointer(pi)
integer, intent(in), pointer :: pi(:)
! CHECK: hlfir.copy_in
diff --git a/flang/test/Lower/dispatch.f90 b/flang/test/Lower/dispatch.f90
index 7692bd31d4533..a66287d2b4fd6 100644
--- a/flang/test/Lower/dispatch.f90
+++ b/flang/test/Lower/dispatch.f90
@@ -107,7 +107,7 @@ subroutine p1_proc4_arg1(i, this)
integer, intent(in) :: i
class(p1) :: this
end subroutine
- ! CHECK-LABEL: func.func @_QMcall_dispatchPp1_proc4_arg1(%{{.*}}: !fir.ref<i32> {fir.bindc_name = "i"}, %{{.*}}: !fir.class<!fir.type<_QMcall_dispatchTp1{a:i32,b:i32}>> {fir.bindc_name = "this"})
+ ! CHECK-LABEL: func.func @_QMcall_dispatchPp1_proc4_arg1(%{{.*}}: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}, %{{.*}}: !fir.class<!fir.type<_QMcall_dispatchTp1{a:i32,b:i32}>> {fir.bindc_name = "this"})
subroutine tbp_nopass()
end subroutine
@@ -127,7 +127,7 @@ subroutine tbp_pass_arg1(i, this)
integer, intent(in) :: i
class(p1) :: this
end subroutine
- ! CHECK-LABEL: func.func @_QMcall_dispatchPtbp_pass_arg1(%{{.*}}: !fir.ref<i32> {fir.bindc_name = "i"}, %{{.*}}: !fir.class<!fir.type<_QMcall_dispatchTp1{a:i32,b:i32}>> {fir.bindc_name = "this"})
+ ! CHECK-LABEL: func.func @_QMcall_dispatchPtbp_pass_arg1(%{{.*}}: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}, %{{.*}}: !fir.class<!fir.type<_QMcall_dispatchTp1{a:i32,b:i32}>> {fir.bindc_name = "this"})
subroutine check_dispatch(p)
class(p1) :: p
diff --git a/flang/test/Lower/dummy-argument-readonly.f90 b/flang/test/Lower/dummy-argument-readonly.f90
new file mode 100644
index 0000000000000..97ae5bea85227
--- /dev/null
+++ b/flang/test/Lower/dummy-argument-readonly.f90
@@ -0,0 +1,175 @@
+! Test the fir.read_only marker produced by CallInterface for the supported
+! subset of INTENT(IN) dummy data objects. The marker is emitted during
+! lowering independently of the optimization level.
+!
+! RUN: %flang_fc1 -emit-hlfir %s -o - | FileCheck %s
+
+subroutine scalar_intent_in(x)
+ integer, intent(in) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPscalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.read_only}) {
+
+subroutine real_scalar_intent_in(x)
+ real, intent(in) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPreal_scalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<f32> {fir.bindc_name = "x", fir.read_only}) {
+
+subroutine complex_scalar_intent_in(x)
+ complex, intent(in) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPcomplex_scalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<complex<f32>> {fir.bindc_name = "x", fir.read_only}) {
+
+subroutine logical_scalar_intent_in(x)
+ logical, intent(in) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPlogical_scalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.logical<4>> {fir.bindc_name = "x", fir.read_only}) {
+
+recursive subroutine recursive_scalar_intent_in(x)
+ integer, intent(in) :: x
+ if (x > 0) call recursive_scalar_intent_in(x - 1)
+end subroutine
+! CHECK-LABEL: func.func @_QPrecursive_scalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.read_only}) attributes
+
+subroutine optional_scalar_intent_in(x)
+ integer, intent(in), optional :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPoptional_scalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.optional, fir.read_only}) {
+
+subroutine target_scalar_intent_in(x)
+ integer, intent(in), target :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPtarget_scalar_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.read_only, fir.target}) {
+
+subroutine bindc_definition_intent_in(x) bind(c)
+ use iso_c_binding, only : c_int
+ integer(c_int), intent(in) :: x
+end subroutine
+! CHECK-LABEL: func.func @bindc_definition_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x", fir.read_only}) attributes
+
+! Arrays are excluded because a forwarded dummy without an INTENT contract may
+! require compiler-generated copy-out.
+subroutine explicit_shape_intent_in(x)
+ integer, intent(in) :: x(10)
+end subroutine
+! CHECK-LABEL: func.func @_QPexplicit_shape_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.array<10xi32>> {fir.bindc_name = "x"}) {
+
+subroutine assumed_size_intent_in(x)
+ integer, intent(in) :: x(*)
+end subroutine
+! CHECK-LABEL: func.func @_QPassumed_size_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "x"}) {
+
+subroutine target_explicit_shape_intent_in(x)
+ integer, intent(in), target :: x(10)
+end subroutine
+! CHECK-LABEL: func.func @_QPtarget_explicit_shape_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.array<10xi32>> {fir.bindc_name = "x", fir.target}) {
+
+subroutine assumed_shape_intent_in(x)
+ integer, intent(in) :: x(:)
+end subroutine
+! CHECK-LABEL: func.func @_QPassumed_shape_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.box<!fir.array<?xi32>> {fir.bindc_name = "x"}) {
+
+subroutine assumed_rank_intent_in(x)
+ integer, intent(in) :: x(..)
+end subroutine
+! CHECK-LABEL: func.func @_QPassumed_rank_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.box<!fir.array<*:i32>> {fir.bindc_name = "x"}) {
+
+! CHARACTER and derived types are outside the current conservative subset.
+subroutine character_intent_in(x)
+ character(len=*), intent(in) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPcharacter_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.boxchar<1> {fir.bindc_name = "x"}) {
+
+module readonly_derived_types
+ type :: type_with_array
+ real :: values(4)
+ end type
+contains
+ subroutine derived_with_array_intent_in(x)
+ type(type_with_array), intent(in) :: x
+ end subroutine
+! CHECK-LABEL: func.func @_QMreadonly_derived_typesPderived_with_array_intent_in(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.type<{{.*}}>> {fir.bindc_name = "x"}) {
+end module
+
+subroutine intent_inout(x)
+ integer, intent(inout) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_inout(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x"}) {
+
+subroutine intent_out(x)
+ integer, intent(out) :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_out(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x"}) {
+
+subroutine intent_unspecified(x)
+ integer :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_unspecified(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.bindc_name = "x"}) {
+
+subroutine intent_in_value(x)
+ integer, intent(in), value :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_value(
+! CHECK-SAME: %{{.*}}: i32 {fir.bindc_name = "x"}) {
+
+subroutine intent_in_pointer(x)
+ integer, intent(in), pointer :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_pointer(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.bindc_name = "x", fir.read_only}) {
+
+subroutine intent_in_allocatable(x)
+ integer, intent(in), allocatable :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_allocatable(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.heap<i32>>> {fir.bindc_name = "x", fir.read_only}) {
+
+subroutine intent_in_pointer_array(x)
+ integer, intent(in), pointer :: x(:)
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_pointer_array(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.ptr<!fir.array<?xi32>>>> {fir.bindc_name = "x", fir.read_only}) {
+
+subroutine intent_in_allocatable_array(x)
+ integer, intent(in), allocatable :: x(:)
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_allocatable_array(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.heap<!fir.array<?xi32>>>> {fir.bindc_name = "x", fir.read_only}) {
+
+! The marker is shallow for descriptor arguments. Defining a POINTER target is
+! valid even though the descriptor itself has INTENT(IN).
+subroutine intent_in_pointer_target_write(x)
+ integer, intent(in), pointer :: x
+ x = 42
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_pointer_target_write(
+! CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.bindc_name = "x", fir.read_only}) {
+! CHECK: hlfir.assign {{.*}} to {{.*}} : i32, !fir.ptr<i32>
+
+subroutine intent_in_asynchronous(x)
+ integer, intent(in), asynchronous :: x
+end subroutine
+! CHECK-LABEL: func.func @_QPintent_in_asynchronous(
+! CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.asynchronous, fir.bindc_name = "x"}) {
+
+! VOLATILE with INTENT(IN) is prohibited by C870 (Fortran 2023) and is already
+! covered by test/Semantics/misc-declarations.f90, so it cannot be exercised by
+! a valid lowering test. The Volatile exclusion in
+! dummyArgCanUseLLVMReadonly remains a defensive check.
diff --git a/flang/test/Lower/polymorphic-temp.f90 b/flang/test/Lower/polymorphic-temp.f90
index 394c8e2069f2e..cb52a1b9f5581 100644
--- a/flang/test/Lower/polymorphic-temp.f90
+++ b/flang/test/Lower/polymorphic-temp.f90
@@ -208,7 +208,7 @@ subroutine test_merge_intrinsic2(a, b, i)
end subroutine
! CHECK-LABEL: func.func @_QMpoly_tmpPtest_merge_intrinsic2(
-! CHECK-SAME: %[[A:.*]]: !fir.ref<!fir.class<!fir.heap<!fir.type<_QMpoly_tmpTp1{a:i32}>>>> {fir.bindc_name = "a"}, %[[B:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.type<_QMpoly_tmpTp1{a:i32}>>>> {fir.bindc_name = "b"}, %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i"}) {
+! CHECK-SAME: %[[A:.*]]: !fir.ref<!fir.class<!fir.heap<!fir.type<_QMpoly_tmpTp1{a:i32}>>>> {fir.bindc_name = "a", fir.read_only}, %[[B:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.type<_QMpoly_tmpTp1{a:i32}>>>> {fir.bindc_name = "b"}, %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}) {
! CHECK: %[[A_DECL:.*]]:2 = hlfir.declare %[[A]]
! CHECK: %[[B_DECL:.*]]:2 = hlfir.declare %[[B]]
! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %[[I]]
@@ -230,7 +230,7 @@ subroutine test_merge_intrinsic3(a, b, i)
end subroutine
! CHECK-LABEL: func.func @_QMpoly_tmpPtest_merge_intrinsic3(
-! CHECK-SAME: %[[A:.*]]: !fir.class<none> {fir.bindc_name = "a"}, %[[B:.*]]: !fir.class<none> {fir.bindc_name = "b"}, %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i"}) {
+! CHECK-SAME: %[[A:.*]]: !fir.class<none> {fir.bindc_name = "a"}, %[[B:.*]]: !fir.class<none> {fir.bindc_name = "b"}, %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}) {
! CHECK: %[[A_DECL:.*]]:2 = hlfir.declare %[[A]]
! CHECK: %[[B_DECL:.*]]:2 = hlfir.declare %[[B]]
! CHECK: %[[I_DECL:.*]]:2 = hlfir.declare %[[I]]
@@ -247,7 +247,7 @@ subroutine test_merge_intrinsic4(i)
end subroutine
! CHECK-LABEL: func.func @_QMpoly_tmpPtest_merge_intrinsic4(
-! CHECK-SAME: %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i"}) {
+! CHECK-SAME: %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}) {
! CHECK: %[[V_0:[0-9]+]] = fir.alloca !fir.class<!fir.heap<none>> {bindc_name = "a", uniq_name = "_QMpoly_tmpFtest_merge_intrinsic4Ea"}
! CHECK: %[[V_1:[0-9]+]] = fir.zero_bits !fir.heap<none>
! CHECK: %[[V_2:[0-9]+]] = fir.embox %[[V_1]] : (!fir.heap<none>) -> !fir.class<!fir.heap<none>>
@@ -274,7 +274,7 @@ subroutine test_merge_intrinsic5(i)
end subroutine
! CHECK-LABEL: func.func @_QMpoly_tmpPtest_merge_intrinsic5(
-! CHECK-SAME: %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i"}) {
+! CHECK-SAME: %[[I:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}) {
! CHECK: %[[V_0:[0-9]+]] = fir.alloca !fir.class<!fir.ptr<none>> {bindc_name = "a", uniq_name = "_QMpoly_tmpFtest_merge_intrinsic5Ea"}
! CHECK: %[[V_1:[0-9]+]] = fir.zero_bits !fir.ptr<none>
! CHECK: %[[V_2:[0-9]+]] = fir.embox %[[V_1]] : (!fir.ptr<none>) -> !fir.class<!fir.ptr<none>>
diff --git a/flang/test/Lower/polymorphic.f90 b/flang/test/Lower/polymorphic.f90
index cd9a9a213e354..3e47463222077 100644
--- a/flang/test/Lower/polymorphic.f90
+++ b/flang/test/Lower/polymorphic.f90
@@ -66,7 +66,7 @@ elemental subroutine assign_p1_int(lhs, rhs)
lhs%b = rhs
End Subroutine
! CHECK-LABEL: func.func @_QMpolymorphic_testPassign_p1_int(
-! CHECK-SAME: %[[ARG0:.*]]: !fir.class<!fir.type<_QMpolymorphic_testTp1{a:i32,b:i32}>> {fir.bindc_name = "lhs"}, %[[ARG1:.*]]: !fir.ref<i32> {fir.bindc_name = "rhs"}
+! CHECK-SAME: %[[ARG0:.*]]: !fir.class<!fir.type<_QMpolymorphic_testTp1{a:i32,b:i32}>> {fir.bindc_name = "lhs"}, %[[ARG1:.*]]: !fir.ref<i32> {fir.bindc_name = "rhs", fir.read_only}
! CHECK-SAME: attributes {fir.proc_attrs = #fir.proc_attrs<elemental, pure>}
! CHECK: %[[LHS:.*]]:2 = hlfir.declare %[[ARG0]]{{.*}}{fortran_attrs = #fir.var_attrs<intent_inout>, uniq_name = "_QMpolymorphic_testFassign_p1_intElhs"}
! CHECK: %[[RHS:.*]]:2 = hlfir.declare %[[ARG1]]{{.*}}{fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QMpolymorphic_testFassign_p1_intErhs"}
@@ -102,7 +102,7 @@ elemental subroutine elemental_sub_pass(c, this)
this%a = this%a * this%b + c
end subroutine
! CHECK-LABEL: func.func @_QMpolymorphic_testPelemental_sub_pass(
-! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32> {fir.bindc_name = "c"}, %[[ARG1:.*]]: !fir.class<!fir.type<_QMpolymorphic_testTp1{a:i32,b:i32}>> {fir.bindc_name = "this"})
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32> {fir.bindc_name = "c", fir.read_only}, %[[ARG1:.*]]: !fir.class<!fir.type<_QMpolymorphic_testTp1{a:i32,b:i32}>> {fir.bindc_name = "this"})
! CHECK-SAME: attributes {fir.proc_attrs = #fir.proc_attrs<elemental, pure>}
logical elemental function lt(i, poly)
@@ -111,7 +111,7 @@ logical elemental function lt(i, poly)
lt = i < poly%a
End Function
! CHECK-LABEL: func.func @_QMpolymorphic_testPlt(
-! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32> {fir.bindc_name = "i"}, %[[ARG1:.*]]: !fir.class<!fir.type<_QMpolymorphic_testTp1{a:i32,b:i32}>> {fir.bindc_name = "poly"})
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.read_only}, %[[ARG1:.*]]: !fir.class<!fir.type<_QMpolymorphic_testTp1{a:i32,b:i32}>> {fir.bindc_name = "poly"})
! CHECK-SAME: -> !fir.logical<4> attributes {fir.proc_attrs = #fir.proc_attrs<elemental, pure>}
! CHECK: %[[I:.*]]:2 = hlfir.declare %[[ARG0]]{{.*}}{fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QMpolymorphic_testFltEi"}
! CHECK: %[[POLY:.*]]:2 = hlfir.declare %[[ARG1]]{{.*}}{fortran_attrs = #fir.var_attrs<intent_in>, uniq_name = "_QMpolymorphic_testFltEpoly"}
@@ -680,7 +680,7 @@ subroutine opt_int(i)
call opt_up(i)
end subroutine
! CHECK-LABEL: func.func @_QMpolymorphic_testPopt_int(
-! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.optional}
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<i32> {fir.bindc_name = "i", fir.optional, fir.read_only}
subroutine opt_up(up)
class(*), optional, intent(in) :: up
@@ -714,7 +714,7 @@ subroutine up_pointer(p)
class(*), pointer, intent(in) :: p
end subroutine
! CHECK-LABEL: func.func @_QMpolymorphic_testPup_pointer(
-! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.class<!fir.ptr<none>>> {fir.bindc_name = "p"}
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.class<!fir.ptr<none>>> {fir.bindc_name = "p", fir.read_only}
subroutine test_char_to_up_pointer(c)
character(*), target :: c
@@ -884,4 +884,3 @@ program test
l = i < o%inner
end program
-
diff --git a/flang/test/Lower/select-type.f90 b/flang/test/Lower/select-type.f90
index 1c6bb5ce0d4f6..edec4e276bd1b 100644
--- a/flang/test/Lower/select-type.f90
+++ b/flang/test/Lower/select-type.f90
@@ -166,7 +166,7 @@ subroutine select_type3(a)
end subroutine
! CHECK-LABEL: func.func @_QMselect_type_lower_testPselect_type3(
-! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>> {fir.bindc_name = "a"})
+! CHECK-SAME: %[[ARG0:.*]]: !fir.ref<!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>> {fir.bindc_name = "a", fir.read_only})
! CHECK: %[[A:.*]]:2 = hlfir.declare %[[ARG0]]{{.*}}{fortran_attrs = #fir.var_attrs<intent_in, pointer>, uniq_name = "_QMselect_type_lower_testFselect_type3Ea"}
! CHECK: %[[ARG0_LOAD:.*]] = fir.load %[[A]]#0 : !fir.ref<!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>>
! CHECK: %[[SELECTOR:.*]] = hlfir.designate %[[ARG0_LOAD]] (%{{.*}}) : (!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>, index) -> !fir.class<!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>
@@ -177,7 +177,7 @@ subroutine select_type3(a)
! CHECK: ^[[DEFAULT_BLK]]
! CFG-LABEL: func.func @_QMselect_type_lower_testPselect_type3(
-! CFG-SAME: %[[ARG0:.*]]: !fir.ref<!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>> {fir.bindc_name = "a"}) {
+! CFG-SAME: %[[ARG0:.*]]: !fir.ref<!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>> {fir.bindc_name = "a", fir.read_only}) {
! CFG: %[[SELECTOR:.*]] = hlfir.designate %{{.*}} (%{{.*}}) : (!fir.class<!fir.ptr<!fir.array<?x!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>>>, index) -> !fir.class<!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>
! CFG: %[[TDESC_P1_ADDR:.*]] = fir.type_desc !fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>
! CFG: %[[SELECTOR_TDESC:.*]] = fir.box_tdesc %[[SELECTOR]] : (!fir.class<!fir.type<_QMselect_type_lower_testTp1{a:i32,b:i32}>>) -> !fir.tdesc<{{.*}}>
diff --git a/flang/test/Lower/unsigned-ops.f90 b/flang/test/Lower/unsigned-ops.f90
index 644f0c4ea11c9..ff6704e37d77b 100644
--- a/flang/test/Lower/unsigned-ops.f90
+++ b/flang/test/Lower/unsigned-ops.f90
@@ -5,7 +5,7 @@ unsigned function f01(u, v)
f01 = u + v - 1u
end
-!CHECK: func.func @_QPf01(%[[ARG0:.*]]: !fir.ref<ui32> {fir.bindc_name = "u"}, %[[ARG1:.*]]: !fir.ref<ui32> {fir.bindc_name = "v"}) -> ui32 {
+!CHECK: func.func @_QPf01(%[[ARG0:.*]]: !fir.ref<ui32> {fir.bindc_name = "u", fir.read_only}, %[[ARG1:.*]]: !fir.ref<ui32> {fir.bindc_name = "v", fir.read_only}) -> ui32 {
!CHECK: %[[C1_I32:.*]] = arith.constant 1 : i32
!CHECK: %[[VAL_0:.*]] = fir.dummy_scope : !fir.dscope
!CHECK: %[[VAL_1:.*]] = fir.alloca ui32 {bindc_name = "f01", uniq_name = "_QFf01Ef01"}
@@ -30,7 +30,7 @@ unsigned function f02(u, v)
f02 = u ** v - 1u
end
-!CHECK: func.func @_QPf02(%[[ARG0:.*]]: !fir.ref<ui32> {fir.bindc_name = "u"}, %[[ARG1:.*]]: !fir.ref<ui32> {fir.bindc_name = "v"}) -> ui32 {
+!CHECK: func.func @_QPf02(%[[ARG0:.*]]: !fir.ref<ui32> {fir.bindc_name = "u", fir.read_only}, %[[ARG1:.*]]: !fir.ref<ui32> {fir.bindc_name = "v", fir.read_only}) -> ui32 {
!CHECK: %[[C1_i32:.*]] = arith.constant 1 : i32
!CHECK: %[[VAL_0:.*]] = fir.dummy_scope : !fir.dscope
!CHECK: %[[VAL_1:.*]] = fir.alloca ui32 {bindc_name = "f02", uniq_name = "_QFf02Ef02"}
@@ -49,4 +49,3 @@ unsigned function f02(u, v)
!CHECK: fir.store %[[VAL_13]] to %[[VAL_2]] : !fir.ref<ui32>
!CHECK: %[[VAL_14:.*]] = fir.load %[[VAL_2]] : !fir.ref<ui32>
!CHECK: return %[[VAL_14]] : ui32
-
diff --git a/flang/test/Lower/volatile1.f90 b/flang/test/Lower/volatile1.f90
index ec1c85eb8e981..8d7c71fd0e05c 100644
--- a/flang/test/Lower/volatile1.f90
+++ b/flang/test/Lower/volatile1.f90
@@ -76,7 +76,7 @@ subroutine declared_volatile_in_this_scope(v,n)
! CHECK-LABEL: func.func private @_QFPdeclared_volatile_in_this_scope(
! CHECK-SAME: %[[VAL_0:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<!fir.array<?xi32>> {fir.bindc_name = "v"},
-! CHECK-SAME: %[[VAL_1:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<i32> {fir.bindc_name = "n"}) attributes {{.+}} {
+! CHECK-SAME: %[[VAL_1:[0-9]+|[a-zA-Z$._-][a-zA-Z0-9$._-]*]]: !fir.ref<i32> {fir.bindc_name = "n", fir.read_only}) attributes {{.+}} {
! CHECK: %[[VAL_2:.*]] = arith.constant 1 : i32
! CHECK: %[[VAL_3:.*]] = arith.constant 0 : index
! CHECK: %[[VAL_4:.*]] = fir.dummy_scope : !fir.dscope
diff --git a/flang/test/Transforms/function-attrs-readonly.fir b/flang/test/Transforms/function-attrs-readonly.fir
new file mode 100644
index 0000000000000..7f68424dc1283
--- /dev/null
+++ b/flang/test/Transforms/function-attrs-readonly.fir
@@ -0,0 +1,90 @@
+// RUN: fir-opt --function-attr="set-readonly=true" %s | FileCheck %s --check-prefix=CHECK
+// RUN: fir-opt --function-attr="set-readonly=true set-nocapture=true set-noalias=true" %s | FileCheck %s --check-prefix=ALL
+// RUN: fir-opt --function-attr="set-readonly=false" %s | FileCheck %s --check-prefix=OFF
+
+// Test translation of the FIR marker placed on INTENT(IN) by-reference dummy
+// arguments to the LLVM dialect argument attribute.
+
+// CHECK-LABEL: func.func private @test_ref(
+// CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only, llvm.readonly}) {
+// ALL-LABEL: func.func private @test_ref(
+// ALL-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only, llvm.noalias, llvm.nocapture, llvm.readonly}) {
+// OFF-LABEL: func.func private @test_ref(
+// OFF-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only}) {
+func.func private @test_ref(%arg0: !fir.ref<i32> {fir.read_only}) {
+ return
+}
+
+// An unmarked reference is not read-only.
+// CHECK-LABEL: func.func private @test_unmarked_ref(
+// CHECK-SAME: %{{.*}}: !fir.ref<i32>) {
+func.func private @test_unmarked_ref(%arg0: !fir.ref<i32>) {
+ return
+}
+
+// TARGET prevents noalias and nocapture, but it does not prevent readonly.
+// Match the complete argument attribute dictionary under the ALL run so an
+// accidental llvm.noalias or llvm.nocapture addition makes the test fail.
+// CHECK-LABEL: func.func private @test_target(
+// CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only, fir.target, llvm.readonly}) {
+// ALL-LABEL: func.func private @test_target(
+// ALL-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only, fir.target, llvm.readonly}) {
+func.func private @test_target(%arg0: !fir.ref<i32> {fir.read_only, fir.target}) {
+ return
+}
+
+// FunctionAttr mechanically translates an explicit marker on any reference.
+// This is independent of which source-level dummies CallInterface marks.
+// CHECK-LABEL: func.func private @test_derived(
+// CHECK-SAME: %{{.*}}: !fir.ref<!fir.type<_QFtest_derivedTt{p:!fir.box<!fir.ptr<i32>>}>> {fir.read_only, llvm.readonly}) {
+func.func private @test_derived(%arg0: !fir.ref<!fir.type<_QFtest_derivedTt{p:!fir.box<!fir.ptr<i32>>}>> {fir.read_only}) {
+ return
+}
+
+// On a reference to a descriptor, readonly protects the descriptor storage,
+// not the data address stored in the descriptor.
+// CHECK-LABEL: func.func private @test_pointer_descriptor(
+// CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.read_only, llvm.readonly}) {
+// ALL-LABEL: func.func private @test_pointer_descriptor(
+// ALL-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.read_only, llvm.nocapture, llvm.readonly}) {
+func.func private @test_pointer_descriptor(%arg0: !fir.ref<!fir.box<!fir.ptr<i32>>> {fir.read_only}) {
+ return
+}
+
+// CHECK-LABEL: func.func private @test_allocatable_descriptor(
+// CHECK-SAME: %{{.*}}: !fir.ref<!fir.box<!fir.heap<i32>>> {fir.read_only, llvm.readonly}) {
+func.func private @test_allocatable_descriptor(%arg0: !fir.ref<!fir.box<!fir.heap<i32>>> {fir.read_only}) {
+ return
+}
+
+// A Fortran BIND(C) definition must obey INTENT(IN), so readonly is valid.
+// Unlike ordinary definitions, it must not receive noalias or nocapture.
+// CHECK-LABEL: func.func @test_bindc_definition(
+// CHECK-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only, llvm.readonly}) attributes
+// ALL-LABEL: func.func @test_bindc_definition(
+// ALL-SAME: %{{.*}}: !fir.ref<i32> {fir.read_only, llvm.readonly}) attributes
+func.func @test_bindc_definition(%arg0: !fir.ref<i32> {fir.read_only}) attributes {fir.bindc_name = "test_bindc_definition", fir.proc_attrs = #fir.proc_attrs<bind_c>} {
+ return
+}
+
+// Do not apply readonly to a declaration of an external C procedure: its body
+// is not constrained by the Fortran INTENT appearing in the local interface.
+// Use a module-style FIR name so this exercises the BIND(C)-declaration check,
+// rather than the generic check that skips non-module declarations.
+// CHECK-LABEL: func.func private @_QMreadonly_modPbindc_declaration(
+// CHECK-SAME: !fir.ref<i32> {fir.read_only}) attributes
+func.func private @_QMreadonly_modPbindc_declaration(!fir.ref<i32> {fir.read_only}) attributes {fir.bindc_name = "bindc_declaration", fir.proc_attrs = #fir.proc_attrs<bind_c>}
+
+// A declaration of a Fortran module procedure carries the Fortran contract.
+// CHECK-LABEL: func.func private @_QMreadonly_modPfortran_declaration(
+// CHECK-SAME: !fir.ref<i32> {fir.read_only, llvm.readonly})
+func.func private @_QMreadonly_modPfortran_declaration(!fir.ref<i32> {fir.read_only})
+
+// A fir.box passed by value is not a ReferenceType, so FunctionAttr does not
+// translate its marker. This is distinct from POINTER and ALLOCATABLE dummies,
+// whose descriptors are passed by reference and are covered above.
+// CHECK-LABEL: func.func private @test_box(
+// CHECK-SAME: %{{.*}}: !fir.box<!fir.array<?xi32>> {fir.read_only}) {
+func.func private @test_box(%arg0: !fir.box<!fir.array<?xi32>> {fir.read_only}) {
+ return
+}
More information about the flang-commits
mailing list