Nvfortran 26.5: "use of undefined value" LLVM IR error with type-bound procedure call inside !$OMP PARALLEL DO

Hi,

I’m hitting an internal LLVM IR verification failure with nvfortran 26.5 when a type-bound procedure is called on a polymorphic dummy argument from inside an !$OMP PARALLEL DO region, writing into module-procedure SAVE allocatables.

The error is:

/path/to/nvhpc-26.5/Linux_x86_64/26.5/compilers/share/llvm/bin/opt: /tmp/nvfortranDZu2kxSBciuLt.ll:1835:31: error: use of undefined value '%.O0006'
        %106 = getelementptr i8, ptr %.O0006, i64 72, !dbg !633

Compile line:

nvfortran -g -traceback -Kieee -Mpreprocess -fast -O3 -fPIC -mp repro.F90

nvfortran --version:

nvfortran 26.5-0 64-bit target on x86-64 Linux -tp haswell

Platform:

bash-5.1$ uname -a
Linux xxx 6.18.33.2-microsoft-standard-WSL2 #1 SMP PREEMPT_DYNAMIC Thu Jun 18 21:54:43 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
bash-5.1$ cat /etc/redhat-release
AlmaLinux release 9.8 (Olive Jaguar)

The build succeeds if I remove -mp. It still fails without optimization, so this does not appear to be an optimizer-aggressiveness issue.

Reproducer attached / inline below:

module grid_mod
  implicit none
  private
  public :: base_grid, concrete_grid, other_grid

  type, abstract :: base_grid
     integer :: nx = 0, ny = 0
   contains
     procedure(field_pos_iface), deferred :: field_pos
  end type base_grid

  abstract interface
     function field_pos_iface(self, i, j) result(pos)
       import :: base_grid
       class(base_grid), intent(in) :: self
       integer, intent(in) :: i, j
       integer :: pos
     end function field_pos_iface
  end interface

  type, extends(base_grid) :: concrete_grid
   contains
     procedure :: field_pos => concrete_field_pos
  end type concrete_grid

  type, extends(base_grid) :: other_grid
   contains
     procedure :: field_pos => other_field_pos
  end type other_grid

contains

  function concrete_field_pos(self, i, j) result(pos)
    class(concrete_grid), intent(in) :: self
    integer, intent(in) :: i, j
    integer :: pos
    if (i < 1 .or. i > self%nx .or. j < 1 .or. j > self%ny) then
       pos = -1
    else
       pos = i + (j - 1) * self%nx
    end if
  end function concrete_field_pos

  function other_field_pos(self, i, j) result(pos)
    class(other_grid), intent(in) :: self
    integer, intent(in) :: i, j
    integer :: pos
    if (i < 1 .or. i > self%nx .or. j < 1 .or. j > self%ny) then
       pos = -1
    else
       pos = i + (j - 1) * self%ny
    end if
  end function other_field_pos

end module grid_mod


module interp_mod
  use grid_mod
  implicit none
  private
  public :: compute

contains

subroutine compute(grid_in, no)
    class(base_grid), intent(in) :: grid_in
    integer, intent(in) :: no

    integer :: n, i, j
    integer :: ijx(4), ijy(4)
    real :: xij, yij, xf, yf
    real :: wx(4), wy(4)

    ! saved allocatables in static storage — as in the real code
    integer, allocatable, save :: nxy(:,:,:), nc(:)
    real,    allocatable, save :: wxy(:,:,:)
    integer, save :: nox = -1
    ! saved POLYMORPHIC allocatable — the unusual bit
    class(base_grid), allocatable, save :: prev_grid_in

    logical :: same_gridi, to_station_points

    if (.not. allocated(prev_grid_in)) then
       allocate(prev_grid_in, source = grid_in)
       same_gridi = .false.
    else
       same_gridi = (prev_grid_in%nx == grid_in%nx)
       if (.not. same_gridi) then
          deallocate(prev_grid_in)
          allocate(prev_grid_in, source = grid_in)
       end if
    end if

    select type(grid_in)
    type is(other_grid)
       to_station_points = .true.
    class default
       to_station_points = .false.
    end select

    if (nox /= no) then
       if (nox >= 0) deallocate(nxy, wxy, nc)
       allocate(nxy(4,4,no), wxy(4,4,no), nc(no))
       nox = no
    end if

    if (.not. same_gridi .or. to_station_points) then
       !$OMP PARALLEL DO PRIVATE(N,XIJ,YIJ,IJX,IJY,XF,YF,J,I,WX,WY) SCHEDULE(STATIC)
       do n = 1, no
          xij = real(n)
          yij = real(n)
          ijx(1:4) = floor(xij - 1) + (/0,1,2,3/)
          ijy(1:4) = floor(yij - 1) + (/0,1,2,3/)
          xf = xij - ijx(2)
          yf = yij - ijy(2)
          do j = 1, 4
             do i = 1, 4
                nxy(i,j,n) = grid_in%field_pos(ijx(i), ijy(j))
             end do
          end do
          wx(1) = xf*(1-xf)*(2-xf)/(-6.)
          wx(2) = (xf+1)*(1-xf)*(2-xf)/2.
          wx(3) = (xf+1)*xf*(2-xf)/2.
          wx(4) = (xf+1)*xf*(1-xf)/(-6.)
          wy(1) = yf*(1-yf)*(2-yf)/(-6.)
          wy(2) = (yf+1)*(1-yf)*(2-yf)/2.
          wy(3) = (yf+1)*yf*(2-yf)/2.
          wy(4) = (yf+1)*yf*(1-yf)/(-6.)
          do j = 1, 4
             do i = 1, 4
                wxy(i,j,n) = wx(i)*wy(j)
             end do
          end do
       end do
       !$OMP END PARALLEL DO
    end if

    print *, sum(nxy), sum(wxy)
  end subroutine compute

end module interp_mod


program repro
  use grid_mod
  use interp_mod
  implicit none
  integer, parameter :: no = 100
  type(concrete_grid) :: g
  type(other_grid) :: o

  g%nx = 10
  g%ny = 10

  o%nx = 10
  o%ny = 10

  call compute(g, no)
  call compute(o, no)

end program repro

This was originally found building NCEPLIBS-ip ( GitHub - NOAA-EMC/NCEPLIBS-ip: Fortran 90 subprograms to be used for interpolating between nearly all grids used at NCEP. · GitHub ), specifically src/bicubic_interp_mod.F90, which uses this pattern — a class(ip_grid) dummy with a field_pos type-bound function called inside an OpenMP parallel loop. The same pattern appears in several sibling interpolation modules in that library, so it’s probably not an isolated occurrence. NCEPLIBS-ip is a NOAA production interpolation library used across US operational NWP software stacks.

Thanks,

Dom

–

Disclaimer: I used Claude Opus 5 to create the reproducer and create the bug report.

Hi Dom,

Thanks for the report! I was able to reproduce the issue here and filed a report, TPR #38828.

I know of one other reported issue with polymorphic types in OpenMP regions. While a bit different than yours, engineering decided to defer fixing it. The reason being that the same code works in our upcoming LLVM Flang based nvfortran which will eventually replace the current PGI based nvfortran.

Since your code also compiles fine with the new compiler, so they might defer yours as well. Not positive, but just want to cache expectations that you may need to wait for the new compiler until its fixed.

In the meantime, one potential work around is use a local copy of “grid_in”:

module grid_mod
  implicit none
  private
  public :: base_grid, concrete_grid, other_grid

  type, abstract :: base_grid
     integer :: nx = 0, ny = 0
   contains
     procedure(field_pos_iface), deferred :: field_pos
  end type base_grid

  abstract interface
     function field_pos_iface(self, i, j) result(pos)
       import :: base_grid
       class(base_grid), intent(in) :: self
       integer, intent(in) :: i, j
       integer :: pos
     end function field_pos_iface
  end interface

  type, extends(base_grid) :: concrete_grid
   contains
     procedure :: field_pos => concrete_field_pos
  end type concrete_grid

  type, extends(base_grid) :: other_grid
   contains
     procedure :: field_pos => other_field_pos
  end type other_grid

contains

  function concrete_field_pos(self, i, j) result(pos)
    class(concrete_grid), intent(in) :: self
    integer, intent(in) :: i, j
    integer :: pos
    if (i < 1 .or. i > self%nx .or. j < 1 .or. j > self%ny) then
       pos = -1
    else
       pos = i + (j - 1) * self%nx
    end if
  end function concrete_field_pos

  function other_field_pos(self, i, j) result(pos)
    class(other_grid), intent(in) :: self
    integer, intent(in) :: i, j
    integer :: pos
    if (i < 1 .or. i > self%nx .or. j < 1 .or. j > self%ny) then
       pos = -1
    else
       pos = i + (j - 1) * self%ny
    end if
  end function other_field_pos

end module grid_mod


module interp_mod
  use grid_mod
  implicit none
  private
  public :: compute

contains

subroutine compute(grid_in, no)
    class(base_grid), intent(in) :: grid_in
    integer, intent(in) :: no

    integer :: n, i, j
    integer :: ijx(4), ijy(4)
    real :: xij, yij, xf, yf
    real :: wx(4), wy(4)

    ! saved allocatables in static storage — as in the real code
    integer, allocatable, save :: nxy(:,:,:), nc(:)
    real,    allocatable, save :: wxy(:,:,:)
    integer, save :: nox = -1
    ! saved POLYMORPHIC allocatable — the unusual bit
    class(base_grid), allocatable, save :: prev_grid_in

    logical :: same_gridi, to_station_points

    class(base_grid), allocatable :: grid_local

    if (.not. allocated(prev_grid_in)) then
       allocate(prev_grid_in, source = grid_in)
       same_gridi = .false.
    else
       same_gridi = (prev_grid_in%nx == grid_in%nx)
       if (.not. same_gridi) then
          deallocate(prev_grid_in)
          allocate(prev_grid_in, source = grid_in)
       end if
    end if

    select type(grid_in)
    type is(other_grid)
       to_station_points = .true.
    class default
       to_station_points = .false.
    end select

    allocate(grid_local, source = grid_in)

    if (nox /= no) then
       if (nox >= 0) deallocate(nxy, wxy, nc)
       allocate(nxy(4,4,no), wxy(4,4,no), nc(no))
       nox = no
    end if

    if (.not. same_gridi .or. to_station_points) then
       !$OMP PARALLEL DO PRIVATE(N,XIJ,YIJ,IJX,IJY,XF,YF,J,I,WX,WY) SCHEDULE(STATIC)
       do n = 1, no
          xij = real(n)
          yij = real(n)
          ijx(1:4) = floor(xij - 1) + (/0,1,2,3/)
          ijy(1:4) = floor(yij - 1) + (/0,1,2,3/)
          xf = xij - ijx(2)
          yf = yij - ijy(2)
          do j = 1, 4
             do i = 1, 4
                nxy(i,j,n) = grid_local%field_pos(ijx(i), ijy(j))
             end do
          end do
          wx(1) = xf*(1-xf)*(2-xf)/(-6.)
          wx(2) = (xf+1)*(1-xf)*(2-xf)/2.
          wx(3) = (xf+1)*xf*(2-xf)/2.
          wx(4) = (xf+1)*xf*(1-xf)/(-6.)
          wy(1) = yf*(1-yf)*(2-yf)/(-6.)
          wy(2) = (yf+1)*(1-yf)*(2-yf)/2.
          wy(3) = (yf+1)*yf*(2-yf)/2.
          wy(4) = (yf+1)*yf*(1-yf)/(-6.)
          do j = 1, 4
             do i = 1, 4
                wxy(i,j,n) = wx(i)*wy(j)
             end do
          end do
       end do
       !$OMP END PARALLEL DO
    end if

    print *, sum(nxy), sum(wxy)
  end subroutine compute

end module interp_mod


program repro
  use grid_mod
  use interp_mod
  implicit none
  integer, parameter :: no = 100
  type(concrete_grid) :: g
  type(other_grid) :: o

  g%nx = 10
  g%ny = 10

  o%nx = 10
  o%ny = 10

  call compute(g, no)
  call compute(o, no)

end program repro

-Mat

Thanks very much for your quick response. We’ve been using flang-new from the LLVM project extensively. If this issue is fixed with the update to a flang-new version of nvfortran, then that’s fine.

Hi Dom,

Good news, engineering was able to fix the issue in the “classic” nvfortran 26.9 release.

-Mat