[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