Problem with parameterized derived type

Hello. It’s my first time messing with parameterized derived types and I’m not sure what’s happening here. Is the following program legal?

program test
  implicit none

  type :: mytype_t ( l )
    integer, len :: l = 40
    real :: arr(0:l-1)
  end type

  type(mytype_t) :: a

  a%arr(0) = 1.0

end program

It compiles without warnings but crashes when executing. Here’s what I get

$ gfortran -v
Using built-in specs.
COLLECT_GCC=gfortran
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/12/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 12.2.0-14' --with-bugurl=file:///usr/share/doc/gcc-12/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-12 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --enable-cet --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-12-bTRWOB/gcc-12-12.2.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-12-bTRWOB/gcc-12-12.2.0/debian/tmp-gcn/usr --enable-offload-defaulted --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 12.2.0 (Debian 12.2.0-14) 

$ ./a.out 
double free or corruption (out)

Program received signal SIGABRT: Process abort signal.

Backtrace for this error:
#0  0x7ff39de218c2 in ???
#1  0x7ff39de20a55 in ???
#2  0x7ff39dc5b04f in ???
        at ./signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
#3  0x7ff39dca9ebc in __pthread_kill_implementation
        at ./nptl/pthread_kill.c:44
#4  0x7ff39dc5afb1 in __GI_raise
        at ../sysdeps/posix/raise.c:26
#5  0x7ff39dc45471 in __GI_abort
        at ./stdlib/abort.c:79
#6  0x7ff39dc9e42f in __libc_message
        at ../sysdeps/posix/libc_fatal.c:155
#7  0x7ff39dcb3839 in malloc_printerr
        at ./malloc/malloc.c:5660
#8  0x7ff39dcb589f in _int_free
        at ./malloc/malloc.c:4584
#9  0x7ff39dcb7f1e in __GI___libc_free
        at ./malloc/malloc.c:3385
#10  0x5613eb322224 in ???
#11  0x5613eb32226a in ???
#12  0x7ff39dc46249 in __libc_start_call_main
        at ../sysdeps/nptl/libc_start_call_main.h:58
#13  0x7ff39dc46304 in __libc_start_main_impl
        at ../csu/libc-start.c:360
#14  0x5613eb3220a0 in ???
#15  0xffffffffffffffff in ???
Aborted (core dumped)

This won’t happen if I don’t specify a lower bound for arr.

Any help appreciated.

2 Likes

If I build the program with ifx and run it, there is no problem. I even printed the array bounds of a%arr - exactly as expected. gfortran will also print the array bounds as expected. It is only when you try and access an element of a%arr, that the program says “bang”.

I suspect a bug in gfortran.

I see no reason why a non-default lower bound should be forbidden, I meant to add :slight_smile:

gfortran’s implementation of PDTs is still problematic (but in my experience you can say that about most/all current Fortran compilers). Also, it looks like you are using gfortran/gcc 12. I think some PDT issues were fixed in gcc 13 (and probably 14) so you might try an newer version of gfortran.

As I mentioned here (Intel forum), PDTs are not very popular among compiler vendors, so implementation behavior remains untested/poorly-tested.

And if combined with another impopular feature (Intel forum) (that I happen to like a lot), things start to get weird.

This has been a problem since PDTs were introduced into the language in f2003. There are several situations where I would love to use PDTs rather than allocatable components in my codes, but I cannot use them until they are supported well by all of the popular compilers. So as long as even one popular compiler does not support the feature, I will not use them in my codes. The compiler writers say that the feature is not used much, so why bother with the effort to support them. In the case of gfortran, they say it is an open source compiler, so if it is important to you, then implement it yourself (in C of course, not in fortran). Thus the lack of support has been, and is still, self-reinforcing. Many other programmers are in this same situation, they would use the feature if it were reliable among all the popular compilers, but it’s not, even after some 20 years.

4 Likes

Even though I have gfortran, ifort/ifx, nvfortran and flang installed, my list of feature support reduces to gfortran and ifort/ifx (since nobody really knows what’s the implementation status of what in nvfortran and flang :laughing: ).

I remember, back at comp.lang.fortran, someone (I guess, Richard Maine), mentioned that as a curiosity —i.e., that most Fortran-related tools tend to be written in some other language. fpm seems to be the exception, not the rule.

And now everything is moving to clang (it’s the new Chromium!), so it’ll be C++ all the way.

Just out of curiosity, is the main reason for this trouble of PDT well known already? Is it related to the basic design? If so, is it the dynamic nature of the len parameter as compared to the static nature of kind parameter? (Also, are the issues possibly similar to those of variable-length arrays in C/C++?)

If so, is it the dynamic nature of the len parameter as compared to the static nature of kind parameter?

I’ve wondered about this myself. For me most of my uses of len in a PDT in an actual code wouild have a fixed length at compile time (ie a real parameter or constant. An example would be a 3x3 or 4x4 matrix etc.). We already have allocatable components of derived types so I’m not sure why whoever did the basic design of PDTs thought having one more deferred length/size entity was needed

You cannot initialize (i.e. at compile time) an allocatable array. I think you can initialize an array within a PDT, right?

If the fortran standard allowed allocatable entities to be initialized, then lots of things could be done in simpler and more robust ways, and maybe then PDTs would be less useful.

In my chemistry codes, I have an irreducible representation (irrep) multiplication table that changes from run to run depending on what kind of molecule is being studied. That array is 1x1, 2x2, 4x4, or 8x8. That seems to be an ideal situation for a PDT with a len parameter determined at run time. The workaround is to use an allocatable array along with a runtime allocation and initialization step.

1 Like

Yes having the ability to initialize allocatable entities would be useful. Something else I’ve wondered about that maybe goes to how len is implemented in PDTs is would some of the issues with len disappear if the array being sized with len had to have an allocatable attribute. As I see it, this would bring the existing machinery of allocatable entities into play. The big difference would be (at least to me) is that the compiler at compiler time or run time would call ALLOCATE for you. Just a thought that probably betrays my ignorance of how PDTs (and probably ALLOCATE) works. In other words, using len would also require an allocatable attribute.

Thank you all for your answers.

I think it’s best for me if I just don’t use this kind of stuff for the moment.

Thanks again.

Intel Fortran compilers have strong support for PDTs. I have tested it extensively. There were minor bugs in the previous versions, that I believe are now resolved. If you intend to use Intel products (or NAG) alone, you will be mostly fine using PDTs. However, GNU compiler’s implementation of PDTs effectively can only recognize syntax but crash at runtime. As far as I remember, there is only one volunteer who has been actively/passively working on gfortran PDTs over the past years. If you can contribute to gfortran’s development of PDTs, please do so.
The most useful feature of PDTs is the kind type parameter, which is a compile time constant, allowing devopment of complex generic procedures and interfaces. The len type parameter is the source of all PDT problems and the least useful as implemented, with a significant impact on the performance.

1 Like

As discussed previously, I don’t use PDTs in my production codes, but I’m curious about the performance comment. Can you show some examples of how PDTs with the len parameter show poor performance? Is that poor performance relative to an allocatable component, or poor relative to a fixed length component, or what?

I could not find the relevant benchmarks I saw a few years ago on PDTs in the Intel forum. But I remember the len type parameter degraded performance by order(s) of magnitudes compared to all other array types, even allocatable. The compiler developers could provide the best answer on why this is the case. If I remember correctly, the complexity of PDT implementation arises when PDTs are passed as arguments to procedures.
On the positive side, the kind type parameter is super useful for generic programming. But its implementation in gfortran is still incomplete.

1 Like

I’m very interested to hear success stories on this. In my experience, because kind type parameters cannot be assumed, you run in to the same problem you have with intrinsics; you have to write a separate version for each kind you wish to support.

1 Like

Unrelated to performances, but I was surprised when realizing that the storage size of a PDT object does not depend at all on the len parameters:

implicit none

type dt1_t
    integer :: i(1)
end type
type dt1000_t
    integer :: i(1000)
end type
type adt_t
    integer, allocatable :: i(:)
end type
type pdt_t(l)
    integer, len :: l
    integer :: i(l)
end type

type(dt1_t) :: dt1
type(dt1000_t) :: dt1000
type(adt_t) :: adt1, adt1000
type(pdt_t(1)) :: pdt1
type(pdt_t(1000)) :: pdt1000

allocate( adt1%i(1), adt1000%i(1000) )

print*, storage_size(dt1)
print*, storage_size(dt1000)
print*, storage_size(adt1)
print*, storage_size(adt1000)
print*, storage_size(pdt1)
print*, storage_size(pdt1000)

end program

returns (with ifx, but this is similar with other compilers that compile this code (not all of them do)):

          32
       32000
         576
         576
        1280
        1280

Does it mean that in practice and under the hood, compilers handle the parametrized component with an allocatable?

2 Likes

My interpretation of this result is that that particular compiler already stores the length of the array in the derived type metadata anyway, and the integer, len :: l syntax is simply a way for the programmer to access that information with the pdt1000%l syntax. Of course, the compiler must store some metadata about the rank, length, and bounds of the array, but whether it is metadata like a static array, or like an allocatable array, or somewhere in between is just an internal detail of the compiler; nothing is specified about this in the standard, so the programmer cannot depend on the portability of any of those storage_size() results. Even the scalar derived type example is unspecified by the standard because int32 is not required to be supported, and even if it is it might not be the default integer kind, and also there could be padding bits somewhere.

On the other hand, qualifiers like sequence and bind place extra restrictions on the internal structures of derived types, and then more details become defined especially with respect to the companion C processor.

Another interesting set of questions is about the PDT parameters that are not used in kind or bounds expressions. Here, the compiler would not have a place already to store those values, but they must still be available to the programmer through the component syntax, so they might store them as part of the derived type metadata. Again, I do not think this is required by the standard, so the compiler has some flexibility where exactly that kind of information would be stored. Here is an example using a modified version of your code.

implicit none

type dt1_t
    integer :: i(1)
end type
type dt1000_t
    integer :: i(1000)
end type
type adt_t
    integer, allocatable :: i(:)
end type
type pdt_t(l)
    integer, len :: l
    integer :: i(l)
end type
type pdt_t4(l,j,k,m)
    integer, len :: l,j,k,m
    integer :: i(l)
end type

type(dt1_t) :: dt1
type(dt1000_t) :: dt1000
type(adt_t) :: adt1, adt1000
type(pdt_t(1)) :: pdt1
type(pdt_t(1000)) :: pdt1000
type(pdt_t4(1000,-huge(0),42,huge(0))) :: pdt4_1000

allocate( adt1%i(1), adt1000%i(1000) )

print*, storage_size(dt1)
print*, storage_size(dt1000)
print*, storage_size(adt1)
print*, storage_size(adt1000)
print*, storage_size(pdt1), pdt1%l
print*, storage_size(pdt1000), pdt1000%l
print*, storage_size(pdt4_1000), pdt4_1000%l, pdt4_1000%j, pdt4_1000%k, pdt4_1000%m

end program

$ gfortran dtsize.f90 && a.out
          32
       32000
         512
         512
         576           1
         576        1000
         640        1000 -2147483647          42  2147483647

The results with gfortran may be nonsense, but hopefully the code is correct for other compilers. It appears that somehow gfortran is storing those other three 32-bit integers within 64 bits in the derived type. BTW, if I change len to kind in the derived types (which I think is allowed in this example), the same results are printed.

No success stories. Gfortran lacks PDT support. Still, in the land of the blind, the one-eyed man is king.

Although PDT are not reliably available in Gfortran, I can usually make do with allocatable derived types and include the length of the allocatable array as one of the components of the derived type.
This functionality serves the requirements I have for allocatable arrays in derived types.

  type adt_t
    integer :: len_of_i
    integer, allocatable :: i(:)
  end type

  type (adt_t) aaa
  integer :: li

  li = 5678
  allocate ( aaa%i(li) )
  aaa%len_of_i = li

  write (*,*) storage_size ( aaa%i ), size (aaa%i )  ! interesting result on Gfortran

end