Today, I did the unthinkable: I got all of my tools to cooperate.
The application we're working on is primarily driven by computation done in Fortran, with some bulky operations parallelized with CUDA in C. Getting it to work was not exactly the easiest thing ever. There are a number of things to take into consideration:
- Fortran symbol mangling
- Data type equivalency
- Fortran passes by reference
- Function or Subroutine?
- Row-major vs. column-major arrays
There are a number of other things to consider, but those were the main stumbling blocks that I came across.
Fortran Symbol ManglingFortran isn't case sensitive like C, so the Fortran compiler forces all symbol names into lowercase. This can be changed with a compiler option, but I am perfectly okay with lowercase C function names. If you absolutely must have camelCase function names, take a look at the -Mupcase option for the Fortran compiler. Fortran also adds an underscore to all function and subroutine names. So if you want to call a function in Fortran (e.g. add()), the function in your C source should have an underscore appended (add_()).
Data type equivalencyThis is going to be specific for each Fortran implementation. HP's Fortran has different data type equivalency than the PGI Fortran. If you take the time to make sure that you use equivalent data types, you will have a much easier time of getting Fortran and C to play together. Refer to your Fortran documentation--they should include a table that indicates which data types you should use.
Fortran passes by referenceThis was a point of contention with the professor I work for. He speaks Fortran, and I speak C (though, I speak a little Fortran now). When he saw all of my pointers in the source, he was very confused, and almost ditched the idea of cross-language interoperation altogether. I was a little flummoxed by this, but discovered something key: hide your C from your Fortran-speaking professors! :) Seriously, though, the implications of pass by reference are that all of your function arguments should be pointers.
Functions or Sub-routinesI had a hell of a time here. Theoretically, you should be able to return values from the C functions that you're calling from Fortran. This didn't work for me in practice. This may very well be my fault, but I ended up treating all of my C functions as sub-routines, and I'm perfectly okay with the outcome. This means that in your Fortran code, you will need to call the subroutine and pass the variable in which you wish to store the return as an argument.
Row-major vs. column-major arraysFortran stores its array members in column-major order instead of row-major order. This means that if you want to access element (1, 2, 3) in a Fortran array, you would access it as array[2][1][3] in C. Since we're being passed a reference to the beginning of the array, we'll be doing a little bit of simple math to find the address of the element we want to access:
index = z*dim*dim + x*dim + y
Whereas in C we would normally calculate this as:
index = z*dim*dim + y*dim + x
To get an idea of how all this comes together, take a look at the following examples. Let's say you want to perform some simple addition in C instead of in Fortran. First, you need to write your C (remember what we've already discussed about references, name mangling, etc):
int add_(int *a, int *b, int *result); {
*result = *a + *b;
}Then compile with gcc -c add.c. This will give you add.o--the object file you'll be linking into your Fortran application. From here we write our Fortran.
integer x, y, sum
x=1
y=1
add(x, y, sum)
print *, sum
Now compile this and link it with your add.o object file. We use PGI Fortran, but the syntax is similar pgf90 add.o add.f90. Now just run ./a.out.:
$ ./a.out
2
$
Obviously a contrived and very simple example, but it should be enough to get you going.