fortrancompiler-optimizationintel-fortran

Intel/ifort optimization causes segmentation fault


I'm trying to write a mixed-language (C++/Fortran) code in which memory passed from C++ to the Fortran subroutine may or may not be used and so in fact may or may not be allocated. The behavior inside the Fortran subroutine should be determined by an additional integer flag that is passed from C++ to Fortran. The problem that I am encountering seems to occur primarily with the Intel compiler (2022.1.2, 2022.1.0, 2021.4.0, 2021.1) combined with optimization (-O2). In that case, the compiler ignores my conditional statement and chooses to make an assignment from the unallocated memory, triggering a segmentation fault.

This example, consisting of one .cpp file, one .F90 file, and one Makefile, should demonstrate the problem: The line in the Fortran code that says "my_number = numbers" should never be executed based on the surrounding conditional, but with optimization, the line triggers a segmentation fault anyway.

Is there a way to prevent the optimization from ignoring my conditional or a better way to handle this overall (while maintaining the mixed-language paradigm)?

main.cpp:

extern "C" {
void test_module_mp_test_routine_(int* numbers,int* allocated);
}

int main(void)
{
  int* numbers(0);
  bool allocated(numbers);
  int allocated_int(allocated);

  test_module_mp_test_routine_(numbers,&allocated_int);

  return 0;
}

test_module.F90:

! ============================================================
  module test_module
! ============================================================

  implicit none
  private
  save

  public :: test_routine

  contains

! ============================================================
  subroutine test_routine(numbers,allocated_int)
! ============================================================

  implicit none

  integer :: numbers,allocated_int
  logical :: have_numbers
  integer :: my_number


  continue


  have_numbers = (allocated_int == 1)

  my_number = 0
  if (have_numbers) then
    my_number = numbers
  end if

  write(*,*)"allocated_int = ",allocated_int

  if (have_numbers) then
    write(*,*)"my_number = ",my_number
  end if

  return
  end subroutine test_routine

  end module test_module`

Makefile:

CXX=icpc
FORT=ifort

main: main.cpp test_module.o
        $(CXX) -O2 -o $@ $^ -lifcore

test_module.o: test_module.F90
        $(FORT) -O2 -c $<

clean:
        rm -f test_module.{o,mod} main

Solution

  • Within a Fortran procedure like

    subroutine sub(x)
       real :: x
    end subroutine
    

    the argument x is an ordinary (data) dummy variable. Within Fortran, if such a dummy variable is argument associated with an allocatable actual argument, that actual argument must be allocated (Fortran 2018, 15.5.2.4 p7); if argument associated with a pointer argument, that pointer argument must be pointer associated (F2018, 15.5.2.3 p1).

    If we have something like

    program bad
       implicit none
    
       real, allocatable :: a
       real, pointer :: b => null
    
       call sub(a)
       call sub(b)
    
    contains
    
       subroutine sub(x)
         real :: x
       end subroutine
    
    end program bad
    

    we have problems in each call to sub. You can ask your friendly compiler to tell you about these problems, instead of presenting a segmentation fault, or whatever other nastiness it chooses to perform. With ifort -check all is the way to ask.

    Now, when calling a Fortran subroutine like this by means other than Fortran (using C interoperation, for example, or C++ hacky stuff) our compiler-generated runtime test may not work, and Fortran's rules about allocation/pointer association could be seen as a little fuzzy. Still, we can work fully within the meaning of the code and Fortran's requirements by stating the goal here as "optional arguments". If we don't want to use a variable's value within the procedure, we avoid providing one.

    Going back to our Fortran program with a change:

    program lessbad
       implicit none
    
       real, allocatable :: a
       real, pointer :: b => null
    
       call sub(a)
       call sub(b)
    
    contains
    
       subroutine sub(x)
         real, optional :: x  ! A difference
       end subroutine
    
    end program lessbad
    

    For the optional x we are allowed to argument associate an unallocated actual argument or a disassociated pointer actual argument. Simply, in these cases x is taken as not present (F2018, 15.5.2.12).

    Optional arguments are interoperable with C in Fortran 2018, as implemented by recent Intel compilers:

    subroutine test_routine(numbers) bind(c)
      use, intrinsic :: iso_c_binding, only : c_int
      integer(c_int), optional :: numbers
    
      logical :: have_numbers
      integer :: my_number
    
      have_numbers = PRESENT(numbers)
    
      my_number = 0
      if (have_numbers) then
        my_number = numbers
      end if
    
      if (have_numbers) then
        write(*,*)"my_number = ",my_number
      end if
    end subroutine test_routine
    

    An optional dummy argument is absent if (and only if) it is associated with a (C) null pointer (F2018, 18.3.6 p7).

    In the example of the question, the pointer nature of the dummy argument is not used: it's not necessary for the dummy to be a pointer. In more general cases it may be necessary to have the dummy be a pointer: here this other answer shows how to use c_associated to test whether a type(c_ptr) is associated. In even more general cases it's possible to have a dummy a Fortran pointer (instead of a C pointer), and use associated, but that's somewhat more advanced.

    As also stated in that other answer, small changes to the invocation and C prototype definition are required, to support these alternative approaches.