C interoperability with assumed-shape arrays

Are assumed-shape arrays interoperable with C?

For example, is it possible to write a C interface so that the timestwo subroutine is available in C? (This is a simplified example.)

module as_mod

contains

subroutine timestwo(x)
implicit none
real(kind(0.0D0)), intent(inout) :: x(:)
x = 2.0D0*x
end subroutine timestwo
end module as_mod

Many thanks. It would be great if someone could provide such an interface if possible.

Yes, you need a Fortran 2018 compiler or one that supports ISO/IEC TS 29113:2012 Further Interoperability of Fortran with C. Current versions of Intel Fortran in OneAPI or GNU Fortran support this. See chapter 21 of Modern Fortran Explained for more details or consult the appropriate compiler documentation.

I think yes is the correct answer with the following caveat. You have to use the C descriptors from ISO_Fortran_binding.h on the C side to define the array. I personnaly don’t see any advantage for your example of assumed shape arrays over assumed size but I guess you have a reason for wanting to use assumed shape.

In general, assumed shape allows noncontiguous arguments, while assumed size effectively requires contiguous arrays. That means the compiler might be required to do copy-in/copy-out argument association, which requires a relatively expensive memory allocation/deallocation step for the temporary array.

Thank you @rwmsu for the information. Does this mean that x cannot be a common C array but a special one defined by the C descriptor? I would not be surprised since assumed-shape arrays carry information like size, lower/upper bounds of indices, whereas a vanilla C array carries only the address of the initial element.

Thank you @RonShepard for pointing this out. I also hope compilers will verify the shape and type of the arrays at compilation time, which can be achieved by using assumed-shape arrays.

See this thread for a fully worked out illustration and commentary.

1 Like

My biggest problem with ISO_Fortran_binding.h and assumed shape arrays is that it forces me to write more C code than I need. To me that defeats the purpose of the original idea behind the Fortran C-Interop facility. Basically, if you know the C function prototype you can call a C function without writing any additional C code. Conversely, on the C side all you have to do is write a conforming function prototype for the Fortran routine and call it without resorting to convoluted name mangling. However, I’ll admit that 99.99% of my forays into C-Interop has been Fortran calling C so I rarely see a need for writing another C wrapper around my target C routine just so I can use assumed shape arrays.

If your C code is required to interoperate with fortran assumed shape arrays, then it must conform to however the fortran compiler passes those arrays. Conversely, if your fortran subroutine that uses assumed shape dummy arguments is called directly by your C code, then it must pass the necessary information to the fortran subroutine. There really isn’t any other way, right?

To me, the big problem is the portability issue. Each fortran compiler has its own conventions, so just as it is not possible to write portable object libraries that use assumed shape arrays among fortran compilers, when you include C interoperability, each fortran/C combination requires its own special treatment. For fortran code, the lack of portable *.mod file formats are part of the problem too. Is there any possibility that this can ever be standardized, even among fortran compilers, much less when fortran/C interop is included?

@rwmsu, what do you mean by “name mangling”? See the example I provide in the thread I linked above. Where is the name mangling?

Re: “To me that defeats the purpose of the original idea behind the Fortran C-Interop facility” - no, it does not. Take a look at the example I provide. The code in Fortran is compact and highly functional and thus at a higher level of abstraction. Whereas it is at a low level in C. This is exactly as one would want. Subliminally, it should inform and influence scientists and technologists and engineers to move more of their coding to a higher level language of Fortran offering efficient computation with functional offering of containers of their data rather than raw pointers and user-defined structs to work with them, like in C.

I agree it cuts both ways, but overall my vision will be to take Fortran higher with about 6 to 10 more facilities and abstractions toward them, as I have listed here. Then if other languages want to interoperate with the abstractions in Fortran, a few more coding instructions in a companion processor (such as C) is what shall be on the anvil.

1 Like

Name mangleing . Prior to C interop the names of the C routines called from Fortran had to be either (1. annotated with either one or two trailing underscores (Linux mostly), 2. be all upper case (Crays if I remember correctly) or all lower case (IBM). The same was true in reverse. You had to “adorn” the actual Fortran name in some fashion (except maybe on IBMs). Also, you had to write explicit C wrapper routines to handle the differences in array indexing, how data is passed etc. Because it wasn’t the same on all systems you had a rat’s nest of #ifdefs that were specific to each OS and it was a code maintenance nightmare. F2003 C-Interop changed that. My personal preference is to write everything in Fortran and NEVER have to soil my hands with C code or any of its illegitimate progeny. Sadly, that is not how the world works particularly with 3rd party libraries and you are sometimes forced into using something because it just doesn’t exist in Fortran.

If you use C++17 (or higher), you can have high level code just like in Fortran.

#include <array>
#include <numeric>

#include "Fcpp.h" // <-- C++ descriptor adaptor

// Fortran routine
extern "C" void sub(CFI_cdesc_t *);

int main(void)
{
    using namespace Fcpp;

    std::array<int,5> x;
    std::iota(x.begin(),x.end(),42); // or std::ranges::iota(x,42) in C++23

    sub(desc(x)); /* Create adaptor on the fly */

    return 0;
}
~/tmp$ gfortran-13 -c m.f90     # module providing routine sub
~/tmp$ g++-13 -Wall -std=c++17 wrapper.cpp m.o -lgfortran
~/tmp$ ./a.out
 In Fortran sub: size(x) =            5
          42          43          44          45          46

How does it work? Well you create an adaptor using a templated class; all the ugly work is hidden in the constructor. Click on the little arrow (triangle) for a look at the internals:

Fcpp - Adaptor between C++ and Fortran
#include <cassert>
#include <array>
#include <vector>

#include "ISO_Fortran_binding.h"

namespace Fcpp {

    namespace {

        // Type converters
        template<typename T>
        constexpr CFI_type_t type(){ return CFI_type_other; };
        template<>
        constexpr CFI_type_t type<float>(){ return CFI_type_float; } 
        template<>
        constexpr CFI_type_t type<double>(){ return CFI_type_double; } 
        template<>
        constexpr CFI_type_t type<int>(){ return CFI_type_int; } 

    }

// Descriptor Adaptor - provides Fortran descriptor of a C++ entity
template<typename T>
class desc {
public:

    desc(T* ptr, int n0) {

        CFI_index_t extents[] = { 
            static_cast<CFI_index_t>(n0)};

        [[maybe_unused]] int status = CFI_establish(
            this->get(),
            ptr,
            CFI_attribute_other,
            type<T>(),
            sizeof(T),
            1,
            extents
        );

        assert(status == CFI_SUCCESS);

    }

    // Constructor from std::vector
    desc(std::vector<T> &buffer) : desc(buffer.data(),buffer.size()) {}

    // Constructor from std::array
    template<std::size_t N>
    desc(std::array<T,N> &buffer) : desc(buffer.data(),N) {}

    // Return pointer to the underlying descriptor
    constexpr auto get() const { 
      return (CFI_cdesc_t *) &desc_; 
    }

    // Implicit cast to C-descriptor pointer
    operator CFI_cdesc_t* () { return this->get(); }

private:
   CFI_CDESC_T(1) desc_;
};

} // namespace Fcpp
3 Likes

If there were more good Fortran libraries and software, potentially even interoperable ones, maybe some of the C/C++ programmers would reconsider Fortran, at least for numerical uses. Just look how long-lived the Fortran codes in SciPy are, or the LAPACK interface is (not the reference routines). Part of the C/C++ world still lives with the mangled name mess, ignorant of the fact it’s been solved since F2003.

From what I can tell, most compilers today are written in C++, including Fortran ones (e.g. LFortran, flang, GCC is mainly C but also some C++; vendors are rallying around LLVM which is C++). In other words we depend on the “illegitimate progeny” more than they depend on us. My personal opinion - if you can’t beat them, join them.

Actually my biggest issue is not with the languages but with what I consider an overdependence on 3rd party libraries in general. This comes from me being tasked to install a C++ framework on a DoD HPC system that relied heavily on 3rd party libraries. The base code would compile O.K. The libraries were another story. Some developer thought it was jolly fun to require a specific version of a system library (this was under Linux so a .so file). Since I didn’t have sys-admin priveleges and the folks that did where not in the mood shut down a multi-million dollar system just to install one library that could potentially break the whole installed OS, I was stuck with trying to explain why I couldn’t get the software installed. I have seen several examples of people releasing a code that relied on third party libs in which they might just pull one function from the lib that was small enough and simple enough that they could have written it themselves and added to their base code. I know modern package managers are a big help in solving dependency problems but you do your customers (the people you want to use your software) a big disservice if you force them to build a whole bunch of libraries in addition to your base code. I know of one Fortran FEM code used for weapon penetration problems that effectively reversed engineered the output from the Sandia EXODUS libraries that are built on top of netCDF and HDF5 to write a compatible file just from pure Fortran because the developers didn’t want their customers to have to install EXODUS, netCDF and HDF5 just to use their code. I’ll admit I’m old and set in my ways so I’ll always prefer a pure Fortran solution over one that forces me to build 10 libraries in 3 different languages just to use the base code.

1 Like

That’s a problem I agree. I listened recently to Mike Heroux speak about the work which went into the E4S software stack (for the DOE Exascale Computing Project), just to have a common set of base libraries across DOE sites.

Maybe one day a C/C++ - Fortran interoperability library could be part of it.

@rwmsu , what does that have to do with standard interoperability with C, especially the with Fortran 2018 that facilitates descriptors with the companion processor for assumed-shape array arguments, the topic of thread?

@ivanpribec , in this context, what specifically do you mean by, “if you can’t beat them, join them.”? What would that translate to, say, for OP or any other practitioner of Fortran?

The name mangleing issue is one of the main reasons the Fortran C-Interop facility was created in the first place so it has everything to do with “standard interoperbility with C”.

I see absolutely no evidence the above quoted statement above is accurate. Sure with bind(C, clause, the standard offered an option to mitigate the issue. However the interoperability with a C companion processor introduced starting Fortran 2003 addresses a variety of other aspects, especially with interoperable types, that needed to be addressed. It was the entire concept of mixed-language programming for which there was demand and the vendors were doing it one way or the other anyway that became the reason to standardize.

I agree with @rwmsu that the name mangling was one of the main reasons. One of the other main ones was fortran character variables, which were treated in various ways in fortran compilers with hidden arguments and descriptors. However, through the 1980s and 1990s, the main demands for interoperability were not so much with programs or user libraries written in C, but rather access to unix/POSIX operating systems, where the APIs were defined in terms of C (and practically, most were actually written in C). However, by the early 2000s, that was no longer true. By then, fortran access to various user libraries, such as GUI interfaces (X11, Tkl, etc.), had also become important.

1 Like