[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`.
This commit is contained in:
Sergey Shcherbinin authored and GitHub committed 2026-07-14 10:20:15 +05:30
1 parent a527c6f0bc
commit a70234b00d
23 files changed
+447 -41

No files matched your search

@@ -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() {
@@ -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">,
];
}
+43 -1
View File
@@ -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 @@ private:
} 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 @@ private:
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)
+2 -1
View File
@@ -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>(
+25 -10
View File
@@ -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);
}
}
@@ -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
@@ -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"}
+1 -1
View File
@@ -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]] {{.*}}
+1 -1
View File
@@ -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]] {{.*}}
+1 -1
View File
@@ -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]]
@@ -15,7 +15,7 @@ module foo
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>>)
@@ -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"}) {
! [...]
+1 -1
View File
@@ -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"}
+2 -2
View File
@@ -7,7 +7,7 @@ end
! 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)
+25 -1
View File
@@ -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 @@ end subroutine
! 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
+2 -2
View File
@@ -107,7 +107,7 @@ module call_dispatch
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 @@ module call_dispatch
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
@@ -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.
+4 -4
View File
@@ -208,7 +208,7 @@ contains
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 @@ contains
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 @@ contains
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 @@ contains
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>>
+5 -6
View File
@@ -66,7 +66,7 @@ module polymorphic_test
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 @@ module polymorphic_test
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 @@ module polymorphic_test
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 @@ module polymorphic_test
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 @@ module polymorphic_test
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
+2 -2
View File
@@ -166,7 +166,7 @@ contains
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 @@ contains
! 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<{{.*}}>
+2 -3
View File
@@ -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 @@ end
!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
+1 -1
View File
@@ -76,7 +76,7 @@ end program
! 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
@@ -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
}