[flang-commits] [flang] 1e592bb - [Flang][OpenMP] Support nested derived types in DO CONCURRENT device conversion (#218963)

via flang-commits flang-commits at lists.llvm.org
Fri Sep 18 10:55:26 PDT 2026


Author: Arth Srivastava
Date: 2026-09-18T19:55:21+02:00
New Revision: 1e592bb44245a9ae44115fe78be1b07251dc3d9e

URL: https://github.com/llvm/llvm-project/commit/1e592bb44245a9ae44115fe78be1b07251dc3d9e
DIFF: https://github.com/llvm/llvm-project/commit/1e592bb44245a9ae44115fe78be1b07251dc3d9e.diff

LOG: [Flang][OpenMP] Support nested derived types in DO CONCURRENT device conversion (#218963)

Fixes #218760.

When converting `DO CONCURRENT` loops to OpenMP device targets, Flang
previously aborted whenever a live-in derived type contained a nested
derived-type component.

Nested derived types containing only non-allocatable components do not
require special handling: mapping the containing record covers the
storage of its nested components. When a nested component contains an
allocatable member, however, an implicit mapper is required to properly
map the dynamically allocated storage.

This patch removes the unconditional nested-record NYI and updates the
implicit mapper check to recurse through the Fortran element type of
derived-type components. In particular, this handles array-valued nested
derived-type components, which are lowered as `fir::SequenceType`.

Tests are added in
`flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90` to
verify plain record mapping, nested implicit mapper generation, and
chained mappers for array components. An AMDGPU offload test is also
added in
`offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90`
to verify device kernel execution and the computed results.

Reusing visible user-defined default declare mappers for derived-type
live-ins remains a separate pre-existing limitation in
`DoConcurrentConversion` and is not addressed by this patch.

---------

Co-authored-by: agozillon <Andrew.Gozillon at amd.com>

Added: 
    flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
    offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90

Modified: 
    flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp

Removed: 
    


################################################################################
diff  --git a/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp b/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
index 4dc6aa4750eaa..ee677a5d2c7f4 100644
--- a/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
+++ b/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
@@ -548,6 +548,23 @@ class DoConcurrentConversion
         /*dataExvIsAssumedSize=*/false, rawAddr.getLoc());
   }
 
+  static bool recordHasAllocatableMember(fir::RecordType recordType) {
+    for (auto [fieldName, fieldType] : recordType.getTypeList()) {
+      if (fir::isPointerType(fir::unwrapRefType(fieldType)))
+        continue;
+
+      if (fir::isAllocatableType(fieldType))
+        return true;
+
+      if (auto nestedRec = mlir::dyn_cast<fir::RecordType>(
+              fir::getFortranElementType(fieldType)))
+        if (recordHasAllocatableMember(nestedRec))
+          return true;
+    }
+
+    return false;
+  }
+
   mlir::omp::MapInfoOp
   genMapInfoOpForLiveIn(fir::FirOpBuilder &builder, mlir::Value liveIn,
                         bool isReductionVar = false) const {
@@ -596,34 +613,17 @@ class DoConcurrentConversion
     llvm::SmallVector<mlir::Value> boundsOps;
     genBoundsOps(builder, liveIn, rawAddr, boundsOps);
 
-    auto asRecordType = [&](mlir::Type eleType) {
-      return mlir::dyn_cast<fir::RecordType>(
-          fir::getDerivedType(fir::unwrapRefType(eleType)));
-    };
-
-    fir::RecordType recordType = asRecordType(eleType);
-
-    bool requiresImplcitMapper = [&]() {
-      if (!recordType)
-        return false;
-
-      for (auto [fieldName, fieldType] : recordType.getTypeList()) {
-        if (fir::isAllocatableType(fieldType))
-          return true;
+    fir::RecordType recordType = mlir::dyn_cast<fir::RecordType>(
+        fir::getDerivedType(fir::unwrapRefType(eleType)));
 
-        if (asRecordType(fieldType))
-          TODO(liveIn.getLoc(), "Nested record types are not supported yet.");
-      }
-
-      return false;
-    }();
+    bool requiresImplicitMapper =
+        recordType && recordHasAllocatableMember(recordType);
 
     mlir::FlatSymbolRefAttr mapperId;
-    if (requiresImplcitMapper) {
+    if (requiresImplicitMapper) {
       std::string mapperIdName =
           Fortran::utils::openmp::getCanonicalDefaultDeclareMapperName(
               recordType);
-      // TODO Add a mangler callback once nested record types are supported.
       mapperId = Fortran::utils::openmp::getOrGenImplicitDefaultDeclareMapper(
           builder, liveIn.getLoc(), recordType, mapperIdName);
     }

diff  --git a/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90 b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
new file mode 100644
index 0000000000000..3924e156e12c4
--- /dev/null
+++ b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
@@ -0,0 +1,72 @@
+! Regression test for https://github.com/llvm/llvm-project/issues/218760
+! The DO CONCURRENT -> OpenMP device conversion used to abort on arrays
+! whose element type contains a nested derived-type component. Verify that
+! a nested derived type with no allocatable members does not require an
+! implicit mapper and converts successfully, while a nested derived type
+! with allocatable members properly generates implicit mappers (including
+! when the nested component is an array of records).
+
+! RUN: %flang_fc1 -emit-hlfir -fopenmp -fdo-concurrent-to-openmp=device %s -o - \
+! RUN:   | FileCheck %s
+
+module nested_alloc_mod
+  implicit none
+  type :: inner_alloc_t
+    real, allocatable :: values(:)
+  end type
+
+  type :: outer_alloc_t
+    type(inner_alloc_t) :: inner(2)
+  end type
+end module nested_alloc_mod
+
+! CHECK: omp.declare_mapper @[[INNER_MAPPER:.*inner_alloc_t.*]] : !fir.type<{{.*}}inner_alloc_t{{.*}}>
+! CHECK: omp.declare_mapper @[[OUTER_MAPPER:.*outer_alloc_t.*]] : !fir.type<{{.*}}outer_alloc_t{{.*}}> {
+! CHECK:   omp.map.info {{.*}} mapper(@[[INNER_MAPPER]])
+
+subroutine nested_derived()
+  implicit none
+
+  type :: inner_t
+    integer :: x
+  end type
+
+  type :: outer_t
+    type(inner_t) :: member
+  end type
+
+  type(outer_t) :: a(4)
+  integer :: i
+
+  do concurrent (i = 1:4)
+    a(i)%member%x = i
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @{{.*}}nested_derived()
+! CHECK:   %[[ARR_A:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "{{.*}}a"}
+! CHECK:   omp.map.info var_ptr(%[[ARR_A]]#1 : {{.*}}) map_clauses(implicit, tofrom) capture(ByRef)
+! CHECK-NOT: mapper(
+! CHECK-SAME: name("{{.*}}a")
+! CHECK:   omp.target
+! CHECK:   omp.teams
+! CHECK:   omp.parallel
+! CHECK:   omp.distribute
+! CHECK:   omp.wsloop
+! CHECK:   omp.loop_nest
+
+subroutine nested_derived_alloc()
+  use nested_alloc_mod
+  implicit none
+
+  type(outer_alloc_t) :: a(4)
+  integer :: i
+
+  do concurrent (i = 1:4)
+    a(1)%inner(1)%values = [1.0, 2.0]
+  end do
+end subroutine
+
+! CHECK-LABEL: func.func @{{.*}}nested_derived_alloc()
+! CHECK:   %[[ARR_ALLOC:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "{{.*}}a"}
+! CHECK:   omp.map.info var_ptr(%[[ARR_ALLOC]]#1 : {{.*}}) map_clauses(implicit, tofrom) capture(ByRef) mapper(@[[OUTER_MAPPER]]) {{.*}} name("{{.*}}a")

diff  --git a/offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90 b/offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90
new file mode 100644
index 0000000000000..e8e91fd7ef07e
--- /dev/null
+++ b/offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90
@@ -0,0 +1,59 @@
+! Verifies that `do concurrent` correctly lowers map for basic nested derived
+! types, correctly mapping the required components of the derived type.
+! REQUIRES: flang, amdgpu
+
+! RUN: %libomptarget-compile-fortran-generic -fdo-concurrent-to-openmp=device
+! RUN: env LIBOMPTARGET_INFO=16 %libomptarget-run-generic 2>&1 | %fcheck-generic
+program main
+   implicit none
+
+   type :: alloc_buffer
+     integer :: i
+     real, allocatable :: data(:)
+   end type alloc_buffer
+
+   type :: buffer
+     integer :: i
+     real :: data(8)
+   end type buffer
+
+   type :: array_dt
+      type(buffer) :: buf
+   end type array_dt
+
+   type :: alloc_array_dt
+      type(alloc_buffer) :: buf(2)
+   end type alloc_array_dt
+
+   integer, parameter :: n = 8
+   integer :: i
+   type(alloc_array_dt) :: aad
+   type(array_dt) :: ad
+
+   allocate(aad%buf(1)%data(n), source=0.0)
+   allocate(aad%buf(2)%data(n), source=0.0)
+
+   do concurrent(i=1:n)
+      aad%buf(1)%data(i) = real(i)
+      aad%buf(2)%data(i) = real(i * 2)
+   end do
+
+   do concurrent(i=1:n)
+      ad%buf%data(i) = real(i)
+   end do
+
+   if (sum(ad%buf%data) == 36.0 .and. &
+       sum(aad%buf(1)%data) == 36.0 .and. &
+       sum(aad%buf(2)%data) == 72.0) then
+      print *, "PASS"
+   else
+      print *, "FAIL"
+   end if
+
+   deallocate(aad%buf(1)%data)
+   deallocate(aad%buf(2)%data)
+end program main
+
+! CHECK:  PluginInterface device {{[0-9]+}} info: Launching kernel {{.*}}
+! CHECK:  PluginInterface device {{[0-9]+}} info: Launching kernel {{.*}}
+! CHECK:  PASS


        


More information about the flang-commits mailing list