Java To C To Fortran Interfacing
(used for Multithreading)
|
1. Introduction
2. Overview
3. Java Native Interfacing
3.1 Writing the Java code
3.2 Generating the Class file
3.3 Generating Function Prototypes
3.4 Building the C Stub File
3.5 Writing the C Source File
A. Function specification and mapping
B. Data type
C. Argument handling
3.6 Building Native Libraries
4. C to Fortran Interfacing
4.1 Argument Types
4.2 Subroutines and Functions
5. Fortran Compilation Allowing Multithreading
6. Summary
1. Introduction
You'll find here an extract of my my MS
Thesis, giving enough information to build your own Java to
C to Fortran interface. If you still need a hand just mail
me ! . I made this documentation back in February 1999, by that
time I used my CIS professor in KSU,
and some other materials you may still fin on the web and on other deadtree
books.
2. Overview
Java includes features to import C code, features
called Java Native Interface JNI. But as it is not possible to directly
reference Fortran from Java, we established an interface between Fortran
and C. It was then possible to indirectly call from Java, passing through
C, the subroutines implemented in Fortran. We will cover in this section
the detail of this interfacing.
Let us first have a look at the condensed version
of the different steps involved in this Java to Fortran (through C) interfacing:
1. Write the Java source file, declaring
the native method and specifying the libraries to be loaded.
2. Run Java source file through javac to produce
the class file.
3. Run the Java class file through javah to produce
the header file.
4. Run again the Java class file through javah
to produce the C stub file.
5. Write the C source file according to the function
prototypes generated by javah, importing the header files generated by
javah, and according to Sun conventions and ANSI Standart function prototypes
that allows cross language communication between C and Fortran.
6. Write the Fortran source files that the C
file references.
7. Compile the C source and stubs files.
8. Compile the Fortran source files with the
option that forces local variables to be allocated on the memory stack
so that parallelization can be ensured.
9. Create the shared object (the dynamic library)
referencing all the source files.
10. Run java on the Java class file ( it will
then load once, at run Time, the dynamic library created at the previous
step and the F77 and sunmath libraries thanks to System.LoadLibrary Java
API).
Those words are turned into a picture in the
figure1 below.
 |
Figure 1: Steps to integrate
the C and Fortran Native codes
3. Java Native Interfacing
We used the JDK 1.0 approach (the native method
support), as it was still supported by the Java version we used. Some may
say that the JDK 1.0 solution has a major shortcoming [CIS 4]. It makes
indeed assumptions about how the JVM laid out Java objects in memory. Since
native code may directly reference fields in a Java Object, any change
to this layout on the part of the JVM would require that native method
libraries be recompiled. Further, different implementations of the JVM
for a single platform could arrange objects in memory differently. However,
this is not relevant for us, as in the proposed implementation the native
code does not refer directly to the Java objects.
We will cover here the different Java Native interfacing
steps, corresponding to those listed in section 1 and depicted in Figure
1.
3.1 Writing the Java code
Listing 1. The native method declaration class, FortranCall.java
Declaring a native method within a Java class
is as simple as the use of the native attribute keyword. A class can signify
that any methods will be implemented with native code simply by preceding
the method name with the native keyword and not supplying the body of the
method. So we defined a class named FortranCall referencing the five Fortran
subroutines the java application needs to call. Here is the source code.
Class FortranCall
{
public native int initgrid();
public native void work(int id, int nstep);
public native void borderInfo(int id, int neighbor);
public native void shadowInfo(int id, int neighbor);
public native int updateInfo();
static
{
System.loadLibrary(“fortran”);
System.loadLibrary(“F77”);
System.loadLibrary(“sunmath”),
}
}
|
Listing 1.
The reader may recognize in the last lines of
code the general construct static, which is a static class initializer.
Any code between the brackets is executed only once, when the class is
first loaded onto the system [9]. This is taking advantage of the fact
to run something we want to run only once –the loading of the native library
and the SUN Fortran libraries F77 and sunmath. This ties together the loading
of the class itself with the loading of its native method code. If either
fails for some reason, the other fails as well, guaranteeing that no “half
set up” version of the class can ever be created.
Note that in order for System.loadLibrary to find
your native library, the directory in which the shared object library file
resides must appear in the LD_LIBRARY_PATH environment variable.
3.2 Generating the Class file
Once classes are defined in Java source files, you
obviously need to run javac to generate class files.
3.3 Generating Function Prototypes
Using javah as follows:
% javah FortranCall
It generates an header file that defines the
functions prototypes. The C native code will refer to it
3.4 Building the C Stub File
You create this stub files running javah again
with the –stubs option. The stub file created consists of wrappers
around invocations of native functions.
3.5 Writing the C Source File
While writing the C source file, we have to
follow a mapping between C Function and Java Native method types:
A. Function specification and mapping:
the C Function names are following the
Java Native Interface convention and the header file prototype functions
definitions. For instance:
public native void borderInfo(int id, int neighbor)
is translated into:
void FortranCall_work(struct HFortranCall *this, long id, long neighbor)
The function name is built as following,
first the Java_Class_Name, then underscore, then the
Java_Native_method_Name.
B. Data type:
JNI also defines the Java Native Types
which should be used by all native code. JNI defines types which map to
Java primitives types and reference types. javah generates a struct typedef
for each Java class referenced by the native code. Under this scheme Java
objects are treated as C structures, and Java primitive types mapped to
C scalar types. We mapped the Java int into a long C, but it depends on
the platform.
C. Argument handling:
A macro is needed to change a C struct
into something that looks and feels like an object. Referring back to the
FortranCall_work, the macro will be applied to the argument of type HfortranCall
(which is the struct typedef generates by javah), before using it as an
object
3.6 Building Native Libraries
Usually compilation of the native code
and building native libraries are done in a single command :
%cc –I$JDK_HOME/include–I$JDK_HOME/include/solaris –G –o libNative.so nativeImpl.c nativeStubs.c
The cc command, with –G option, will
generate a Shared Object Library. The lib prefix and so suffix are added
to satisfy both a Solaris convention and a Java requirement. The –I searches
the directories specified in order to point to the right places for inclusion
of native interface .h files.
We use a different method:
1. First we compile the C native
implementation file, the stub file and the Fortran source files. When compiling
the C code, we still need to point to all the right places for inclusion
of StubPreamble.h, which provides information the C language code requires
to interact with the Java runtime system.
2. Then we used the link editor
ld with the –G option to produce a shared object, which we name according
to Java and Solaris specification and according to the name we specified
in System.loadLibrary() in the Java source file.
%cc –c –I$JDK_HOME/include–I$JDK_HOME/include/solaris –G –o FortranCallImp.c
%cc –c –I$JDK_HOME/include–I$JDK_HOME/include/solaris –G –o Fortran.c
%f77 –O –stackvar –c *.f
%ld –G –o libfortran.so *.o –lF77 -lsunmath
Doing so allows us to create and link the
dynamic library (libfortran.so) and the Fortran libraries F77 and sunmath
used to make the C to Fortran interfacing covered in the next section.
4. C to Fortran Interfacing
The ANSI standard definition of C provides
a powerful argument checking facility that, given the correct definitions
of function prototypes, can facilitate cross language communication between
C and not only Java but also Fortran. We will explain in this section how
Fortran routines are called from the C functions and procedures which,
remind, are themselves called from Java. The convention we applied are
particular to Sun and other similar systems (different conventions are
used for VAX/VMS and a NAG C header file can be used for C compilers that
do not support the ANSI Standard function prototypes [6])
4.1 Argument Types
There exists a mapping between relevant Fortran
and C argument and function types. We use long in C to map the Fortran
INTEGER type. This is the only type we use to pass parameters back and
forth from C to Fortran. Note also that all arguments are passed as pointers
to a variable.
4.2 Subroutines and Functions
Fortran routines are declared as void functions
in C. Procedure arguments, i.e. function or subroutine names, are passed
by address in the normal C manner. Most Unix systems as the one we used,
requires the addition of an underscore to the name of Fortran routines
called from C. e.g. in the above Listing (Listing 2) do_work becomes do_work_.
[WEB 6, 7, 8]
#include <StubPreamble>
#include “FortranCall.h”
#include <stdio.h>
extern void initialization_(long *);
extern void do_work_(long *, long *);
extern void get_border_(long *, long*);
extern void update_shadow_(long *, long *);
extern void pdf_output_(long *);
long FortranCall_initgrid(struct HFortranCall *this)
{
long numstep;
initialization_(&numstep);
return(numstep);
}
void FortranCall_work(struct HFortranCall *this,
long id, long neighbor)
{
get_border_(&id,&neighbor);
return;
}
void FortranCall_borderInfo(struct HFortranCall *this,
long id, long neighbor)
{
update_shadow_(&id,&neighbor);
return;
}
void FortranCall_shadowInfo(struct HFortranCall *this,
long id, long neighbor)
{
update_shadow_(&id,&neighbor);
return;
}
long FortranCall_updateInfo(struct HFortranCall *this)
{
long iover;
pdf_output_(&iover);
return(iover);
}
|
Listing 2.
5. Fortran Compilation Allowing Multithreading
The Fortran compilers allocate by default
local variable and arrays as STATIC (not on the stack). However, the -stackvar
option allows us to force allocation of all local variables and arrays
on the stack (as if they were AUTOMATIC variables).
Variables are then local, unless they are:
Arguments in a SUBROUTINE or FUNCTION
statement (already on the stack)
Global items in a COMMON or SAVE, or STATIC statement
Items initialized in a type statement or DATA
statement. Note that initializing a local variable in a DATA, statement
after an executable reference to that variable is flagged as an error when
-stackvar is used.
Each thread of the multithreaded program has
its own thread stack. This stack mimics the main program stack but it is
unique to the thread. The thread’s PRIVATE arrays and variables (local
to the thread) are located on the thread stack. The default stack size
in Fortran is about 256 kilobytes for each thread stack and about 8 Megabytes
for the main stack.. Setting the thread stack size value larger than the
default was necessary for most of the implementation test cases. However,
it is not possible to know just how large to set it, except by trial and
error, especially as large private/local arrays are often involved. If
the stack size is too small for a thread to run, the program will abort
by a segmentation fault and a very large core dump. Thus the user of the
proposed implementation should know how to set the stacksize and how to
limit the size of the core dumped.
For example, the limit command with no parameters
shows the current main stacksize:
%limit
cputime unlimited
filesize unlimited
datasize 523256 kbytes
coredumpsize unlimited
descriptor 64
memorysize unlimited
The following commands set the main stacksize
unlimited, and set each thread stack size to 8 Megabytes:
%limit stacksize unlimited
%setenv STACKSIZE 8192
6. Summary
We have presented in this section the Java
to Fortran (through C) interfacing. Listing 5.2-3 shows the script file
we used to achieve the different compilations and interfacing presented
in this section.
#!/bin/bash
javac SparseGrid.java
javah FortranCall
javah -stubs FortranCall
cc -c -I /apps2/JDK1.1.new/include -I /apps2/JDK1.1.new/include/solaris FortranCallImp.c
cc -c -I /apps2/JDK1.1.new/include -I /apps2/JDK1.1.new/include/solaris FortranCall.c
f77 -O -stackvar -c *.f
ld -G -o libfortran.so *.o -lF77 -lsunmath
|
Listing 3. The compilation
script file
Note that we also used the Fortran compiler -c
option to optimize the compilation
This technique has been successfully applied for
my MS implementation. It is a significant achievement in the sense that
it provides the developer with a means to exploit Java parallelism features
(but also Java GUI features) while still using their long running and well-validated
Fortran legacy code. However such interfacing is highly platform dependent
and great attention has to be paid and sensible modification could be required
if one intended to port this implementation onto another platform.
|