[flang-commits] [flang] bd70cf2 - [flang] Avoid descriptor conversion for descriptor args with ignore_tkr(c) (#176240)
via flang-commits
flang-commits at lists.llvm.org
Tue Jan 20 10:21:19 PST 2026
Author: Eugene Epshteyn
Date: 2026-01-20T13:21:15-05:00
New Revision: bd70cf279ce0880ebbbcfcdedfeead42852b99f6
URL: https://github.com/llvm/llvm-project/commit/bd70cf279ce0880ebbbcfcdedfeead42852b99f6
DIFF: https://github.com/llvm/llvm-project/commit/bd70cf279ce0880ebbbcfcdedfeead42852b99f6.diff
LOG: [flang] Avoid descriptor conversion for descriptor args with ignore_tkr(c) (#176240)
For descriptor arguments marked with `!dir$ ignore_tkr(c)` we want to
leave them unmodified: don't create another descriptor or change the
existing descriptor.
Added:
flang/test/Lower/HLFIR/ignore-tkr-c-descriptor.f90
Modified:
flang/docs/Directives.md
flang/include/flang/Optimizer/Builder/HLFIRTools.h
flang/lib/Lower/ConvertCall.cpp
Removed:
################################################################################
diff --git a/flang/docs/Directives.md b/flang/docs/Directives.md
index 5640e44e16bae..704bb76a1d254 100644
--- a/flang/docs/Directives.md
+++ b/flang/docs/Directives.md
@@ -19,8 +19,11 @@ A list of non-standard directives supported by Flang
incompatible in type (T), kind (K), rank (R), CUDA device (D), or managed (M)
status. The letter (A) is a shorthand for (TKRDM), and is the default when no
letters appear. The letter (C) checks for contiguity, for example allowing an
- element of an assumed-shape array to be passed as a dummy argument. The
- letter (P) ignores pointer and allocatable matching, so that one can pass an
+ element of an assumed-shape array to be passed as a dummy argument. It also
+ specifies that dummy arguments passed by descriptor should not have their
+ descriptor copied or reboxed, allowing the original descriptor to be passed
+ directly even if attributes like ALLOCATABLE or POINTER don't match exactly.
+ The letter (P) ignores pointer and allocatable matching, so that one can pass an
allocatable array to routine with pointer array argument and vice versa. For
example, if one wanted to call a "set all bytes to zero" utility that could
be applied to arrays of any type or rank:
diff --git a/flang/include/flang/Optimizer/Builder/HLFIRTools.h b/flang/include/flang/Optimizer/Builder/HLFIRTools.h
index 9933e3ed6c308..c41313da050ac 100644
--- a/flang/include/flang/Optimizer/Builder/HLFIRTools.h
+++ b/flang/include/flang/Optimizer/Builder/HLFIRTools.h
@@ -66,6 +66,7 @@ class Entity : public mlir::Value {
bool isBoxAddressOrValue() const {
return hlfir::isBoxAddressOrValueType(getType());
}
+ bool isBoxAddress() const { return fir::isBoxAddress(getType()); }
/// Is this entity a procedure designator?
bool isProcedure() const { return isFortranProcedureValue(getType()); }
diff --git a/flang/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 2cbb6f20d34d7..7bdd84903c1d3 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -298,6 +298,50 @@ getResultLengthFromElementalOp(fir::FirOpBuilder &builder,
lengths.push_back(len);
}
+// Go through the args. Any descriptor args that have ignore_tkr(c) cause
+// function type modification to avoid changing the descriptor args.
+static std::optional<mlir::FunctionType>
+getTypeWithIgnoreTkrC(mlir::FunctionType funcType,
+ Fortran::lower::CallerInterface &caller,
+ mlir::MLIRContext *context) {
+ llvm::SmallVector<mlir::Type> newInputs =
+ llvm::to_vector(funcType.getInputs());
+ bool typeChanged = false;
+ for (const auto &arg : caller.getPassedArguments()) {
+ if (arg.firArgument >= 0 &&
+ arg.firArgument < static_cast<int>(newInputs.size())) {
+
+ // Only need to change the arg type for ignore_tkr(c)
+ if (!arg.testTKR(Fortran::common::IgnoreTKR::Contiguous))
+ continue;
+
+ mlir::Type expectedType = newInputs[arg.firArgument];
+ // Cast is only needed for descriptors
+ if (!fir::isa_box_type(expectedType))
+ continue;
+
+ // Handle ignore_tkr(c) for descriptors
+ mlir::Value actual = caller.getInput(arg);
+ if (!actual)
+ continue;
+
+ mlir::Type actualType = actual.getType();
+ if (fir::isBoxAddress(actualType)) {
+ newInputs[arg.firArgument] = actualType;
+ typeChanged = true;
+ }
+ }
+ }
+
+ if (typeChanged) {
+ // At least one of the arguments had its type changed, so need to
+ // create a new function type to be used in a cast.
+ return mlir::FunctionType::get(context, newInputs, funcType.getResults());
+ }
+
+ return std::nullopt;
+}
+
std::pair<Fortran::lower::LoweredResult, bool>
Fortran::lower::genCallOpAndResult(
mlir::Location loc, Fortran::lower::AbstractConverter &converter,
@@ -495,6 +539,29 @@ Fortran::lower::genCallOpAndResult(
mlir::FunctionType funcType =
funcPointer ? callSiteType : caller.getFuncOp().getFunctionType();
+
+ // If we have any ignore_tkr(c) dummy args, adjust the function type to
+ // have these args match the caller.
+ if (auto modifiedFuncType =
+ getTypeWithIgnoreTkrC(funcType, caller, builder.getContext())) {
+ // Note: funcPointer would only be non-null here, if we are already
+ // processing indirect function call. In such case we can re-use the same
+ // funcPointer and we'll cast it below the the modified funcType.
+ if (!funcPointer) {
+ // We want to cast the function to a
diff erent type, in order to avoid
+ // changing/casting some of the args. The cast will generate a new
+ // function pointer, so that we would make a function call not through
+ // the original function symbol, but through the new function pointer
+ // (an indirect function call).
+ mlir::SymbolRefAttr symbolAttr =
+ builder.getSymbolRefAttr(caller.getMangledName());
+ // Create pointer to original function. This pointer will be cast later.
+ funcPointer = fir::AddrOfOp::create(builder, loc, funcType, symbolAttr);
+ funcSymbolAttr = {}; // This marks it as indirect call
+ }
+ funcType = *modifiedFuncType;
+ }
+
llvm::SmallVector<mlir::Value> operands;
// First operand of indirect call is the function pointer. Cast it to
// required function type for the call to handle procedures that have a
@@ -1257,6 +1324,12 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
// element if this is an array in an elemental call.
hlfir::Entity actual = preparedActual.getActual(loc, builder);
+ if (arg.testTKR(Fortran::common::IgnoreTKR::Contiguous) &&
+ actual.isBoxAddress()) {
+ // With ignore_tkr(c), pointer to a descriptor should be passed as is
+ return PreparedDummyArgument{actual, /*cleanups=*/{}};
+ }
+
// Handle procedure arguments (procedure pointers should go through
// prepareProcedurePointerActualArgument).
if (hlfir::isFortranProcedureValue(dummyType)) {
@@ -1756,6 +1829,12 @@ void prepareUserCallArguments(
caller.placeInput(arg, boxStorage);
continue;
}
+ if (arg.testTKR(Fortran::common::IgnoreTKR::Contiguous) &&
+ actual.isBoxAddress()) {
+ // With ignore_tkr(c), pointer to a descriptor should be passed as is
+ caller.placeInput(arg, actual);
+ continue;
+ }
if (fir::isPointerType(argTy) &&
(!Fortran::evaluate::IsObjectPointer(*expr) || thisIsPassArg)) {
// Passing a non POINTER actual argument to a POINTER dummy argument.
diff --git a/flang/test/Lower/HLFIR/ignore-tkr-c-descriptor.f90 b/flang/test/Lower/HLFIR/ignore-tkr-c-descriptor.f90
new file mode 100644
index 0000000000000..8982614c51552
--- /dev/null
+++ b/flang/test/Lower/HLFIR/ignore-tkr-c-descriptor.f90
@@ -0,0 +1,55 @@
+! RUN: bbc -emit-hlfir -o - %s | FileCheck %s
+
+! Test that ignore_tkr(c) avoids descriptor copies (rebox/embox) for dummy arguments.
+
+module m_ignore_tkr_c
+ interface
+ subroutine pass_array_ptr(a)
+ !dir$ ignore_tkr(cp) a
+ real, pointer :: a(:)
+ end subroutine
+ subroutine pass_array_val(a)
+ !dir$ ignore_tkr(c) a
+ real :: a(:)
+ end subroutine
+ end interface
+contains
+ ! CHECK-LABEL: func.func @_QMm_ignore_tkr_cPs1(
+ ! CHECK-SAME: %[[ARR:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ subroutine s1(arr)
+ real, allocatable :: arr(:)
+ ! CHECK: %[[BOX_REF:.*]]:2 = hlfir.declare %[[ARR]]
+ ! CHECK: %[[CONV:.*]] = fir.convert %[[BOX_REF]]#0 : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> !fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>
+ ! CHECK: fir.call @_QPpass_array_ptr(%[[CONV]]) {{.*}} : (!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>) -> ()
+ call pass_array_ptr(arr)
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMm_ignore_tkr_cPs2(
+ ! CHECK-SAME: %[[ARR:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ subroutine s2(arr)
+ real, allocatable :: arr(:)
+ ! CHECK: %[[BOX_REF:.*]]:2 = hlfir.declare %[[ARR]]
+ ! CHECK-NOT: fir.load %[[BOX_REF]]#0
+ ! CHECK: %[[ADDR:.*]] = fir.address_of(@_QPpass_array_val)
+ ! CHECK: %[[CAST:.*]] = fir.convert %[[ADDR]]
+ ! CHECK: fir.call %[[CAST]](%[[BOX_REF]]#0) {{.*}} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> ()
+ call pass_array_val(arr)
+ end subroutine
+
+ ! CHECK-LABEL: func.func @_QMm_ignore_tkr_cPs3(
+ ! CHECK-SAME: %[[ARR:.*]]: !fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>
+ subroutine s3(arr)
+ real, allocatable :: arr(:)
+ procedure(pass_array_val), pointer :: p
+ p => pass_array_val
+ ! CHECK: %[[BOX_REF:.*]]:2 = hlfir.declare %[[ARR]]
+ ! CHECK: %[[P_REF:.*]]:2 = hlfir.declare %{{.*}} {fortran_attrs = #fir.var_attrs<pointer>, uniq_name = "_QMm_ignore_tkr_cFs3Ep"}
+ ! CHECK: %[[ADDR:.*]] = fir.load %[[P_REF]]#0 : !fir.ref<!fir.boxproc<(!fir.box<!fir.array<?xf32>>) -> ()>>
+ ! CHECK: %[[FUNC_ADDR:.*]] = fir.box_addr %[[ADDR]] : (!fir.boxproc<(!fir.box<!fir.array<?xf32>>) -> ()>) -> ((!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> ())
+ ! CHECK: fir.call %[[FUNC_ADDR]](%[[BOX_REF]]#0) {{.*}} : (!fir.ref<!fir.box<!fir.heap<!fir.array<?xf32>>>>) -> ()
+ call p(arr)
+ end subroutine
+
+ ! CHECK: func.func private @_QPpass_array_ptr(!fir.ref<!fir.box<!fir.ptr<!fir.array<?xf32>>>>)
+ ! CHECK: func.func private @_QPpass_array_val(!fir.box<!fir.array<?xf32>>)
+end module
More information about the flang-commits
mailing list