I work on a very old code which relies on two big working arrays IW and RW, which are equivalenced in the main program and then used directly or propagated to called subroutines like this:
program test implicit none external inner integer(4),parameter :: li = 100 integer(4) iw(li) real(8) rw(li/2) equivalence (iw,rw) rw = -10.20 print *,iw print *,rw ! call someroutine(iw,li,rw,lr) will also work end
When I replace the static arrays with dynamically allocated, then I of course cannot equivalence the arrays. I came up with this code:
program test implicit none external inner,outer integer(4),parameter :: li = 100 call outer(inner,li) end subroutine outer(proc,li) implicit none external proc integer(4),intent(in) :: li integer(4),allocatable :: iw(:) allocate(iw(li)) call proc(iw,li,iw,li/2) deallocate(iw) end subroutine inner(iw,li,rw,lr) implicit none integer(4),intent(in) :: li,lr integer(4),intent(inout) :: iw(li) real(8),intent(inout) :: rw(lr) rw = -10.20 print *,iw print *,rw ! call someroutine(iw,li,rw,lr) will also work end
So here the main program is reduced to a call to a helper subroutine outer, which allocates a single array and then passes it as two arrays to subroutine inner, which in turn contains the code of the original main program. The reason the compiler does not complain about the third parameter to inner not being a real(8) array is that in outer, inner is hidden in proc as an external subroutine with arbitrary arguments.
Is there any other, maybe more simpler or clean, way?
If you were helped with an answer to your inquiry on this thread, can you please mark it as a "solution" per this Intel community terminology so other readers can benefit from knowing how things worked out for you?
One last question, just to be sure. To solve the issue with the array argument, it is sufficient to add just a colon after the index to have a standard-compliant code? Like so:
Isn't RW(...) argument a subsection of the blob?
As such, you would need to pass the lower bound (LBFOO) as well as the upper bound (UBFOO).
Using the RW(LBFOO:) would pass a slice starting at LBFOO and extending to the end of the blob.
Remember that this is ancient code where arrays are always passed by their first element and length:
CALL FOO(RW(LBBAR),LBAR,IW(LBBAZ),LBAZ) . . . END SUBROUTINE FOO(A,LA,IA,LIA) DIMENSION A(LA),IA(LIA) . . .
However, the moment RW becames POINTER, the compiler starts to complain about RW(LBBAR) not being an array.
Adding a colon in the argument RW(LBBAR:) makes the code compilable again. For all intents and purposes, this should have no effect at runtime.
@Petr_Parik wrote: .. One last question, just to be sure. To solve the issue with the array argument, it is sufficient to add just a colon after the index to have a standard-compliant code? Like so: CALL BAR(...,RW(LBFOO:),...)
While you can use an array section notation to solve the issue, note you may possibly face other issues and that's why I didn't suggest it earlier. Note it's tough to list what problems you may run into what without knowing your entire code but it can be a performance hit in the form of an array temporary in CALLs to other fault scenarios during run-time.
If you're OPEN to the notion of making changes on the CALLER side like what you show with "CALL BAR(..,RW(..:), ..", then my suggestion will be to use the POINTER facility in modern Fortran to setup SPECIFIC objects with the POINTER attribute for EACH of your "work" ARRAYs. See an illustration of this with your SAMPLE program where you will see 4 SPECIFIC objects for your 4 arrays - IA1, IA2, RA2, and RA2. Given your legacy code with all the existing "book-keeping" toward the indices and array sizes, you'll see it's straightforward to do this and it WILL work with Intel Fortran, gfortran, and pretty much all the standard-conforming Fortran compilers out there. I show this Intel Fortran and gfortran below.
PROGRAM TEST_PTR USE ISO_C_BINDING IMPLICIT INTEGER(4) (I-N), REAL(8) (A-H,O-Z) PARAMETER (LI = 100) ALLOCATABLE IW(:) TARGET IW REAL(8), POINTER :: RW(:) C Additional pointers for array argument to WORK* subprograms INTEGER(4), POINTER :: IA1(:) INTEGER(4), POINTER :: IA2(:) REAL(8), POINTER :: RA1(:) REAL(8), POINTER :: RA2(:) ALLOCATE(IW(LI)) CALL C_F_POINTER(C_LOC(IW),RW,(/LI/2/)) ! RW => IW LBIA1 = 1; LIA1 = 10 LBIA2 = 21; LIA2 = 10 LBRA1 = 6; LRA1 = 5 LBRA2 = 16; LRA2 = 5 C Set up array arguments IA1(1:LIA1) => IW(LBIA1:) IA2(1:LIA2) => IW(LBIA2:) RA1(1:LRA1) => RW(LBRA1:) RA2(1:LRA2) => RW(LBRA2:) CALL WORK1(IA1,LIA1,IA2,LIA2, & RA1,LRA1,RA2,LRA2) CALL WORK2(IA1,LIA1,IA2,LIA2, & RA1,LRA1,RA2,LRA2) END SUBROUTINE WORK1(IA1,LIA1,IA2,LIA2,RA1,LRA1,RA2,LRA2) IMPLICIT INTEGER(4) (I-N), REAL(8) (A-H,O-Z) DIMENSION IA1(LIA1),IA2(LIA2),RA1(LRA1),RA2(LRA2) IA1 = 1 IA2 = 2 RA1 = .123456789 RA2 = .987654321 END SUBROUTINE WORK2(IA1,LIA1,IA2,LIA2,RA1,LRA1,RA2,LRA2) IMPLICIT INTEGER(4) (I-N), REAL(8) (A-H,O-Z) DIMENSION IA1(LIA1),IA2(LIA2),RA1(LRA1),RA2(LRA2) PRINT *,"IA1 =" PRINT *,IA1 PRINT *,"IA2 =" PRINT *,IA2 PRINT *,"RA1 =" PRINT *,RA1 PRINT *,"RA2 =" PRINT *,RA2 END
Build and execute with Intel Fortran and gfortran:
C:\Temp>ifort test_ptr.for Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.1.2 Build 20201208_000000 Copyright (C) 1985-2020 Intel Corporation. All rights reserved. Microsoft (R) Incremental Linker Version 14.26.28806.0 Copyright (C) Microsoft Corporation. All rights reserved. -out:test_ptr.exe -subsystem:console test_ptr.obj C:\Temp>test_ptr.exe IA1 = 1 1 1 1 1 1 1 1 1 1 IA2 = 2 2 2 2 2 2 2 2 2 2 RA1 = 0.123456791043282 0.123456791043282 0.123456791043282 0.123456791043282 0.123456791043282 RA2 = 0.987654328346252 0.987654328346252 0.987654328346252 0.987654328346252 0.987654328346252 C:\Temp>gfortran test_ptr.for -o gcc-test_ptr.exe C:\Temp>gcc-test_ptr.exe IA1 = 1 1 1 1 1 1 1 1 1 1 IA2 = 2 2 2 2 2 2 2 2 2 2 RA1 = 0.12345679104328156 0.12345679104328156 0.12345679104328156 0.12345679104328156 0.12345679104328156 RA2 = 0.98765432834625244 0.98765432834625244 0.98765432834625244 0.98765432834625244 0.98765432834625244 C:\Temp>
While you can use (LBBAR:) today, sometime in the future, you or your replacement may be updating the code and which at that time may introduce errors (or at least confusion). While adding ":" makes sense to you today, it might not make sense to someone else later.
So you have three choices:
Use ...,RW(LBBAR:),LBAR.... ! ":" indicates to end of blob, fix later
or use RW(LBBAR:UBBAR),LBAR,...! LBAR is passed for legacy purposes, fix later
or use RW(LBBAR:LBBAR+LBAR-1),LBAR, ... ! add UBBAR later, then remove LBAR on next revision.
When you revise the code (some time later), you might consider using Defining Generic Names for Procedures (intel.com)
This way you can have your subroutine FOO become a generic name with two interfaces: old/legacy argument list, and new updated argument list.
See my comment just prior to yours and the option therein - here's a link to it.
The use of additional variables of POINTER attribute for the work arrays of interest to OP - IA1, IA2, RA1, RA2, etc. - in this legacy code is more safe and readable, maintainable in my experience.
A word of caution about being overly zealous with allocating vary large array.
At time of allocation of a huge blob, the allocation may succeed in obtaining virtual addresses, but can fail some time later at first touch when it is found that the page file has insufficient space. This failure (page file full) is not trappable.
The is a caution not to request a few terabytes of memory for your buffer.
I know sub-array pointers would work. Unfortunately, that is even more error-prone work than adding ":" to calls, as I have found. So I have settled with a slightly bloated but simple "loader" approach:
PROGRAM LOADER C allocation/equivalence stuff ... CALL FOO(IW,LI,RW,LI) END SUBROUTINE FOO(IW,LI,RW,LR) DIMENSION IW(LI),RW(LR) C original PROGRAM FOO code with almost no changes C 'RW' behaves like a normal array, no need to modify calls C ... END
Adding ":" or upper bound would be too much error-prone work, see above.
However, interfaces for incremental refactoring are an excellent idea, thank you.
Regarding the virtual memory; allocating a large blob then failing later at runtime due OOM is no different to allocating arrays as needed and then still failing later at runtime when virtual memory is depleted. But fear not, LI is INTEGER(4) so we are talking 8 GiB blob max. Some new programs have 64-bit LI, but even in that case the maximum needed so far has been less than the physical memory (128 GiB). Generally, if I need to do computations, I need to make sure nobody else is using the computer.
>> allocating a large blob then failing later at runtime due OOM is no different to allocating arrays as needed and then still failing later at runtime when virtual memory is depleted
With the exception that one can test for failure at ALLOCATE (assuming the heap isn't larger than remaining space in page file).
>> But fear not, LI is INTEGER(4) so we are talking 8 GiB blob max
From your description (which I may have misunderstood) is your code allocates once and anything becoming unused is lost. Presumably the allocations reach a stasis with a not unreasonable amount of dead space in the blob.
>> With the exception that one can test for failure at ALLOCATE (assuming the heap isn't larger than remaining space in page file).
You are right, but if we distinguish only between a successful computation and a failed computation, it does not matter if the program fails gracefully.
>> From your description (which I may have misunderstood) is your code allocates once and anything becoming unused is lost. Presumably the allocations reach a stasis with a not unreasonable amount of dead space in the blob.
As you pointed out earlier, modern operating systems allocate only the memory accessed by the program. But yes, there can be several gigs of unused space in the blob.