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

Arth Srivastava via flang-commits flang-commits at lists.llvm.org
Tue Sep 8 14:00:00 PDT 2026


https://github.com/Blazearth updated https://github.com/llvm/llvm-project/pull/218963

>From 559c14d73804dedd48e480114ab170b8ba6d70a1 Mon Sep 17 00:00:00 2001
From: Arth Srivastava <arthsrivastava1 at gmail.com>
Date: Wed, 26 Aug 2026 21:03:00 +0530
Subject: [PATCH 1/5] [Flang][OpenMP] Support DO CONCURRENT device conversion
 for nested derived types

In DoConcurrentConversion, avoid aborting when a live-in derived type
contains nested derived-type components. Use fir::isRecordWithAllocatableMember
to determine if implicit default declare mappers are required, and safely
handle absent mangler callbacks during recursive mapper generation.

Fixes #218760
---
 .../OpenMP/DoConcurrentConversion.cpp         | 25 ++-----
 .../nested_derived_type_device.f90            | 69 +++++++++++++++++++
 2 files changed, 73 insertions(+), 21 deletions(-)
 create mode 100644 flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90

diff --git a/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp b/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
index 4dc6aa4750eaa..091dfb44a1b07 100644
--- a/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
+++ b/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
@@ -596,34 +596,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 requiresImplcitMapper =
+        recordType && fir::isRecordWithAllocatableMember(recordType);
 
     mlir::FlatSymbolRefAttr mapperId;
     if (requiresImplcitMapper) {
       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..d53d8de7a6304
--- /dev/null
+++ b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
@@ -0,0 +1,69 @@
+! 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.
+
+! 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
+  end type
+end module nested_alloc_mod
+
+! CHECK-DAG: omp.declare_mapper @[[INNER_MAPPER:.*inner_alloc_t.*]] : !fir.type<{{.*}}inner_alloc_t{{.*}}>
+! CHECK-DAG: omp.declare_mapper @[[OUTER_MAPPER:.*outer_alloc_t.*]] : !fir.type<{{.*}}outer_alloc_t{{.*}}>
+
+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) {{.*}} 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%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")
+

>From e6c3751336cf1239aa76c2c9112f0eba5e05035e Mon Sep 17 00:00:00 2001
From: Arth Srivastava <arthsrivastava1 at gmail.com>
Date: Fri, 4 Sep 2026 02:15:44 +0530
Subject: [PATCH 2/5] [Flang][OpenMP] Support nested derived type array
 components in DO CONCURRENT

In DoConcurrentConversion, inspect Fortran element types when checking for
allocatable members in derived types, so that records containing array
components of derived types with allocatable members properly generate
implicit declare mappers.

Update nested_derived_type_device.f90 to verify the array component case
and mapper chaining.
---
 .../OpenMP/DoConcurrentConversion.cpp         | 23 ++++++++++++++++---
 .../nested_derived_type_device.f90            | 14 ++++++-----
 2 files changed, 28 insertions(+), 9 deletions(-)

diff --git a/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp b/flang/lib/Optimizer/OpenMP/DoConcurrentConversion.cpp
index 091dfb44a1b07..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 {
@@ -599,11 +616,11 @@ class DoConcurrentConversion
     fir::RecordType recordType = mlir::dyn_cast<fir::RecordType>(
         fir::getDerivedType(fir::unwrapRefType(eleType)));
 
-    bool requiresImplcitMapper =
-        recordType && fir::isRecordWithAllocatableMember(recordType);
+    bool requiresImplicitMapper =
+        recordType && recordHasAllocatableMember(recordType);
 
     mlir::FlatSymbolRefAttr mapperId;
-    if (requiresImplcitMapper) {
+    if (requiresImplicitMapper) {
       std::string mapperIdName =
           Fortran::utils::openmp::getCanonicalDefaultDeclareMapperName(
               recordType);
diff --git a/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90 b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
index d53d8de7a6304..c8f96b3c11acd 100644
--- a/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
+++ b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
@@ -3,7 +3,8 @@
 ! 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.
+! 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
@@ -15,12 +16,13 @@ module nested_alloc_mod
   end type
 
   type :: outer_alloc_t
-    type(inner_alloc_t) :: inner
+    type(inner_alloc_t) :: inner(2)
   end type
 end module nested_alloc_mod
 
-! CHECK-DAG: omp.declare_mapper @[[INNER_MAPPER:.*inner_alloc_t.*]] : !fir.type<{{.*}}inner_alloc_t{{.*}}>
-! CHECK-DAG: omp.declare_mapper @[[OUTER_MAPPER:.*outer_alloc_t.*]] : !fir.type<{{.*}}outer_alloc_t{{.*}}>
+! 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
@@ -43,6 +45,7 @@ subroutine nested_derived()
 
 ! CHECK-LABEL: func.func @{{.*}}nested_derived()
 ! CHECK:   %[[ARR_A:.*]]:2 = hlfir.declare {{.*}} {uniq_name = "{{.*}}a"}
+! CHECK-NOT: mapper(
 ! CHECK:   omp.map.info var_ptr(%[[ARR_A]]#1 : {{.*}}) map_clauses(implicit, tofrom) capture(ByRef) {{.*}} name("{{.*}}a")
 ! CHECK:   omp.target
 ! CHECK:   omp.teams
@@ -59,11 +62,10 @@ subroutine nested_derived_alloc()
   integer :: i
 
   do concurrent (i = 1:4)
-    a(1)%inner%values = [1.0, 2.0]
+    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")
-

>From 4395aa73662386f3ac0fda5534237618867c29fd Mon Sep 17 00:00:00 2001
From: Arth Srivastava <arthsrivastava1 at gmail.com>
Date: Fri, 4 Sep 2026 04:09:41 +0530
Subject: [PATCH 3/5] [Flang][OpenMP] Add offload test for nested derived types
 in do concurrent

Add offload test case covering basic and allocatable-containing nested
derived types in do concurrent mapping, adapted from PR #220099.

Co-authored-by: agozillon <Andrew.Gozillon at amd.com>
---
 ...-concurrent-to-omp-nested-derived-type.f90 | 50 +++++++++++++++++++
 1 file changed, 50 insertions(+)
 create mode 100644 offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90

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..886c68f7e89bd
--- /dev/null
+++ b/offload/test/offloading/fortran/do-concurrent-to-omp-nested-derived-type.f90
@@ -0,0 +1,50 @@
+! 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: %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
+   end type alloc_array_dt
+
+   integer, parameter :: n = 8
+   integer :: i
+   type(alloc_array_dt) :: aad
+   type(array_dt) :: ad
+
+   allocate(aad%buf%data(n), source=0.0)
+
+   do concurrent(i=1:n)
+      aad%buf%data(i) = real(i)
+   end do
+
+   do concurrent(i=1:n)
+      ad%buf%data(i) = real(i)
+   end do
+
+   print *, sum(ad%buf%data)
+   print *, sum(aad%buf%data)
+
+   deallocate(aad%buf%data)
+end program main
+
+! CHECK: 36.
+! CHECK: 36.

>From 2c078b69e4395369cf6bf1bee4c1ca7a06920e2c Mon Sep 17 00:00:00 2001
From: Arth Srivastava <arthsrivastava1 at gmail.com>
Date: Sat, 5 Sep 2026 04:52:47 +0530
Subject: [PATCH 4/5] [Flang][OpenMP] Harden DO CONCURRENT nested derived-type
 offload test

Set LIBOMPTARGET_INFO=16 and assert kernel launches for both target
regions to prevent silent host fallback under OMP_TARGET_OFFLOAD=DISABLED.
Use an array component type(alloc_buffer) :: buf(2) to actively exercise
the fir::SequenceType traversal path, and check exact sum equality with a
single PASS marker.
---
 ...-concurrent-to-omp-nested-derived-type.f90 | 27 ++++++++++++-------
 1 file changed, 18 insertions(+), 9 deletions(-)

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
index 886c68f7e89bd..e8e91fd7ef07e 100644
--- 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
@@ -3,7 +3,7 @@
 ! REQUIRES: flang, amdgpu
 
 ! RUN: %libomptarget-compile-fortran-generic -fdo-concurrent-to-openmp=device
-! RUN: %libomptarget-run-generic 2>&1 | %fcheck-generic
+! RUN: env LIBOMPTARGET_INFO=16 %libomptarget-run-generic 2>&1 | %fcheck-generic
 program main
    implicit none
 
@@ -22,7 +22,7 @@ program main
    end type array_dt
 
    type :: alloc_array_dt
-      type(alloc_buffer) :: buf
+      type(alloc_buffer) :: buf(2)
    end type alloc_array_dt
 
    integer, parameter :: n = 8
@@ -30,21 +30,30 @@ program main
    type(alloc_array_dt) :: aad
    type(array_dt) :: ad
 
-   allocate(aad%buf%data(n), source=0.0)
+   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%data(i) = real(i)
+      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
 
-   print *, sum(ad%buf%data)
-   print *, sum(aad%buf%data)
+   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%data)
+   deallocate(aad%buf(1)%data)
+   deallocate(aad%buf(2)%data)
 end program main
 
-! CHECK: 36.
-! CHECK: 36.
+! CHECK:  PluginInterface device {{[0-9]+}} info: Launching kernel {{.*}}
+! CHECK:  PluginInterface device {{[0-9]+}} info: Launching kernel {{.*}}
+! CHECK:  PASS

>From 57997217eef129f918dbc00403d30766e0bf21c9 Mon Sep 17 00:00:00 2001
From: Arth Srivastava <arthsrivastava1 at gmail.com>
Date: Wed, 9 Sep 2026 02:29:39 +0530
Subject: [PATCH 5/5] [Flang][OpenMP] Tighten FileCheck mapper exclusions in DO
 CONCURRENT test

---
 .../Transforms/DoConcurrent/nested_derived_type_device.f90     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90 b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
index c8f96b3c11acd..3924e156e12c4 100644
--- a/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
+++ b/flang/test/Transforms/DoConcurrent/nested_derived_type_device.f90
@@ -45,8 +45,9 @@ subroutine nested_derived()
 
 ! 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:   omp.map.info var_ptr(%[[ARR_A]]#1 : {{.*}}) map_clauses(implicit, tofrom) capture(ByRef) {{.*}} name("{{.*}}a")
+! CHECK-SAME: name("{{.*}}a")
 ! CHECK:   omp.target
 ! CHECK:   omp.teams
 ! CHECK:   omp.parallel



More information about the flang-commits mailing list