Isn’t it feasible to just devirtualize (restore) the concrete type for the paths you need? In your original snippet calculate_density_1d
isn’t polymorphic.
For instance like this: Compiler Explorer (nvfortran is now also available in CE!)
module EOS_base_mod
implicit none
type, abstract :: EOS_base
contains
procedure :: calc_density_array
end type EOS_base
contains
subroutine calc_density_array(this)
class(EOS_base), intent(in) :: this
end subroutine calc_density_array
end module EOS_base_mod
module EOS_child_mod
use EOS_base_mod, only: EOS_base
implicit none
type, extends(EOS_base) :: EOS_child
contains
procedure :: calc_density_array => calc_density_array_host
end type
contains
! Default host procedure
subroutine calc_density_array_host(this)
class(EOS_child), intent(in) :: this
end subroutine
! An ACC version is available too
subroutine calc_density_array_acc(this)
!$acc routine
type(EOS_child), intent(in) :: this
end subroutine
end module
module EOS
use EOS_base_mod, only : EOS_base
implicit none
type :: EOS_type
class(EOS_base), allocatable :: type
end type
logical, parameter :: use_acc = .true.
contains
subroutine calculate_density_1d(EOS)
use EOS_child_mod, only: EOS_child, calc_density_array_acc
type(EOS_type), intent(in) :: EOS
select type (tmp => EOS%type)
type is (EOS_child)
if (use_acc) then
call calc_density_array_acc(tmp)
else
call tmp%calc_density_array()
end if
class default
call tmp%calc_density_array
end select
end subroutine calculate_density_1d
end module EOS
Depending on what you are trying to achieve, it may make more sense to devirtualize in the calling scope:
program EOS_main
use EOS, only: EOS_type, calculate_density_1d, use_acc
use EOS_child_mod, only: EOS_child, calc_density_array_gpu
implicit none
type(EOS_type) :: top
! top%type could be set by a factory function
allocate(EOS_child :: top%type)
select type(type => top%type)
type is (EOS_child)
! We know EOS_child has an OpenACC variant
if (use_acc) then
!$acc kernels
call calc_density_array_gpu(type)
!$acc end kernels
else
! Normal polymorphic path
call calculate_density_1d(top)
end if
class default
! Normal polymorphic path
call calculate_density_1d(top)
end select
print *, "done!"
end program
Perhaps even more natural would be to provide an extended child type which allows runtime selection of OpenACC:
type, extends(EOS_base) :: EOS_child
logical :: use_acc = .false.
contains
procedure :: calc_density_array => calc_density_array_EOS_child
end type
subroutine calc_density_array_EOS_child(this)
class(EOS_child), intent(in) :: this
!$acc kernels if(this%use_acc)
! ...
!$acc end kernels
end subroutine
This would be more in-line with the “depend on abstractions, not concretions” philosophy. In other words, the container EOS_type should not need to be aware of low-level details like what parallel API you are using.
The wrapper calc_density_array
could now remain unchanged.
program main
use EOS, only: EOS_type, calculate_density_1d
implicit none
type(EOS_type) :: top
! This block would be replaced with a factory function
block
use EOS_child_mod, only: EOS_child
top%type = EOS_child(use_acc=.true.)
end block
call calculate_density_1d(top)
print *, "done!"
end program
Here is a link to CE: Compiler Explorer. This is the least intrusive way because nothing needs to change in your type hierarchy.