Fortran 2023 standard

The program required a few fixes:

subroutine canard(n, a, b, c)
   integer, intent(in) :: n
   real, intent(in)  :: a(n,n), b(n,n)
   real, intent(out) :: c(n,n)
   c = matmul(transpose(a), b)
end subroutine canard

I’m on MacOS Monterey 12.5.1, with an Intel x86-64 CPU. I’m not sure which version of Accelerate I have, however the standard BLAS names work:

$ gfortran -O -fexternal-blas test_gemm.f90
Undefined symbols for architecture x86_64:
  "_sgemm_", referenced from:
      _MAIN__ in ccQ5YWSp.o
ld: symbol(s) not found for architecture x86_64
collect2: error: ld returned 1 exit status
$ gfortran -O -fexternal-blas test_gemm.f90 -framework Accelerate
$

@certik, the Mac developer pages only mention the C BLAS interface, but at least on x86-64, the Accelerate library exports BLAS symbols which match the standard name mangling scheme expected by gfortran. I assume this will be true on the Apple M1 too.

3 Likes

Yes it does, but @ivanpribec’s suggestion works!

$ gfortran -O -fexternal-blas test_gemm.f90 -framework Accelerate

That’s excellent. I didn’t know you can use -framework Accelerate with gfortran. I don’t have time right now to benchmark if it is truly using the transposed version or doing transpose ahead of time. I checked using -fdump-tree-all that doing matmul(A, B) vs matmul(tranpose(A), B) seems to only change some indexing, the final optimized tree representation doesn’t seem to do an explicit tranpose. The BLAS call must happen in the backend. I also looked at the assembly, but it’s hard for me to see if it is setting the CblasTrans option or not. I’ll check performance with fastGPT soon! This would be great if it works. Thank you.

3 Likes

Accelerate is great, it does include LAPACK too! The double precision routines likely run on the CPU, while the single precision part is already accelerated.

Here is a thread from last year that discusses some errors in some of the real32 routines in the Accelerate framework. I think examples were given with mkl and OpenBLAS as the discussion progressed.

I have not tested a recent Xcode install, which is where Accelerate lives, so I’m not sure if this has been corrected or not by Apple since that thread was posted. I did receive an email that they received my bug report and that they were looking at it.

2 Likes

Intrinsic types for BITS and STRING make the use of Fortran in numeric computation and scientific computing a lot more effective and productive.

It is truly a disservice to a Fortranner that she cannot write

string parts(..)
..
parts = [ "valve", "compressor", .. ]
..

It is an utterly sorry situation the 14 voting member companies/organizations of J3 - Nvidia, ORNL, IBM, NASA, LBNL, LANL, Dancing Bear, Lionel, HPE, AMD, Intel, ARM, LLNL, NCAR - simply cannot be influenced to do the bit of effort, which is mostly one-time and which can be made much easier by subcontracting to the Community, required to get these types into the language. Types that one can guarantee a lot more practitioners will use far more productively and frequently than the too little, too late of half-baked features are added to the standard, whether it be PGAS-based SPMD parallelization or the (not-)concurrent but named so option or you name it.

With the simple work of adding BITS / STRING types, that is also what the Fortranner seeks, a simple standard way to write code involving such types because the standard would have established a common “API”, whether it be erase / delete with string or anything else, just as with matmul(transpose(A), B) shown above toward matrix operations.

@FortranFan try this in LPython:

parts: list[str]
parts = [ "valve", "compressor", "etc." ]
print(parts)
part: str
for part in parts:
    print(part)

It gives:

$ lpython a.py
['valve', 'compressor', 'etc.']
valve
compressor
etc.

So LFortran internally already has a support for lists and strings like this.

Why don’t you come up with some good Fortran syntax for this? We can easily create a prototype for this, since we already did the hard work of making it working in the middle end and backends.

One way is to emulate a list using some derived type and provide a Fortran implementation, but the syntax will not be the best. Alternatively we can extend Fortran to have lists of strings.

2 Likes

@certik and anyone interested in this:

Please note I am not particularly hung up on the syntax per se, my interest is mostly consistent and ease-of-use for the practitioners which does mean a facility that is an integral part of the language that all conforming Fortran processors support.

But now, I do think there are 7 aspects that can make it much more pleasant for the practitioners and which can be considered toward a prototype implementation in LFortran.

On this, first take a look at the situation with the current standard and the derived type option:

module xxx
   type :: string
      ! private?
      character(len=:), allocatable :: s ! or perhaps s(:)  and private or not
   end type
   ..
end module
   use xxx ..  ! superfluous
   type(string), allocatable :: parts(:) ! why type(string)?  should be simply string
   parts = [ string("valve"), string("compressor"), string("transducer") ]
   ! substring
   print *, parts(3)%s(2:4)
end 

So, as you will know, the derived type facility is the excuse by the standards committee to not work on an intrinsic STRING type. But their argument places the practitioners last and it is deeply flawed, a total disservice.

  1. it is difficult for the library developers who wishes to author such a type to decide on the attributes, especially the RANK of the member underpinning the type i.e., s in the above example.
  2. as with 1, whether the member can be made PRIVATE and yet successfully meet the use cases of the consumers including IO will be a big concern for the library developer(s) of such type,
  3. so with 2, a particularly vexxing problem is the must-have use case for practitioners which is substring accessor consistent with the current intrinsic CHARACTER type e.g., parts(3)(2:4). There are only poor workarounds toward this with the current standard and that should be unacceptable for the practitioners, they need to be served better.
  4. Next is methods which operate on said type. A lot of the APIs (interfaces) have been established both with the now-deleted Part 3 (ISO VARYING STRING) and also so many other languages that offer string types. However there are two questions: a) should be type-bound or not and b) what should be their names (e.g., erase or delete?). Issue a) becomes relevant with respect to USE statement and ONLY clause. An intrinsic type which is part of the standard, whatever the decision as long as it is easy-to-use for a Fortran coder, makes this is all straight-forward.
  5. Similarly with type construction: library authors have limited options - the above snippet shows one possibility. But an intrinsic type in a standard can provide for a simpler one, perhaps an array of jagged strings - that will be cool.
  6. The need for type( .. ) with such an simple but important type is entirely lame, users should simply be able to reference it as string (or some such).
  7. for such a basic type, all the administrative tasks with MODULE management (MOD and library files) and toward their USE by the practitioners with a library solution is mindless. Making it intrinsic does away with all this, Meaning, the type must be available anywhere and everywhere just like CHARACTER

Sure, this all means some work on the standard document itself and also for the implementors. But those working on the standard as well as the implementors must be ready to serve the Fortran practitioners. Otherwise, they can get out of the way, let the world not bother them anymore, just as no one will anyone at Absoft and Lahey Fortran.

Nonetheless, should there a few people willing to prototype something in LFortran and be open to considering the above 7 aspects, I will be more than open to both funding with $$ the effort in LFortran as well as helping out the specs, design, testing, etc.

4 Likes

I agree heavily that a string type should be intrinsic to the language, and not dependent on the derived type faculties of the language. Derived types are nice for making my own structs and small groups of variables. They’re somewhat cumbersome to use when you have something like an array of derived types with some component that is also an array, such as array_of_strings(4)%string_data(2:3) rather than array_of_strings(4)(2:3) in order to access characters 2:3 of array_of_strings(4).

I particularly like point 5 above, and would prefer an array of jagged strings.

2 Likes

There are a couple of other points that might be useful. One is the ability to initialize the values as they are declared, including default initialization of components of a derived type, and the other is the ability to define parameters of that type. With the current workaround involving a derived type with an allocatable component, neither of those are possible.

2 Likes

Excellent. We should probably split this into its own thread for strings.

Currently Fortran doesn’t allow arrays of allocatable types, correct?

Should string be essentially equivalent to character(:), allocatable, just better syntax?

Isn’t appending to a “list of strings” a very common operation? For example in Python, I don’t think I ever used a NumPy array of strings, but I frequently use a list of strings. Just 1D. So that’s why I suggested to add a list type to Fortran and allow a string to be used in it.

1 Like

I think it’s important to keep all intrinsic types compatible as either scalar or arrays. List type would also be excellent. I know you’ve referenced the Lcompilers having a very performant list and dictionary type intrinsic already. The list handling you already have may be leveraged here very nicely.

Lists in general would be nice to have in Fortran to contain any of the intrinsic types, basically matching C++ vector. I believe there was a thread recently on the Discourse here about how best to handle the internals of a “reallocatable” array (I believe other languages call this concept a dynamic array). Pre-allocating space, perhaps in powers of two, or some mix of logarithmic and linear growth (1, 2, …, 9, 10, 20, …, 90, 100, 200, …, 900, 1000, 2000, …, 9000, 10000, 20000, etc), could lend itself to a more performant solution than what currently seems to happen for allocatable with move_alloc.

There are aspects of the design you are asking for here that do not work with existing rules in the language, namely that array elements have consistent sizes. As an example, how could the following be expected to work?

interface
  subroutine show_second(strings)
    string, intent(in) :: strings(*)
  end subroutine
end interface
string, allocatable :: parts(:)
parts = [string :: "valve", "compressor", "transducer"]
call show_second(parts)
end
subroutine show_second(strings)
  string, intent(in) :: strings(*)
  ! where does the second element start, and how big is it?
  print *, strings(2)
end subroutine

@everythingfunctional

There are aspects of the design you are asking for here that do not work with existing rules

Then change the existing rules to do as I’ve proposed in the past and use curly brackets ({ }) to signal an array like structure that can have variable length entries. I would restrict it to two cases, the string case under discussion and a true array of pointers. Your example then becomes.

interface
  subroutine show_second(strings)
    string, intent(in) :: strings{*}
  end subroutine
end interface
string, allocatable :: parts{:}
parts = [string :: "valve", "compressor", "transducer"]
call show_second(parts)
end

subroutine show_second(strings)
  string, intent(in) :: strings{*}
  ! where does the second element start, and how big is it?
  print *, strings{2}
end subroutine

With some reasonable constraints, the curly bracket syntax would function in most cases like standard array syntax.

2 Likes

I would want this to print ‘compressor,’ but would be unsurprised if it printed ‘a.’ However, I would expect that to work fine and print ‘compressor’ if the dummy argument was assumed shape rather than assumed size.

There are several possible data structures that could store a list, including arrays, linked lists, binary search trees, and many others. Some of those support/allow new members to be appended efficiently and others don’t.

I guess the question at this time is whether just the low-level data type should be added to the standard, or also the high-level data structure with its associated high-level functionality?

I’m reading between the lines of your response here, but I think you are saying the 'a' would be printed because it is the second character of the strings(1) array element. That doesn’t make sense to me. I don’t see why there should be any syntax difference in that respect between the array declarations strings(*) and strings(:). Another feature of the array semantics is that elements can be replaced easily in the obvious way. A statement like

strings(1) = string('new')

should simply replace that array element, it should not require compressing or copying the entire data structure. If it were required to copy the whole data structure any time a member is modified, I think the data structure would be effectively useless.

I think we need to figure out where the new type string will be allowed to be used, so that’s why I brought it up.

In LPython we don’t allow it in arrays currently, because it’s essentially “allocatable” and it seems the current Fortran design doesn’t allow having allocatables inside arrays?

So adding a list would fix the use case issue, we don’t need to support a string inside arrays, just inside a list.

Absolutely, I agree with you. The string type should be an intrinsic type to the language, fully implemented to work for scalar or array variables, just like every other type. However, I would prefer that it allowed a jagged array, because that is a lot more useful for strings as opposed to something like real or integer.

I was saying I wanted the correct string element printed, but wouldn’t be that surprised if it ended up being just the second character, based on the statement “the current rules of the language don’t allow…”

It wouldn’t be a bad thing for a new, modern string type to effectively replace all use cases currently for character variables of length greater than 1. That is fine, and I’m not saying we should be removing any functionality. This new intrinsic type can be better than what we had before. That would be ideal.

Perhaps all this means that the syntax looks like normal Fortran arrays ‘string :: var(3)’ but actually under the hood arrays of type string we’re using a list construct, if that made things easier to implement.

1 Like

So then adapt the rules toward array constructor syntax: if done properly by the standard bearers, the enhanced array constructor can prove very useful in other contexts also e.g., with ALLOCATABLE components as mentioned by @RonShepard .

There is no issue with the question in the snippet, “where does the second element start, and how big is it?” once the array object is suitably defined.

That’s the thing. You have to find all the places in the nearly 700 page document that need to be adapted, and make sure that they actually can be adapted in a consistent way. I’ve given just one example where it seems the rules would not be consistent and I didn’t have to look or think that hard about it. There are very likely others.

1 Like