[flang-commits] [flang] [flang] Avoid descriptor conversion for descriptor args with ignore_tkr(c) (PR #176240)

via flang-commits flang-commits at lists.llvm.org
Thu Jan 15 12:51:05 PST 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-fir-hlfir

Author: Eugene Epshteyn (eugeneepshteyn)

<details>
<summary>Changes</summary>

For descriptor arguments marked with `!dir$ ignore_tkr(c)` we want to leave them unmodified: don't create or change the descriptor.

---
Full diff: https://github.com/llvm/llvm-project/pull/176240.diff


3 Files Affected:

- (modified) flang/docs/Directives.md (+5-2) 
- (modified) flang/lib/Lower/ConvertCall.cpp (+88-9) 
- (added) flang/test/Lower/HLFIR/ignore-tkr-c-descriptor.f90 (+55) 


``````````diff
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/lib/Lower/ConvertCall.cpp b/flang/lib/Lower/ConvertCall.cpp
index 2cbb6f20d34d7..109a93b2751fe 100644
--- a/flang/lib/Lower/ConvertCall.cpp
+++ b/flang/lib/Lower/ConvertCall.cpp
@@ -298,6 +298,49 @@ 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 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.
+    funcType = mlir::FunctionType::get(context, newInputs,
+        funcType.getResults());
+  }
+
+  return funcType;
+}
+
 std::pair<Fortran::lower::LoweredResult, bool>
 Fortran::lower::genCallOpAndResult(
     mlir::Location loc, Fortran::lower::AbstractConverter &converter,
@@ -495,6 +538,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.
+  mlir::FunctionType modifiedFuncType =
+      getTypeWithIgnoreTkrC(funcType, caller, builder.getContext());
+
+  // Note: funcPointer would only be non-null in this case, 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 && modifiedFuncType != funcType) {
+    // We want to cast the function to a different 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
@@ -1396,7 +1462,17 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
   // Step 2: prepare the storage for the dummy arguments, ensuring that it
   // matches the dummy requirements (e.g., must be contiguous or must be
   // a temporary).
-  hlfir::Entity entity =
+
+  const bool ignoreTKRcontig =
+      arg.testTKR(Fortran::common::IgnoreTKR::Contiguous);
+
+  // If IgnoreTKR(C) is set and we are passing by descriptor, we want to
+  // avoid loading the descriptor if it is already a reference to a box.
+  const bool keepRefToBox =
+      arg.passBy == Fortran::lower::CallerInterface::PassEntityBy::Box &&
+      ignoreTKRcontig && actual.isMutableBox();
+
+  hlfir::Entity entity = keepRefToBox ? actual :
       hlfir::derefPointersAndAllocatables(loc, builder, actual);
   if (entity.isVariable()) {
     // Set dynamic type if needed before any copy-in or copy so that the dummy
@@ -1469,11 +1545,13 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
         loc, fir::isa_volatile_type(dummyType), entity)};
     addr = hlfir::genVariableBoxChar(loc, builder, nonVolatileEntity);
   } else if (mlir::isa<fir::BaseBoxType>(dummyTypeWithActualRank)) {
-    entity = hlfir::genVariableBox(loc, builder, entity);
+    if (!keepRefToBox)
+      entity = hlfir::genVariableBox(loc, builder, entity);
     // Ensures the box has the right attributes and that it holds an
     // addendum if needed.
-    fir::BaseBoxType actualBoxType =
-        mlir::cast<fir::BaseBoxType>(entity.getType());
+    fir::BaseBoxType actualBoxType = keepRefToBox ?
+        mlir::cast<fir::BaseBoxType>(fir::unwrapRefType(entity.getType()))
+        : mlir::cast<fir::BaseBoxType>(entity.getType());
     mlir::Type boxEleType = actualBoxType.getEleTy();
     // For now, assume it is not OK to pass the allocatable/pointer
     // descriptor to a non pointer/allocatable dummy. That is a strict
@@ -1492,8 +1570,8 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
     const bool needToAddAddendum =
         fir::isUnlimitedPolymorphicType(dummyTypeWithActualRank) &&
         !actualBoxHasAddendum;
-    if (needToAddAddendum || actualBoxHasAllocatableOrPointerFlag ||
-        needsZeroLowerBounds) {
+    if ((needToAddAddendum || actualBoxHasAllocatableOrPointerFlag ||
+        needsZeroLowerBounds) && !ignoreTKRcontig) {
       if (actualIsAssumedRank) {
         auto lbModifier = needsZeroLowerBounds
                               ? fir::LowerBoundModifierAttribute::SetToZeroes
@@ -1526,8 +1604,8 @@ static PreparedDummyArgument preparePresentUserCallActualArgument(
   // descriptors.
   // For TKR dummy characters, the boxchar creation also happens later when
   // creating the fir.call .
-  preparedDummy.dummy =
-      builder.createConvert(loc, dummyTypeWithActualRank, addr);
+  preparedDummy.dummy = keepRefToBox ? addr
+      : builder.createConvert(loc, dummyTypeWithActualRank, addr);
   return preparedDummy;
 }
 
@@ -1757,7 +1835,8 @@ void prepareUserCallArguments(
         continue;
       }
       if (fir::isPointerType(argTy) &&
-          (!Fortran::evaluate::IsObjectPointer(*expr) || thisIsPassArg)) {
+          (!Fortran::evaluate::IsObjectPointer(*expr) || thisIsPassArg) &&
+              !arg.testTKR(Fortran::common::IgnoreTKR::Contiguous)) {
         // Passing a non POINTER actual argument to a POINTER dummy argument.
         // Create a pointer of the dummy argument type and assign the actual
         // argument to it.
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

``````````

</details>


https://github.com/llvm/llvm-project/pull/176240


More information about the flang-commits mailing list