Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.
28381 Discussions

memory reallocation inside subroutine

may_ka
Beginner
625 Views

Hi,

I want copy array content including possible memory allocation for the recipient array in side a subroutine but have to allow for the option that the recipient array and the donor array are the same, or that the donor array is a section of the recipient array. I tried to filter the latter cases via the "loc" function but this seems to fail when the the array section is contiguous but does not start at position one.

A small example program which I was expecting NOT to work ..........   but i works:

program test
  implicit none
  integer, allocatable :: i1(:)
  integer :: i
  allocate(i1(10),source=(/(i,i=1,10)/))
  !call subi1(i1,i1)
  !call subi1(i1,i1(1:8))
  call subi1(i1,i1(4:8))
  write(*,*) allocated(i1)
  write(*,"(*(g0:"",""))") i1
contains
  subroutine subi1(a,b)
    integer, intent(inout), allocatable :: a(:)
    integer, intent(in) :: b(:)
    write(*,*) loc(a)==loc(b)
    if(allocated(a)) deallocate(a)
    allocate(a,source=b)
  end subroutine subi1
end program test

I compiled it with

ifort -check all -check arg_temp_created -fno-inline-functions 1.f90

It ran with all three different calls, whereas I was expecting it not to run at all. I know that fortran requires subroutine arguments to point to non-overlappingg memory locations, and I wanted to use "loc" to filter for violation, but this filter fails at the third call (it reports F whereas the donor is a subset of the recipient and no temporary argument was created).

 

Now the questions are: A) did it run just accidentally and B) if so, is there any why to catch the last call in a way that one can infer that the second array is a subset of the first array?

 

any suggestions.

Thanks a lot.

0 Kudos
13 Replies
may_ka
Beginner
625 Views

So I found that it worked accidentally and a filter for the last case is

subroutine subi1(a,b)
    integer, intent(inout), allocatable :: a(:)
    integer, intent(in) :: b(:)
    integer :: l,i
    l=loc(b(1))
    do i=1,size(a)
      if(loc(a(i))/=l) cycle
      !!do something to accommodate for this
      return
    end do
    if(allocated(a)) deallocate(a)
    allocate(a,source=b)
  end subroutine subi1

However, that requires to iterate over "a" which might be detrimental to performance.

0 Kudos
Steve_Lionel
Honored Contributor III
625 Views

A) Yes. B) No.

0 Kudos
jimdempseyatthecove
Honored Contributor III
625 Views

>>call subi1(i1,i1(4:8))

This violates the aliasing of arguments requirement of Fortran.

subroutine subi1(a,b)
    integer, intent(inout), allocatable :: a(:)
    integer, intent(in) :: b(:) ! **** b may vioalate antialiasing rule
    integer, allocatable :: b_copy(:)
    
    ! in the event that b is subsection of a
    b_copy = b ! requires reallocate left hand side
    if(allocated(a)) deallocate(a) ! optional with reallocate left hand side
    allocate(a,source=b_copy)
    ! b_copy will be automatically deallocated
end subroutine subi1

Jim Dempsey

0 Kudos
may_ka
Beginner
625 Views

Hi

thanks for the response.

with "!!do something to accommodate for this" in my second post I meant exactly what Jim described.

subroutine subi1(a,b)
    integer, intent(inout), allocatable :: a(:)
    integer, intent(in) :: b(:)
    integer, allocatable :: b_copy(:)
    integer :: l,i
    if(allocated(a)) then
      l=loc(b(1))
      do i=1,size(a)
        if(loc(a(i))/=l) cycle
        b_copy=b;deallocate(a);a=b_copy
        return
      end do
    end if
    if(allocated(a)) deallocate(a)
    allocate(a,source=b)
 end subroutine subi1

the question is whether that is sufficient to determine whether "b" is a section of "a". I don't see why this is not a solution (as suggested by Steve).

 

0 Kudos
jimdempseyatthecove
Honored Contributor III
625 Views

This might be better:

subroutine subi1(a,b)
    integer, intent(inout), allocatable :: a(:)
    integer, intent(in) :: b(:)
    integer, allocatable :: b_copy(:)
    if(allocated(a)) then
      ! assure b not alias with a
      if( loc(b(size(b))) < loc(a(1)) .or. loc(b(1)) > loc(a(size(a))) ) then
        a = b ! realloc lhs will take care of deallocation and reallocation
        return
      endif
      ! alias
      b_copy = b
      a = b_copy ! realloc lhs will take care of deallocation and reallocation
      return
    endif
    a = b  ! realloc lhs will take care of allocation
    ! *** call subi1(i1,i1(4:8)) invalid when i1 not allocated
 end subroutine subi1

Jim Dempsey

0 Kudos
may_ka
Beginner
625 Views

Hi Jim.

Thanks for the suggestions.

Do you say that "loc" will yield an increasing sequence of numbers when applied to all ram locations, starting with say 0 and ending with say N?? If so, is there an description of "loc" which verifies that? From what I found from the compiler suppliers (Intel, gnu) I wouldn't be able to make that inference.

Thanks

0 Kudos
IanH
Honored Contributor II
625 Views

The language rules are still being violated by the attempted workarounds in #4, #5, and #6.  If the thing associated with the dummy `b` is part of the thing associated with the actual argument `a`, then the allocation status of `a` must not be changed at all (F2018 15.5.2.13).  The workarounds attempt to deal with one possible symptom of the violation, but the violation of the language rules is still there.  (Note compilers take advantage of these rules (that's one reason why the rules are there...) - given the rules the temporary b_copy variable is fair picking for an clever optimiser to eliminate.)

An assignment statement in the otherwise calling scope would be a simple solution.  Alternatively, break the association between the actual arguments by enclosing the argument corresponding to the INTENT(IN) `b` dummy in parentheses.

0 Kudos
may_ka
Beginner
625 Views

@Ian H

I tried to get my head around what "call subi1(i1,(i1(4:8)))" would actually mean and it occurs to me that it will force the compiler to make a temporary copy of "i1(4:8)", similar to call-by-value.

If that's correct I would assume that compiling with "-check arg_temp_created" should generated a message at run time. But that doesn't seem to happen?? So either the assumption about the temporary array is wrong or the compiler is not showing up when using this syntax.

Any idea.

0 Kudos
Steve_Lionel
Honored Contributor III
625 Views

Because b is an assumed-shape array and the effective argument is a contiguous slice, no copy is made. The descriptor passed has the base address and the shape.

0 Kudos
IanH
Honored Contributor II
625 Views

If you look at the generated assembly for a parenthesised expression as an actual argument, you will see the creation of a temporary.

I don't know what the design intent is for "-check arg_temp_created", but I presume that option warns for the situation where the need for the temporary is implicit, not explicit.

0 Kudos
may_ka
Beginner
625 Views

I have to believe you guys as I am not familiar with assembly. But from making trials with large arrays where the code segfaults without parenthesis I found that the parenthesis are doing the trick.

Cheers

0 Kudos
jimdempseyatthecove
Honored Contributor III
625 Views

IanH,

given the rules the temporary b_copy variable is fair picking for an clever optimiser to eliminate.)

An assignment statement in the otherwise calling scope would be a simple solution.  Alternatively, break the association between the actual arguments by enclosing the argument corresponding to the INTENT(IN) `b` dummy in parentheses.

Then wouldn't the same fair picking optimization occur at the call as well?

To avoid fair picking of optimization, perhaps attributing b_copy with VOLATILE might suffice. But then the compiler designer is caught in the position of having a "must modify" versus "can eliminate".

Assuming call subi1(i1,(i1(4:8))) works, this (problem with alias) might be would be obfuscated, should the a and b arguments at the call be dummy arguments themselves. And in which case the caller wouldn't know they were aliases, and as such would always have to call with ()'s around the 2nd argument. This would be unnecessary work when they weren't aliases, and easily overlooked with many points of call. Placing the alias test inside the called subroutine is the safest place to put it. You would have to assure that the code optimization would not circumvent your intentions.

subroutine subi1(a,b)
!DIR$ NOOPTIMIZE
...

Jim Dempsey

0 Kudos
Steve_Lionel
Honored Contributor III
625 Views

IanH (Blackbelt) wrote:

I don't know what the design intent is for "-check arg_temp_created", but I presume that option warns for the situation where the need for the temporary is implicit, not explicit.

That option's diagnostics are limited to creation of temps as actual arguments, such as passing a non-contiguous array slice to a routine that wants a contiguous one. It doesn't fire for many other places where temps are created.

0 Kudos
Reply