Conflict between intent(IN) and inlining

I’m encountering a situation where optimizer inlining produces a different result because of conflict with intent(IN). Consider the following example code:

module mymod
   implicit none
contains
subroutine mysub(i)
   integer, intent(IN) :: i

   call setval(i)
end subroutine mysub
subroutine setval(i)
   integer :: i

   ! -- i may be set here because setval does not use intent(IN)
   i = 20
end subroutine setval
end module mymod

program main
   use mymod, only : mysub
   implicit none
   integer :: i
   i = 10
   write(*,*) 'before sub: ', i
   call mysub(i)
   write(*,*) 'after sub:  ', i
end program main

With optimizations off, the code produces:

% pgfortran -O0 test_intent.f90 && ./a.out
 before sub:            10
 after sub:             20

With full optimizations, including inlining which I suspect is the culprit, the code produces:

% pgfortran -O3 test_intent.f90 && ./a.out
 before sub:            10
 after sub:             10

Without any inlining, setval is able to set i because intent(IN) is only declared within mysub. With optimizations on, though, i is being set within a scope where i is readonly. So I guess the compiler is silently deciding not to set it?

I’m not certain what expected behaviour is in this case. It’s disconcerting to me that -O3 and -O0 give different results. I also would have liked to see a warning about editing a read-only variable when inlining is performed - silently changing the behaviour seems like an error to me. Intel and gfortran both produce the same results (10 and 20) regardless of optimization level.

What is expected behaviour in this case? Is there any way for me to see a warning about not setting i? My tests were conducted on PGI 15.5.

If a dummy argument has INTENT(IN) specified, a conforming program is required to call the subprogram concerned with a corresponding actual argument that is “definable”. This requirement is placed upon the program (and, by inference, on the programmer). Thus, a program that violates this requirement, such as yours, is a “non-conforming” program, and the “processor”, i.e., the PGI compiler+runtime, can do anything with it.

Some compilers may provide optional means of checking that requirements such as the one under discussion are met. For example, your program generates the following run-time message when compiled with the -C option with the NAG Fortran compiler:

Runtime Error: int.f90, line 13: Dummy argument I is associated with an expression - cannot assign

However, unlike “constraints” that are listed in the Fortran standard, a standard-conforming compiler is not required to catch violations of requirements.

Thanks for the response mecej4. I agree that the code above is not standard-conforming and it makes sense that pgfortran’s behaviour is undefined.

It seems that not all of pgfortran assumes that intent(IN) will be treated correctly by the developer, though. This whole issue came up when discussing optimizations performed on integer switches.

Consider the following subroutine:

subroutine mysub(N,iswitch,x,y)
   implicit none
   integer, intent(IN) :: N, iswitch
   real, intent(IN) :: x(N)
   real, intent(OUT) :: y(N)
   integer :: i

   do i=1,N
      if (iswitch == 1) then
         y(i) = x(i)
      endif
#ifdef ADD_CALL
      call do_something(iswitch)
#endif
   enddo

end subroutine mysub

Given that iswitch is declared as intent(IN) in mysub, it shouldn’t change throughout the execution of mysub - regardless of whether another subroutine is called.

Without the subroutine call, the if statement is recognized as invariant and the order is switched:

% pgfortran -c -Minfo=all -O3 -Mpreprocess vectorize.f90 
mysub:
      8, Invariant if transformation
         Generated 4 alternate versions of the loop
         Generated vector sse code for the loop
         Generated a prefetch instruction for the loop

However, the if statement is not recognized as invariant when do_something is called:

% pgfortran -c -Minfo=all -O3 -Mpreprocess -DADD_CALL vectorize.f90 
mysub:
      8, Loop not vectorized/parallelized: contains call

I can even use an interface to declare do_something to be pure with intent(IN) for iswitch and the loop order will still not be switched.

Is there any way to instruct pgfortran to treat iswitch as constant for the purposes of optimization throughout mysub? intent(IN) does not seem to be doing it. It would also be nice to be able to do something similar with protected variables.