[flang-commits] [flang] [llvm] [Flang][OpenMP] Preserve absent OPTIONAL arguments in target mappings (PR #227343)

Akash Banerjee via flang-commits flang-commits at lists.llvm.org
Tue Sep 29 12:53:35 PDT 2026


https://github.com/TIFitis updated https://github.com/llvm/llvm-project/pull/227343

>From 0a49bffe8bd3868f155adcb2b9135240017449a0 Mon Sep 17 00:00:00 2001
From: Akash Banerjee <Akash.Banerjee at amd.com>
Date: Tue, 29 Sep 2026 16:08:19 +0100
Subject: [PATCH 1/2] [Flang][OpenMP] Preserve absent OPTIONAL arguments in
 target mappings

Generate empty mapping bounds for absent optional scalars and explicit-shape arrays, preserving null pointers and preventing device allocation or copies from null.

Co-authored-by: Codex <codex at openai.com>
---
 .../Optimizer/OpenMP/MapInfoFinalization.cpp  |  60 ++++++
 .../Lower/OpenMP/optional-argument-map-4.f90  |  94 +++++++++
 .../fortran/optional-mapped-arguments-4.f90   | 186 ++++++++++++++++++
 3 files changed, 340 insertions(+)
 create mode 100644 flang/test/Lower/OpenMP/optional-argument-map-4.f90
 create mode 100644 offload/test/offloading/fortran/optional-mapped-arguments-4.f90

diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index dfbf014f95b43f..c0aef1934ef456 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -400,6 +400,64 @@ class MapInfoFinalizationPass
                     });
   }
 
+  /// An absent optional passed by reference must retain its null address and
+  /// must not cause an allocation or transfer. Descriptor maps handle this when
+  /// expanding the descriptor. For non-descriptor arguments, express absence
+  /// as empty bounds, including a single-element bound for scalar arguments.
+  void genOptionalBounds(mlir::omp::MapInfoOp map, fir::FirOpBuilder &builder) {
+    if (map.getVarPtrPtr() || !map.getMembers().empty() ||
+        map.getMapCaptureType() != mlir::omp::VariableCaptureKind::ByRef ||
+        !fir::factory::isOptionalArgument(map.getVarPtr().getDefiningOp()))
+      return;
+
+    // Array bounds are supplied by lowering. A synthetic scalar bound would
+    // otherwise map only one element of a bounds-free array mapping.
+    if (map.getBounds().empty() &&
+        mlir::isa<fir::SequenceType>(
+            fir::unwrapRefType(map.getVarPtr().getType())))
+      return;
+
+    mlir::OpBuilder::InsertionGuard guard(builder);
+    builder.setInsertionPoint(map);
+    mlir::Location loc = map.getLoc();
+    mlir::Value present = fir::IsPresentOp::create(
+        builder, loc, builder.getI1Type(), map.getVarPtr());
+    auto selectIfPresent = [&](mlir::Value value,
+                               int64_t absent) -> mlir::Value {
+      if (!value)
+        return {};
+      mlir::Value absentValue =
+          builder.createIntegerConstant(loc, value.getType(), absent);
+      return builder.createOrFold<mlir::arith::SelectOp>(loc, present, value,
+                                                         absentValue);
+    };
+
+    llvm::SmallVector<mlir::Value> bounds;
+    if (map.getBounds().empty()) {
+      mlir::Value zero =
+          builder.createIntegerConstant(loc, builder.getIndexType(), 0);
+      mlir::Value one =
+          builder.createIntegerConstant(loc, builder.getIndexType(), 1);
+      bounds.push_back(mlir::omp::MapBoundsOp::create(
+          builder, loc, builder.getType<mlir::omp::MapBoundsType>(), zero,
+          selectIfPresent(zero, -1), selectIfPresent(one, 0), one,
+          /*stride_in_bytes=*/false, one));
+    } else {
+      for (mlir::Value value : map.getBounds()) {
+        auto bound = value.getDefiningOp<mlir::omp::MapBoundsOp>();
+        // Also clear section offsets so that an absent argument's null base
+        // address is not adjusted when computing the mapped address.
+        bounds.push_back(mlir::omp::MapBoundsOp::create(
+            builder, loc, bound.getType(),
+            selectIfPresent(bound.getLowerBound(), 0),
+            selectIfPresent(bound.getUpperBound(), -1),
+            selectIfPresent(bound.getExtent(), 0), bound.getStride(),
+            bound.getStrideInBytes(), bound.getStartIdx()));
+      }
+    }
+    map.getBoundsMutable().assign(bounds);
+  }
+
   /// When provided a MapInfoOp containing a descriptor type that
   /// we must expand into multiple maps this function will extract
   /// the value from it and return it, in certain cases we must
@@ -1728,6 +1786,8 @@ class MapInfoFinalizationPass
             genOptimizedUseDeviceAddr(builder, targetDataOp, newMapInfo,
                                       module);
           }
+        } else {
+          genOptionalBounds(op, builder);
         }
       });
 
diff --git a/flang/test/Lower/OpenMP/optional-argument-map-4.f90 b/flang/test/Lower/OpenMP/optional-argument-map-4.f90
new file mode 100644
index 00000000000000..f4a7214efc3f73
--- /dev/null
+++ b/flang/test/Lower/OpenMP/optional-argument-map-4.f90
@@ -0,0 +1,94 @@
+! RUN: %flang_fc1 -emit-hlfir -fopenmp %s -o - | FileCheck %s
+
+! Non-descriptor optional arguments need empty mapping bounds when absent,
+! while retaining their null base address for PRESENT inside the target.
+
+subroutine scalar(x, found)
+  real, optional :: x
+  logical :: found
+  !$omp target map(alloc:x) map(from:found)
+    found = present(x)
+  !$omp end target
+end subroutine
+
+! CHECK-LABEL: func.func @_QPscalar(
+! CHECK: %[[X:[^ ,:]+]]:2 = hlfir.declare {{.*}}fortran_attrs = #fir.var_attrs<optional>
+! CHECK: %[[PRESENT:[^ ,:]+]] = fir.is_present %[[X]]#1
+! CHECK: %[[ZERO:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[ONE:[^ ,:]+]] = arith.constant 1 : index
+! CHECK: %[[NEGONE:[^ ,:]+]] = arith.constant -1 : index
+! CHECK: %[[UB:[^ ,:]+]] = arith.select %[[PRESENT]], %[[ZERO]], %[[NEGONE]] : index
+! CHECK: %[[ABSENT_EXTENT:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[EXTENT:[^ ,:]+]] = arith.select %[[PRESENT]], %[[ONE]], %[[ABSENT_EXTENT]] : index
+! CHECK: %[[BOUNDS:[^ ,:]+]] = omp.map.bounds lower_bound(%[[ZERO]] : index) upper_bound(%[[UB]] : index) extent(%[[EXTENT]] : index)
+! CHECK: %[[MAP:[^ ,:]+]] = omp.map.info var_ptr(%[[X]]#1 : !fir.ref<f32>, f32) map_clauses(storage) capture(ByRef) bounds(%[[BOUNDS]])
+! CHECK: omp.target {{.*}}map_entries(%[[MAP]] -> %[[ARG:[^ ,:]+]],
+! CHECK: %[[DEVICE_X:[^ ,:]+]]:2 = hlfir.declare %[[ARG]] {fortran_attrs = #fir.var_attrs<optional>
+! CHECK: fir.is_present %[[DEVICE_X]]#0
+
+subroutine array(n, x, found)
+  integer :: n
+  real, optional :: x(n, 3)
+  logical :: found
+  !$omp target map(from:found)
+    found = present(x)
+  !$omp end target
+end subroutine
+
+! CHECK-LABEL: func.func @_QParray(
+! CHECK: %[[X:[^ ,:]+]]:2 = hlfir.declare {{.*}}fortran_attrs = #fir.var_attrs<optional>
+! CHECK: omp.map.bounds lower_bound(%[[LB0:[^ ,:]+]] : index) upper_bound(%[[UB0:[^ ,:]+]] : index) extent(%[[EXT0:[^ ,:]+]] : index)
+! CHECK: omp.map.bounds lower_bound(%[[LB1:[^ ,:]+]] : index) upper_bound(%[[UB1:[^ ,:]+]] : index) extent(%[[EXT1:[^ ,:]+]] : index)
+! CHECK: %[[PRESENT:[^ ,:]+]] = fir.is_present %[[X]]#1
+! CHECK: %[[ZERO0:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[NEW_LB0:[^ ,:]+]] = arith.select %[[PRESENT]], %[[LB0]], %[[ZERO0]] : index
+! CHECK: %[[NEGONE0:[^ ,:]+]] = arith.constant -1 : index
+! CHECK: %[[NEW_UB0:[^ ,:]+]] = arith.select %[[PRESENT]], %[[UB0]], %[[NEGONE0]] : index
+! CHECK: %[[ZERO_EXT0:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[NEW_EXT0:[^ ,:]+]] = arith.select %[[PRESENT]], %[[EXT0]], %[[ZERO_EXT0]] : index
+! CHECK: %[[BOUNDS0:[^ ,:]+]] = omp.map.bounds lower_bound(%[[NEW_LB0]] : index) upper_bound(%[[NEW_UB0]] : index) extent(%[[NEW_EXT0]] : index)
+! CHECK: %[[ZERO1:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[NEW_LB1:[^ ,:]+]] = arith.select %[[PRESENT]], %[[LB1]], %[[ZERO1]] : index
+! CHECK: %[[NEGONE1:[^ ,:]+]] = arith.constant -1 : index
+! CHECK: %[[NEW_UB1:[^ ,:]+]] = arith.select %[[PRESENT]], %[[UB1]], %[[NEGONE1]] : index
+! CHECK: %[[ZERO_EXT1:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[NEW_EXT1:[^ ,:]+]] = arith.select %[[PRESENT]], %[[EXT1]], %[[ZERO_EXT1]] : index
+! CHECK: %[[BOUNDS1:[^ ,:]+]] = omp.map.bounds lower_bound(%[[NEW_LB1]] : index) upper_bound(%[[NEW_UB1]] : index) extent(%[[NEW_EXT1]] : index)
+! CHECK: omp.map.info var_ptr(%[[X]]#1 : !fir.ref<!fir.array<?x3xf32>>, f32) map_clauses(implicit, tofrom) capture(ByRef) bounds(%[[BOUNDS0]], %[[BOUNDS1]])
+
+! A section must also have zero offsets when absent, so that its null base
+! address is not incremented when computing the start of the mapped data.
+subroutine section(n, x, found)
+  integer :: n
+  real, optional :: x(n)
+  logical :: found
+  !$omp target map(tofrom:x(2:n)) map(from:found)
+    found = present(x)
+  !$omp end target
+end subroutine
+
+! CHECK-LABEL: func.func @_QPsection(
+! CHECK: %[[X:[^ ,:]+]]:2 = hlfir.declare {{.*}}fortran_attrs = #fir.var_attrs<optional>
+! CHECK: omp.map.bounds lower_bound(%[[LB:[^ ,:]+]] : index) upper_bound(%[[UB:[^ ,:]+]] : index) extent(%[[EXT:[^ ,:]+]] : index)
+! CHECK: %[[PRESENT:[^ ,:]+]] = fir.is_present %[[X]]#1
+! CHECK: %[[ZERO:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[NEW_LB:[^ ,:]+]] = arith.select %[[PRESENT]], %[[LB]], %[[ZERO]] : index
+! CHECK: %[[NEGONE:[^ ,:]+]] = arith.constant -1 : index
+! CHECK: %[[NEW_UB:[^ ,:]+]] = arith.select %[[PRESENT]], %[[UB]], %[[NEGONE]] : index
+! CHECK: %[[ZERO_EXT:[^ ,:]+]] = arith.constant 0 : index
+! CHECK: %[[NEW_EXT:[^ ,:]+]] = arith.select %[[PRESENT]], %[[EXT]], %[[ZERO_EXT]] : index
+! CHECK: %[[BOUNDS:[^ ,:]+]] = omp.map.bounds lower_bound(%[[NEW_LB]] : index) upper_bound(%[[NEW_UB]] : index) extent(%[[NEW_EXT]] : index)
+! CHECK: omp.map.info var_ptr(%[[X]]#1 : !fir.ref<!fir.array<?xf32>>, f32) map_clauses(tofrom) capture(ByRef) bounds(%[[BOUNDS]])
+
+! Nonoptional scalar mappings do not need presence checks or bounds.
+subroutine nonoptional(x)
+  real :: x
+  !$omp target map(tofrom:x)
+    x = x + 1
+  !$omp end target
+end subroutine
+
+! CHECK-LABEL: func.func @_QPnonoptional(
+! CHECK-NOT: fir.is_present
+! CHECK-NOT: omp.map.bounds
+! CHECK: omp.target
diff --git a/offload/test/offloading/fortran/optional-mapped-arguments-4.f90 b/offload/test/offloading/fortran/optional-mapped-arguments-4.f90
new file mode 100644
index 00000000000000..edd942dc0ae931
--- /dev/null
+++ b/offload/test/offloading/fortran/optional-mapped-arguments-4.f90
@@ -0,0 +1,186 @@
+! Absent optional arguments represented by plain references must retain their
+! absence on the device and must not be allocated or copied by their mappings.
+! REQUIRES: flang, amdgpu
+! RUN: %libomptarget-compile-fortran-generic
+! RUN: env OMP_TARGET_OFFLOAD=MANDATORY %libomptarget-run-generic 2>&1 | %fcheck-generic
+! RUN: env OMP_TARGET_OFFLOAD=DISABLED %libomptarget-run-generic 2>&1 | %fcheck-generic
+
+module optional_target_args
+contains
+  subroutine scalar_alloc(expected, x)
+    logical, intent(in) :: expected
+    integer, optional :: x
+    logical :: found
+    integer :: value, visits
+
+    visits = 0
+    !$omp target map(alloc:x) map(from:found,value) map(tofrom:visits)
+      visits = visits + 1
+      found = present(x)
+      value = -1
+      if (present(x)) then
+        ! MAP(ALLOC:) does not initialize device storage.
+        x = 73
+        value = x
+      endif
+    !$omp end target
+    if (visits /= 1 .or. (found .neqv. expected)) stop 1
+    if (value /= merge(73, -1, expected)) stop 2
+  end subroutine
+
+  subroutine scalar_copy(expected, x)
+    logical, intent(in) :: expected
+    integer, optional :: x
+    logical :: found
+    integer :: value, visits
+
+    if (present(x)) x = 42
+    visits = 0
+    !$omp target map(tofrom:x) map(from:found,value) map(tofrom:visits)
+      visits = visits + 1
+      found = present(x)
+      value = -1
+      if (present(x)) then
+        value = x
+        x = x + 1
+      endif
+    !$omp end target
+    if (visits /= 1 .or. (found .neqv. expected)) stop 3
+    if (value /= merge(42, -1, expected)) stop 4
+    if (present(x)) then
+      if (x /= 43) stop 5
+    endif
+  end subroutine
+
+  subroutine fixed_array(expected, x)
+    logical, intent(in) :: expected
+    integer, optional :: x(8)
+    logical :: found
+    integer :: value, visits
+
+    if (present(x)) x = 42
+    visits = 0
+    !$omp target map(from:found,value) map(tofrom:visits)
+      visits = visits + 1
+      found = present(x)
+      value = -1
+      if (present(x)) then
+        value = x(8)
+        x(8) = x(8) + 1
+      endif
+    !$omp end target
+    if (visits /= 1 .or. (found .neqv. expected)) stop 6
+    if (value /= merge(42, -1, expected)) stop 7
+    if (present(x)) then
+      if (x(8) /= 43 .or. any(x(1:7) /= 42)) stop 8
+    endif
+  end subroutine
+
+  subroutine dynamic_array(n, expected, x)
+    integer, intent(in) :: n
+    logical, intent(in) :: expected
+    integer, optional :: x(n)
+    logical :: found
+    integer :: value, visits
+
+    if (present(x)) x = 42
+    visits = 0
+    !$omp target map(from:found,value) map(tofrom:visits)
+      visits = visits + 1
+      found = present(x)
+      value = -1
+      if (present(x)) then
+        value = x(n)
+        x(n) = x(n) + 1
+      endif
+    !$omp end target
+    if (visits /= 1 .or. (found .neqv. expected)) stop 9
+    if (value /= merge(42, -1, expected)) stop 10
+    if (present(x)) then
+      if (x(n) /= 43 .or. any(x(1:n-1) /= 42)) stop 11
+    endif
+  end subroutine
+
+  subroutine array_alloc(n, expected, x)
+    integer, intent(in) :: n
+    logical, intent(in) :: expected
+    integer, optional :: x(n)
+    logical :: found
+    integer :: value, visits
+
+    visits = 0
+    !$omp target map(alloc:x) map(from:found,value) map(tofrom:visits)
+      visits = visits + 1
+      found = present(x)
+      value = -1
+      if (present(x)) then
+        x(n) = 73
+        value = x(n)
+      endif
+    !$omp end target
+    if (visits /= 1 .or. (found .neqv. expected)) stop 12
+    if (value /= merge(73, -1, expected)) stop 13
+  end subroutine
+
+  subroutine array_section(n, m, expected, x)
+    integer, intent(in) :: n, m
+    logical, intent(in) :: expected
+    integer, optional :: x(n, m)
+    logical :: found
+    integer :: value, visits
+
+    if (present(x)) x = 42
+    visits = 0
+    !$omp target map(tofrom:x(1:n,2:3),visits) map(from:found,value)
+      visits = visits + 1
+      found = present(x)
+      value = -1
+      if (present(x)) then
+        value = x(1,2) + x(n,3)
+        x(1,2) = 43
+        x(n,3) = 73
+      endif
+    !$omp end target
+    if (visits /= 1 .or. (found .neqv. expected)) stop 14
+    if (value /= merge(84, -1, expected)) stop 15
+    if (present(x)) then
+      if (x(1,2) /= 43 .or. x(n,3) /= 73) stop 16
+      if (x(1,1) /= 42 .or. x(n,m) /= 42 .or. x(2,2) /= 42) stop 17
+    endif
+  end subroutine
+
+  subroutine forward(n, expected, scalar, array, matrix)
+    integer, intent(in) :: n
+    logical, intent(in) :: expected
+    integer, optional :: scalar, array(n), matrix(5,4)
+    call scalar_alloc(expected, scalar)
+    call scalar_copy(expected, scalar)
+    call fixed_array(expected, array)
+    call dynamic_array(n, expected, array)
+    call array_alloc(n, expected, array)
+    call array_section(5, 4, expected, matrix)
+  end subroutine
+end module
+
+program main
+  use optional_target_args
+  integer :: scalar, array(8), matrix(5,4)
+
+  call scalar_alloc(.false.)
+  call scalar_alloc(.true., scalar)
+  call scalar_copy(.false.)
+  call scalar_copy(.true., scalar)
+  call fixed_array(.false.)
+  call fixed_array(.true., array)
+  call dynamic_array(8, .false.)
+  call dynamic_array(8, .true., array)
+  call array_alloc(8, .false.)
+  call array_alloc(8, .true., array)
+  call array_section(5, 4, .false.)
+  call array_section(5, 4, .true., matrix)
+  call forward(8, .false.)
+  call forward(8, .true., scalar, array, matrix)
+  print *, "PASS"
+end program
+
+! CHECK: PASS

>From fc5f49bcc8ace2751e7976b661d1da0ed5e2ae62 Mon Sep 17 00:00:00 2001
From: Akash Banerjee <Akash.Banerjee at amd.com>
Date: Tue, 29 Sep 2026 20:53:05 +0100
Subject: [PATCH 2/2] Fix build failure.

---
 flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
index c0aef1934ef456..8bd9302a7aa9e5 100644
--- a/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
+++ b/flang/lib/Optimizer/OpenMP/MapInfoFinalization.cpp
@@ -438,21 +438,23 @@ class MapInfoFinalizationPass
           builder.createIntegerConstant(loc, builder.getIndexType(), 0);
       mlir::Value one =
           builder.createIntegerConstant(loc, builder.getIndexType(), 1);
+      mlir::Value upperBound = selectIfPresent(zero, -1);
+      mlir::Value extent = selectIfPresent(one, 0);
       bounds.push_back(mlir::omp::MapBoundsOp::create(
           builder, loc, builder.getType<mlir::omp::MapBoundsType>(), zero,
-          selectIfPresent(zero, -1), selectIfPresent(one, 0), one,
+          upperBound, extent, one,
           /*stride_in_bytes=*/false, one));
     } else {
       for (mlir::Value value : map.getBounds()) {
         auto bound = value.getDefiningOp<mlir::omp::MapBoundsOp>();
         // Also clear section offsets so that an absent argument's null base
         // address is not adjusted when computing the mapped address.
+        mlir::Value lowerBound = selectIfPresent(bound.getLowerBound(), 0);
+        mlir::Value upperBound = selectIfPresent(bound.getUpperBound(), -1);
+        mlir::Value extent = selectIfPresent(bound.getExtent(), 0);
         bounds.push_back(mlir::omp::MapBoundsOp::create(
-            builder, loc, bound.getType(),
-            selectIfPresent(bound.getLowerBound(), 0),
-            selectIfPresent(bound.getUpperBound(), -1),
-            selectIfPresent(bound.getExtent(), 0), bound.getStride(),
-            bound.getStrideInBytes(), bound.getStartIdx()));
+            builder, loc, bound.getType(), lowerBound, upperBound, extent,
+            bound.getStride(), bound.getStrideInBytes(), bound.getStartIdx()));
       }
     }
     map.getBoundsMutable().assign(bounds);



More information about the flang-commits mailing list