[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:
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">,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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]] {{.*}}
|
||||
|
||||
@@ -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]] {{.*}}
|
||||
|
||||
@@ -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"}) {
|
||||
! [...]
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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>>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<{{.*}}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user