Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

gfortran: pass logical argument to Fortran function from C

What argument type should I use in C when calling a Fortran function that takes logical arguments, specifically with gfortran? Where is this documented for gfortran?

Here's an example program that doesn't compile without warnings:

Contents of one.f:

      subroutine proc1(x)
      logical x
      end

Contents of main.c:

void proc1_(_Bool *x);

int main() {
    _Bool x;

    proc1_(&x);

    return 0;
}

If I compile using GCC as follows, with LTO enabled, I get a warning about mismatching function prototypes:

gfortran -flto -c one.f
gcc -flto -c main.c
gcc -flto main.o one.o

The warning I get:

main.c:2:6: warning: type of 'proc1_' does not match original declaration [-Wlto-type-mismatch]
    2 | void proc1_(_Bool *x);
      |      ^
one.f:2:22: note: 'proc1' was previously declared here
    2 |       subroutine proc1(x)
      |                      ^
one.f:2:22: note: code may be misoptimized unless '-fno-strict-aliasing' is used

Note that enabling LTO allows the linker to verify that argument types match between prototypes. Using LTO is unfortunately not our choice. CRAN requires the submitted code to compile without these warnings with LTO enabled.

I only see problems when trying to use logical arguments. real, integer and character are all fine.

gfortran can be asked to produce C prototypes, and this is the output it gives me:

gfortran -flto -fc-prototypes-external -c one.f
void proc1_ (int_fast32_t *x);

Using int_fast32_t in the C prototype doesn't work either. No type that I tried did, neither int, nor _Bool. Usually, when there is a type mismatch between prototypes, the error message mentions what the type should be—but not in this case.

How can I find what is the correct type to use?

like image 964
Szabolcs Avatar asked Nov 02 '21 18:11

Szabolcs


People also ask

Does Fortran pass by reference?

In general, Fortran routines pass arguments by reference. In a call, if you enclose an argument with the f77 and f90 nonstandard function %VAL() , the calling routine passes it by value.

How do I call C from Fortran?

To actually call a C function from Fortran, first the interface must be declared. This is essentially equivalent to the C function prototype, and lets the compiler know about the number and type of the arguments, etc.

What is C in Fortran?

A most peculiar feature of Fortran 77 is its line structure, which is a carryover from the old days when programs were typed on punch cards. A punch card had 80 columns, and so does a line of Fortran code. A "c" in column 1 indicates a comment (similar to REM in Basic).

How do you define a function in Fortran?

The purpose of a function is to take in a number of values or arguments, do some calculations with those arguments and then return a single result. There are some functions which are written into FORTRAN and can be used without any special effort by you, the programmer. They are called intrinsic functions.


2 Answers

For real modern C-Fortran interoperability you should use the types (kinds) supplied by the iso_c_binding module and make your Fortran procedure bind(c). That way you can use logical(c_bool).

In the old style the best thing is to work with integers and pass an int and only correct from integer to logical inside Fortran. Old C did not have any bool, it was added later.

With minimal changes:

      subroutine proc1(x)
      use iso_c_binding
      logical(c_bool) x
      end
#include <stdbool.h>
void proc1_(bool *x);

int main() {
    bool x;

    proc1_(&x);

    return 0;
}
> gfortran -flto -c one.f
> gcc -flto -c main.c
> gcc -flto main.o one.o

issues no warning on my Linux and GCC 7 and 10.

Or after further changes:

      subroutine proc1(x) bind(C, name="proc1")
      use iso_c_binding
      logical(c_bool), value :: x
      end
#include <stdbool.h>
void proc1(bool x);

int main() {
    bool x;

    proc1(x);

    return 0;
}

The change to pass-by-value of course only when it is indeed just an input parameter.

like image 96
Vladimir F Героям слава Avatar answered Nov 14 '22 23:11

Vladimir F Героям слава


The correct and guaranteed to be portable solution is, as explained in the answer by Vladimir F, to create a Fortran wrapper routine that uses ISO_C_BINDING. This wrapper can also take the opportunity to make a more idiomatic C interface, e.g. using the value specifier to pass scalars by value.

However, for the quick and dirty solution that works on GFortran WITHOUT LTO (and somewhat likely on other compilers, but no guarantees), see https://gcc.gnu.org/onlinedocs/gfortran/Internal-representation-of-LOGICAL-variables.html#Internal-representation-of-LOGICAL-variables . That is, you can pass a C integer variable of the appropriate size containing 1 for true and 0 for false. Appropriate size here meaning that unless you have compiled your Fortran code with -fdefault-integer-8 or such compile options, the GFortran default kind logical will be 4 bytes, so a plain C int should be good (or int32_t if you really want to be sure, though I don't think GFortran supports any targets where the C int is not 32 bits).

The reason this doesn't work with LTO is that while the above works, in the bowels of GCC the Fortran LOGICAL variables are almost the same as integers, but not quite. So in practice they are special integer variables with max value 1 and min value 0 even though they take up more space (as specified by their kind parameter). So this kind of type mismatch is likely what it complains about. Unfortunately no solution to this one, except the above correct solution via ISO_C_BINDING.

like image 43
janneb Avatar answered Nov 14 '22 22:11

janneb