1 Portability Strategy for Portable Fortran Code ================================== D. McCune, C. Karney, C. Ludescher, A. Pletzer, L. Randerson -- Apr. 1999 PPPL Computational Plasma Physics Group -- work funded by U.S. DoE. Last Update -- D. McCune 26 July 1999 This document describes a set of tools for making fortran code portable between machines. The emphasis is on the conversion of legacy codes written in fixed format fortran-77. If you want to use these tools without knowing all the details: please read the introductory sections up through "Quick_Start_Guide". 2 Design_Philosophy This document describes a reasonably comprehensive strategy for the conversion of legacy Fortran code to a form which is portable to a wide variety of machines. As will be seen, portability concerns impose many constraints; satisfying all of these simultaneously is not always a simple task. Even so, there are a number of solutions. One method is presented in this document. The philosophy of the strategy presented here is based on the following principles: * compatibility with reuse of legacy fortran-77 code without requirement of extensive rewrite. * useful in context of mixed language codes containing C, C++, fortran-77, and fortran-90. * programmer continues to work with "familiar" languages: fortran-77, fortran-90, cpp, with C and C++ used only if required by the application itself. The C pre-processor language (cpp) is used for conditional compilation of machine specific code, and machine- dependent macro expansion. * portability considerations should not force a programmer to maintain a "private copy" of standard math library routines such as those contained in BLAS and LAPACK. However, mathematical tools, for example the "Slatec" library, may be provided as NTCC modules, if the standard distributions contain portability problems. The emphasis will be on portability "within the Unix world", although the techniques presented would not be incompatible with maintaining a single source that also builds on VMS, NT, or other non-Unix platforms. In fact, the methods described have been used to implement a portable version of TRANSP (a very large tokamak physics data analysis code system with 500K lines of code, much of it ancient). This code now runs on both VMS and many brands of Unix. Note also that the portability techniques described here are based on methods that have been tested and found to work-- but may not be 100% standards conforming in all respects. Although 100% adherance to ANSI standard fortran-77 is certainly one way to assure portability, we find this approach is seldom feasible in practice. In any case, the main concern is methods for achieving a practical portability for legacy fortran codes. These codes, as received, are essentially never in 100% conformance with ANSI standards. Even so, programmers are urged to stick to the standards wherever possible. In codes where non-portable vendor-specific methods for such things as compound data types or memory allocation have been used, it is generally not possible to achieve portability without rewrite to a (more nearly) standards conforming programming approach. One good solution in such sitations would be a port to fortran-90. Difficult porting problems do arise. This document cannot cover all issues. Some codes, which rely on a non-portable vendor specific subroutine libraries, might not be feasibly portable at all. Development of portability strategies is an ongoing effort. Suggestions for improvements are welcome. Such suggestions can be sent by Email to PPPL participants in the NTCC Modules Library Project: Doug McCune -- dmccune@pppl.gov Tina Ludescher -- ludesche@pppl.gov 2 Summary_of_Issues Portability issues come up in several ways: * portability of the fortran source code itself, * portability issues related to mixed language programming, * access to portable macro preprocessing of fortran source code, * "makefile" issues -- machine dependence in the process of compilation and loading of code binaries. Each of these sets of issues will be considered in separate sections. It should be emphasized that the proposed solutions to porting problems are not unique, and that they are also a matter of ongoing development. Users and programmers may choose other methods. However, achievement of genuine portability is not as simple as one might expect, and yet is a matter of considerable "economic" importance if one is concerned with the ability to share and reuse code -- especially, legacy fortran code. Programmers desiring a more sophisticated and thorough approach to portability, documentation and other programming aids... may want to look at FWEB: http://w3.pppl.gov/~krommes/fweb_toc.html Another approach to portability is to use the LLNL Basis software, see: http://basis.llnl.gov 2 NTCC_Portability_Modules Reference will be made to codes and portability tools which are available in the National Transport Code Collaboration (NTCC) Modules Library; NTCC library codes can be downloaded from the website at http://w3.pppl.gov/NTCC The NTCC Modules Library Project is funded by the U.S. Department of Energy (Fusion Energy Sciences Office). The following NTCC modules are of interest in code porting work: * portlib -- a library of interfaces to operating system functions (such as "getenv", "getcwd", "system", etc), which hides operating system dependency from the user code. * fpreproc-- a set of python scripts and cpp ".h" files that allow the use of cpp for preprocessing of fortran sources, to support conditional compilation and macro substitution, which give methods for implementing portable code from a single source. * ftoken -- this is a small collection of programs based on a subroutine library "ftoken" which detects symbols or "tokens" in fortran code, while screening out comments and quoted strings. The programs included in the ftoken module are useful for portability conversions. These programs are: => fgtok -- program for wholesale manipulation of names and declarations in fortran codes (mainly: fixed format fortran). The `fgtok' program, with accompanying name substitution tables, can perform such operations as => perform any user specified table driven token (name) substitution operations (for example, change from using single precision "nag" to double precision "nag" subroutine calls). => convert all floating point declarations to "REAL*8" and "COMPLEX*16". => declare "IMPLICIT NONE" in all routines. => convert all floating point constants to a common standard form. => convert all non-generic intrinsic function references to their generic form. => "detab" source code. => check that all source lines satisfy the 72 character length limit. => idecl -- a program for generating the declaration of formerly implicitly declared items in fixed format fortran source. Reason: when converting code for greater portability, it is often a good idea to convert it to use IMPLICIT NONE. But that requires declaring all previously implicitly declared names. The idecl program does this. => cpcheck -- a program that scans a named fortran source looking for macros specified in a named cpp #include file; it simply writes "present" if macros are found, "absent" otherwise. => r8real -- a program that automatically generates an interface, written in fortran-90 using copies from/to dynamically allocated arrays, which allows a REAL*8 code to call subroutines in a library written with REAL arguments, or, conversely for REAL code to call subroutines in a library written with REAL*8 arguments. => cface -- a program that generates a C-callable interface to a fortran routine that contains INTEGER, REAL, REAL*8, and CHARACTER*(*) arguments. The interface routine uses portlib to convert between C null terminated byte strings to fortran character strings. => iface -- a program that generates "interface modules" which a fortran-90 program can "use" for compile time checking of subroutine arguments. Also it can be used to group several specific routine interfaces under one generic name. Further details on these codes are included below. 2 Target_machines The following machines have been designated as target machines in the porting strategy described here, and were used for testing. Supercomputers: Cray UNICOS systems (NERSC) Workstations: DEC alpha (Linux & DIGITAL UNIX),IBM,HP,SGI,SUN PCs: Intel Linux systems -- Portland Group Fortran or Fujitsu Fortran. A subset of our testing was done on workstation class VMS machines as well. We have not attempted to address issues of portability to MacOS or Windows environments-- we think systematic support of portability to these environments in addition to UNIX/VMS may prove expensive. 2 System_Prerequisites The portability scheme outlined in this document makes the following assumptions about target systems: (1) availability of gnu software: * gcc -- for the C preprocessor * gmake -- for makefiles (2) availability of standard mathematical freeware * BLAS * LAPACK In NTCC modules library submissions, it shall be presumed that module authors need not duplicate freely available software in their source distributions -- rather, the module author should document the dependency and indicate a source for the needed freeware, which on some systems may need to be downloaded and installed. The SLATEC library (a library of special functions: Bessel functions, etc.) presents a special case; a portable SLATEC special functions library (not the complete SLATEC) is available as an NTCC module. 2 Quick_Start_Guide For users wishing to try out the NTCC portability tools without learning about all the details, the following procedure is recommended. (1) acquire the NTCC portability tools-- download the portability modules "portlib", "ftoken", and "fpreproc" from the modules library at http://w3.pppl.gov/NTCC . PPPL users take note: the software is already installed on PPPL UNIX cluster machines in: /usr/pppl/ntcc/bin, /usr/pppl/ntcc/etc, and /usr/pppl/ntcc/include. Put /usr/pppl/ntcc/bin and /usr/pppl/ntcc/etc into your PATH. (2) use "fpreproc" to preprocess all fortran code (see subtopic for more information). (3) for Cray / Workstation compatibility, use the commands `r8_convert' and `r8_convert_include' to convert fortran-77 legacy code to REAL*8 precision (see subtopic for more information). (4) as the need arises, use "portlib" routines to replace non- portable calls to operating system services such as access to shell commands. The portlib library is covered in detail at the beginning of the following section, "Fortran_Issues". (5) use a carefully constructed Makefile to build your code. The Makefile invokes the preprocessor and encodes the system variations of compiler switches and the like. Examples are available from the NTCC modules library (e.g. the REAL*8 `lsode' module). (6) mixed language programming: use the "f77name.h" #include file and F77NAME macro, to portably make C functions callable from fortran code (see subtopic for more information). 3 fpreproc `fpreproc' is a script that enables the famous C preprocessor (cpp) to be used in a fortran friendly way: the preprocessor will not be confused by fortran comments, and the 72 character line width limit will be respected wherever possible. In addition, use of `fpreproc' with certain #include files (which can be automatically inserted into source codes with `r8_convert') allows the automatic conversion calls of common library routines (blas, lapack, linpack, nag) when moving code between CRAY and workstation environments. An example of interactive use of fpreproc follows. Note the source code is stored in a file named `foo.F' instead of `foo.f'. On a SUN workstation: > fpreproc foo.F foo.f -D__UNIX -D__SUN > f77 -c foo.f If `foo.F' contains lines like #if __UNIX [...UNIX specific code...] #elif __VMS [...VMS specific code...] #endif then the preprocessor will remove the #if/#elif/#endif lines and the VMS code, leaving only [...UNIX specific code...] The general form of the fpreproc command is: fpreproc sce.F sce.f [CPP options...] [-free|-132] where sce.F -- source file, input to preprocessor sce.f -- preprocessed source, output [CPP options...] -- generally, macro definitions such as "-D__UNIX" -- options for cpp [-free|-132] -- -free for free form f90 source; -132 for fixed form 132 character wide fortran source -- options for prefpp/postfpp perl scripts. Usually, fpreproc or an equivalent procedure is invoked through a makefile. Some common "macro" symbols used to distinguish environments: __UNIX -- UNIX __VMS -- VMS __SUN -- SUN workstations __ALPHA -- DEC ALPHA workstations __CRAY -- NERSC CRAY machines __SGI -- SGI machines such as the Origin-2000 __RS6000 -- IBM RS6000 workstaitons __HP -- HP workstations __LINUX -- LINUX workstations __F90 -- fortran-90 2 limitations The r8_convert tool was designed for precision conversion of fortran-77 "legacy" source code. While it may have applicability to fortran-90 sources as well, its usefulness is less assured and will depend on the programming style used for the sources in question. The tool was built for a "one time only" conversion of legacy code, and to deal with precision portability issues inherant in fortran-77. Unlike fortran-77, fortran-90 has within its standard good methods for dealing with portability of floating point precision. These should be used in preference to reliance on convertor tools. 3 r8_convert This powerful script is used for a onetime conversion of legacy fortran-77 code to a Cray/Workstation portable form. The code is convereted to REAL*8 / COMPLEX*16 precision -- the REAL*8 declaration is preferred, because, unlike "REAL" or "DOUBLE PRECISION", "REAL*8" has the same meaning on all machines. The r8_convert script (for source files) is invoked as follows: r8_convert input.f output.f [s-file(s)...] [CONST|CONST_R8] [k-file] \ [TEX] [NODECL] [NO-COMMENT] and the r8_convert_include script (for include files) as follows: r8_convert_include input.inc output.inc [s-file(s)...] \ [CONST|CONST_R8] [TEX] [NODECL] [NO-COMMENT] where input.f -- input source file output.f -- output, REAL*8 converted source file input.inc -- input source INCLUDE file output.inc -- output, REAL*8 converted source INCLUDE file [s-files(s)...] -- optional, substitution table files (see below). CONST -- optional: convert constants to CONST macro form 1.0 -> CONST(1.0,0) 1.602D-19 -> CONST(1.602,-19) (see below). CONST_R8 -- optional: convert constants using f90 convention: (a) declare "INTEGER, PARAMETER :: R8=SELECTED_REAL_KIND(12,100)" (b) append _R8 on every constant: 1.0 -> 1.0_R8 1.602D-19 -> 1.602E-19_R8 [k-file] -- optional, *not for INCLUDE files*: constants symbol definition file (see below). TEX -- optional: convert source only, in mixed TEX/fortran source file. NODECL -- optional: suppress automatic declaration of implicitly declared symbols (idecl). It is recommended to use this option if attempting to apply r8_convert to a fortran-90 source, otherwise some fortran-90 intrinsics may be mis- taken as symbols requiring automatic declaration. CAUTION -- r8_convert may expand some fortran-77 lines beyond the the 72 column limit. Any such line is flagged in messages written by r8_convert, but, it is left to the user to repair such lines with a text editor. When codes are under active development, r8_convert can be re-applied, e.g. to enforce a standard representation of floating point constants. 4 Substitution-table-files Substitution table files contains records of the form and can be used to convert a set of subroutine names and all corresponding calls. The substitution table files can contain comments starting with "#" in the first character position. For example: # map subroutine names, REAL names -> REAL*8 names pspline r8pspline bcspeval r8bcspeval ... 4 CONST_or_CONST_R8 By default, r8_convert maps all hard coded constants to D- exponent form, i.e. 1.0 -> 1.0D0 1.602e-19 -> 1.602D-19 Then, on CRAY machines, a compiler switch is used to force interpretation of these constants as REAL*8 precision numbers. Alternatively, the CONST macro can be used. Then, r8_convert does the mapping 1.0 -> CONST(1.0,0) 1.602e-19 -> CONST(1.602,-19) and then at compile time the preprocessor (fpreproc) expands CONST(1.602,-19) -> 1.602E-19 on CRAY machines, CONST(1.602,-19) -> 1.602D-19 on workstations, thus yielding code which is portable without reliance on a compiler switch. Another option-- and a good one-- is to rely on fortran-90. Using the CONST_R8 macro option will cause r8_convert to generate a declaration of INTEGER, PARAMETER :: R8=SELECTED_REAL_KIND(12,100) at the top of each program unit, and then, map all floating point constants to one of two forms, as illustrated by these examples: 1.0 -> 1.0_R8 (no exponent) 1.602D-19 -> 1.602E-19_R8 (exponent, "E" symbol enforced). Note: when choosing CONST_R8 or other constants mapping option, all forms of constants, including the CONST(n.nn,m) constructs if already present in the input source file, are mapped to the chosen form. 4 constants-symbol-definition-files It can be convenient to replace commonly occurring constants with a symbolic name (parameter). This has two advantages: (a) improves readability of code, relative to CONST(...,..) or _R8 constructs, and (b) may reduce line length relative to other constructs. The latter aspect is especially useful when working against a 72 character length limit processing fixed form source. This file has the same syntax as a substitution file, but the interpretation is different: leading to all constants matching to be replaced by the , and, causing generation of a declaration of , in fortran-90 style, at the top of program units where it is used. Here is an example of output resulting from using a constants file ("k-file") in conjunction with the CONST option: subroutine dpowers(w,akzt,ichi,jpsi,zdRovR,lposabs,kinetic, % pwrden,pwrspc,pwrmc,pwre,pwrh,cnprp2,emtoep) ! REAL*8, PARAMETER :: ZERO = CONST(0.0,0) ! _fgtok_constant_ REAL*8, PARAMETER :: HALF = CONST(0.5,0) ! _fgtok_constant_ REAL*8, PARAMETER :: ONE = CONST(1.0,0) ! _fgtok_constant_ ... ... cnmag2=(ONE,ZERO)*cabs(cnprp2) tprp0=(ZERO,ZERO) tcrs0=(ZERO,ZERO) (these are complex number assignments) without using this option, one would have seen lines such as cnmag2=(CONST(1.0,0),CONST(0.0,0))*cabs(cnprp2) which are longer and a bit harder to read. Too suppress the generation of declarations, which one should do e.g. if planning to use an f90 module to define constants, the k-file should include the line $DECLARE NONE the default is to declare constants as REAL*8. What follows is a copy of "fgtok.constants", a sample k-file that is distrubuted with the ftoken module: # # this is an example of an fgtok "symbolic constants" file # constants can take several forms: here are examples: # 1.0 1.0e2 3.1415d00 CONST(0.0,0) 1.0e6_R8 # # when selecting a "constants file" for a particular conversion job, # one should take care to choose names for common constants which # (a) are easily recognizable, and # (b) do not conflict with existing names # # fgtok cannot prevent name conflicts # # names will be in CAPITALS when inserted into code. # caution: max mantissa length = 20 characters; "third" is at the limit. # # add $DECLARE NONE ! to suppress generation of declarations # add $DECLARE REAL*4 ! to change type of declarations # add $CONST_TOL 1.0D-10 ! to change equality test tolerance from 1.0D-12 # zero 0.0d0 half 0.5d0 third .3333333333333333333d0 c4th 0.25d0 c10th 0.1d0 c20th 0.05d0 c50th 0.02d0 c100th 0.01d0 c1000th 0.001d0 one 1.0d0 c3ov2 1.5d0 two 2.0d0 three 3.0d0 four 4.0d0 five 5.0d0 six 6.0d0 seven 7.0d0 eight 8.0d0 nine 9.0d0 c10 10.0d0 c20 20.0d0 c100 100.0d0 c1000 1000.0d0 c10000 10000.0d0 cbig10 1.0d10 cbig30 1.0d30 ceps4 1.0d-4 ceps5 1.0d-5 ceps6 1.0d-6 ceps7 1.0d-7 ceps8 1.0d-8 ceps10 1.0d-10 ceps30 1.0d-30 4 TEX This option should be use to process source code that is imbedded inside a .tex file. Only the "verbatim" text is processed; the tex-part of the file is passed through without modification. 4 NODECL This option suppresses use of the idecl program, so that automatic declaration of implicitly declared symbols is not performed. This step may be useful e.g. when codes are converted to fortran-90 and begin to use fortran-90 keywords and intrinsics which might be misinterpreted by the fortran-77 oriented idecl program as implicitly declared symbols requiring a declaration to be generated. By using this option, r8_convert can be re-applied more safely to codes which already use IMPLICIT NONE. 4 Usage_recommendation When converting a system with fortran INCLUDE files, it is best to convert the INCLUDE files last. This is because one of the steps of r8_convert -- explicit declaration of all names (idecl) -- can depend on IMPLICIT statements present only in the unconverted INCLUDE file. 3 f77name The following C code (from portlib) illustrates the use of the f77name #include file and F77NAME macro: /* cpclock.c */ /* cpu clock -- not for all systems */ #include ion names link differently depending on OS */ #include "f77name.h" int F77NAME(cpclock)() { return clock(); } The macro expression F77NAME(cpclock) expands to either cpclock or cpclock_ depending on the requirements of the system being used. (On CRAY systems, a conversion to uppercase CPCLOCK is needed and this is typically handled by invoking shell script code from a makefile). Thus, the function name CPCLOCK is made externally visible and callable by other fortran routines in the larger program. 2 Fortran_Issues Several portability problems arise... 3 OS_interface Problem: fortran-77 offers no standard interface to operating system and i/o functionality. Unix "3f" routines (getenv, getcwd, getarg, etc) are not consistently implemented. Although "posix" standard interfaces have been defined for such functions, real world implementation is rare. => general solution: assemble a library which "encapsulates" functionality requiring operating systems dependent implementation. Applications are coded to use this library rather than calling the non-portable operating systems interface routines provided by vendors. Examples of encapsulated functionality: * fetch value of environment variables * determine the current working directory * file opens * access to shell commands The NTCC subroutine library "portlib" is an implementation of this strategy. Subtopics describe the "portlib" routines. => example of problem: fixed record length random access files are a high performance i/o option in fortran. But, to open such a file, one must specify the record length (RECL keyword in the fortran OPEN statement). On some systems, the record length is given in "bytes"; on other systems, it is given in 4 byte "words". => solution to example: use the "portlib" routine "genopen" to open direct access binary files. call genopen( > ilun, ! fortran i/o unit number > name, ! file name > fstat, ! file status 'OLD' 'NEW' or 'UNKNOWN' > ftype, ! file type 'ASCII' 'BINARY' or 'DIRECT' > irecsz ! record size (4 byte WORDS) ) ! irecsz used only for ftype.eq.'DIRECT' integer ilun,irecsz character*(*) name,fstat,ftype => alternate solution: use cpp code to compile in the correct record length blocking factor (CAUTION: see information on cpp portability, elsewhere in the help document). #if __ALPHA || __SGI nblkfac=1 #elif __HP || __IBM || __SUN || __RS6000 || __LINUX nblkfac=4 #endif open(unit=ilun,file=name,status=fstat, > type='DIRECT',recl=nblkfac*irecsz) => general comments: from a maintenance standpoint, use of "portlib" is superior. If a new machine is added to the "list of machines to be supported", code changes are concentrated in the portlib subroutines. On the other hand, if machine-specific cpp code is scattered through a user application, adding a new machine would require visiting all of these locations in the source code. => summary recommendation: use portlib. 4 Summary_of_Portlib_Routines Date and Time CALL c9date(str_out) CALL c24date(str_out) CALL wall_seconds(isecnds_out) integer iarray_out(4) CALL cvt_days(isecnds_in,iarray_out) CPU Timer REAL rcptime_out CALL cptimer(rcptime_out) REAL*8 rcptim8_out CALL cptimr8(rcptim8_out) Difference in wall clock time (using f90 DATE_AND_TIME intrinsic) INTEGER, DIMENSION(8) :: tim1,tim2 ! returned by 2 calls to DATE_AND_TIME REAL :: tdiff ! time difference in seconds REAL*8 :: tdiff8 ! time difference in seconds (real*8 precision) CALL DATE_AND_TIME(values=tim1) ! f90 intrinsic ... CALL DATE_AND_TIME(values=tim2) ! f90 intrinsic ! tim1(1) = year, tim1(2) = month), tim1(3) = day, etc... ! difference in time between 2 successive calls to DATE_AND_TIME: CALL wclock_diff(tim1,tim2, tdiff) CALL wclock_diff_r8(tim1,tim2, tdiff8) ! real*8 precision Environment variable Translation CALL sget_env(str_varname_in,str_value_out) Process ID number CALL sget_pid(ipid_out) Username CALL sget_user(str_username_out) Current working directory CALL sget_cwd(str_cwd_out) additional related routines are available. Create a (possibly multi-level) subdirectory / CALL gmkdir(str_root_in,str_path_in,iwarn_out) Command line arguments CALL get_arg_count(i_numargs_out) CALL get_arg(i_argnum_in,str_argval_out) Access to shell istat_out = jsystem(str_cmd_in) Convert between fortran string and C string CALL cstring(char_arg,byte_array,cmd_str) Form a filename from parts CALL ufilnam(str_path_in,str_filename_in,str_fullname_out) Generic fortran file OPENs & related routine CALL genopen(ilun_in,str_filename_in,str_fstatus_in, > str_ftype_in,i_recwords_in, io_status_out) irecsize_fac_out = nblkfac(iwarn_out) File rename copy or delete CALL fcopy(str_old_filename_in,str_new_filename_in,i_status_out) CALL frename(str_old_filename_in,str_new_filename_in,i_status_out) CALL fdelete(str_filename_in,i_status_out) Wildcard file deletion - all files in a directory CALL fclean_dir(str_path_in,i_status_out) Wildcard file deletion -- wildcard file expression, one "*" CALL fwc_delete1(path_wildcard_expr,i_status_out) CALL fwc_delete(path,file_wildcard_expr,i_status_out) Unique name for temporary file CALL tmpfile(str_prefix_in,str_filename_out,i_namelen_out) CALL tmpfile_d(str_prefix_in,str_filename_out,i_namelen_out) for difference btw tmpfile & tmpfile_d see detailed description. Determine if file is ascii or binary CALL is_ascii(filename,iascii,ier) Terminal oriented i/o CALL term_str_out(str_in) CALL term_char_in(char_out) Pause in execution CALL gosleep(isecnds_in) Data on floating point arithmetic ***used by lots of numeric software*** real ransr_out double precision dansr_out iansr_out = I1MACH(ival_in) ransr_out = R1MACH(ival_in) dansr_out = D1MACH(ival_in) String Manipulation ilength_out = str_length(str_in) calling programs declare str_length to be integer CALL str_pad(str_inout) CALL uupper(str_inout) CALL ulower(str_inout) Floating Point Error Handling CALL err_ini CALL err_end Program Exit CALL good_exit CALL bad_exit CALL errmsg_exit(str_msg_in) 4 Details_on_Portlib Portlib is a collection of subroutines built to provide a common interface to an assortment of non-portable vendor supplied operating systems functions. It is not possible to hide all portability issues in a single library. If, for example, an application which deals with file paths and file names needs to be portable between UNIX and VMS, the application will need explicit operating system dependent code beyond what portlib can provide, since the syntax for naming files varies between UNIX and VMS. Even so, the authors have found it useful to "hide" common portability details through library calls, where possible. The authors welcome suggestions for improvements, or for new routines to add-- preferably, in the form of tested source code. Send Email to: dmccune@pppl.gov. A brief description of the `portlib' common interfaces follows, below. Acknowledgements: ================= Many thanks to Charles Karney for providing sources "sysdep.web" -- many of its algorithms and methods are incorporated here. `portlib' is really a merger of the methods used by Charles Karney to support the Degas-2, and the methods of Doug McCune to support TRANSP. The methods for dynamic memory allocation from "sysdep.web" were not included. Architectures Currently Supported by "portlib": =============================================== VMS UNIX: DEC-alpha (DIGITAL UNIX), DEC-alpha (Linux), SUN, SGI, IBM RS-6000, HP, Intel LINUX: Fujitsu Fortran and Portland Group Fortran., Cray: UNICOS. Defects in this support are possible. Report such problems to the author, dmccune@pppl.gov. 5 Date_and_Time_Routines Portlib routines: ** c9date: CHARACTER*9 zdate CALL c9date(zdate) returns zdate, Y2K compliant, in form DDmmmYYYY -- e.g. "14apr1999". ** c24date: CHARACTER*24 zdate CALL c24date(zdate) returns zdate in form DD-Mmm-YYYY hh:mm:ss.sss -- e.g. "14-Apr-1999 11:19:13.607". ** wall_seconds: INTEGER secnds CALL wall_seconds(secnds) returns in secnds an integer value giving the elapsed time since 00:00 1 Jan 2000 (a negative number for calls in 1999). You can do a (wall-clock, not cpu) timing of a section of code by the following method: INTEGER wtime, wt1,wt2 ... CALL wall_seconds(wt1) ...(code to be times) CALL wall_seconds(wt2) wtime = wt2-wt1 WRITE(6,*) 'elapsed wall clock time:',wtime When the number of seconds is large, it might be desirable to "change the units" of the time value into elapsed days/hours/minutes/seconds. This can be done with: ** cvt_days: INTEGER tscnds ! elapsed time, seconds, .gt.0, INPUT INTEGER tarray(4) ! this time in days/hours/mins/secs ! OUTPUT CALL cvt_days(tscnds,tarray) example of usage: tscnds=500000 CALL cvt_days(tscnds,tarray) WRITE(6,fmt) tscnds, (tarray(i),i=1,4) where fmt is a format specifier such that the following output is produced: ...500000 seconds is: 5 days, 18 hours, 53 minutes, 20 seconds. Obviously, cvt_days might be applied to elapsed cpu-time (in seconds) as easily as elapsed wall-clock time (in seconds). 5 Elapsed_CPU_Time ** cptimer: REAL zcptime ! elapsed CPU time since start of process, ! OUTPUT CALL cptimer(zcptime) ** cptimr8: REAL*8 zcptime ! elapsed CPU time since start of process, ! OUTPUT CALL cptimr8(zcptime) (REAL*8 version of cptimer). To use this to time a section of code, write REAL cptime,zc1,zc2 .... CALL cptimer(zc1) ...(code to be timed) CALL cptimer(zc2) cptime=zc2-zc1 ! elapsed CPU time between cptimer calls. The result may be well defined only for non-MPP applications. 5 Elapsed_Wall_Clock_Time A portlib routine is provided to compute the difference, in seconds, between time stamps returned by two successive calls to the fortran-90 intrinsic DATE_AND_TIME. The intrinsic returns an 8 word integer vector containing values for: year, month, day, time-zone, hour, minute, second, millisecond E.g.: 2007, 9, 27, -240, 9, 52, 20, 452 for Sept 27, 2007, 9:52:20.452 am EDT. The portlib routines are: wclock_diff and wclock_diff_r8; usage is summarized here: INTEGER, DIMENSION(8) :: tim1,tim2 ! returned by 2 calls to DATE_AND_TIME REAL :: tdiff ! time difference in seconds REAL*8 :: tdiff8 ! time difference in seconds (real*8 precision) CALL DATE_AND_TIME(values=tim1) ! f90 intrinsic ... CALL DATE_AND_TIME(values=tim2) ! f90 intrinsic ! difference in time between 2 successive calls to DATE_AND_TIME: CALL wclock_diff(tim1,tim2, tdiff) ! tdiff in seconds CALL wclock_diff_r8(tim1,tim2, tdiff8) ! tdiff8 in real*8 precision NOTE: the time zone shift data is ignored by wclock_diff[_r8]. In effect, the code assumes that the two successive calls to DATE_AND_TIME are made in the same time zone. 5 Environment_Variable_Translation (on VMS systems, this routine translates a VMS "Logical Name"). ** sget_env: CHARACTER*(*) varname ! name of variable, INPUT CHARACTER*(*) value ! translation value, OUTPUT CALL sget_env(varname,value) For example: "call sget_env('HOME',hdir)" would set the character variable "hdir" to a string specifying the user's login directory, on a UNIX system. On a VMS system, this would be done with "call sget_env('SYS$LOGIN',hdir)". If the name passed is that of an environment variable (logical name) which does not exist, then the value returned is blank (' '). 5 Command_Line_Arguments ** get_arg: INTEGER i ! request to fetch i'th argument, INPUT CHARACTER*(*) arg ! value of argument returned, OUTPUT CALL get_arg(i,arg) ** get_arg_count: INTEGER numargs ! number of command line arguments, OUTPUT CALL get_arg_count(numargs) These routines are supported on both VMS and UNIX. The notion of command line arguments is explicitly supported in UNIX (and the c programming language). On VMS, a runtime library call (lib$get_foreign) is used to fetch the entire command line (excluding the command itself). The number of arguments on the command line is defined as the number of (blank delimited) words. For an item containing imbedded blanks to be counted as a single word, it must be enclosed in double quotes. 5 Other_Process_and_Environment_Info ==> Get Process ID number: ** sget_pid: INTEGER ipid CALL sget_pid(ipid) The pid can be useful, e.g. for creating a unique name for a temporary file. Note that a VMS pid is usually longer than a UNIX pid -- typically a positive integer of 9 digits. ==> Get Name of Current Host: ** sget_host: CHARACTER*(*) host_name ! name of host, OUTPUT CALL sget_host(host_name) This fetches the nodename of the current host. Note, this is not guaranteed to be a fully qualified internet name, it depends on how the host system was set up. Applications which require a reliable internet name may want to support an option to have this specified through an environment variable. ==> Get Username: **sget_user CHARACTER*(*) user_name ! the username string, OUTPUT CALL sget_user(user_name) works on UNIX via translation of standard environment variables; on VMS by means of a lib$getjpi call. ==> Determine if Process is Controlled from a Script: INTEGER iscript ! =1 if a script, =0 if a tty, OUTPUT CALL isbatch(iscript) On UNIX, iscript=0 is returned if "stdin" is the user's tty; iscript=1 is returned if "stdin" is anything else. The behaviour on VMS is essentially the same, though VMS terminology would yield a different description. ==> Get/Set Current Working Directory: ** sget_cwd: CHARACTER*(*) dir_name ! the directory path, OUTPUT CALL sget_cwd(dir_name) ** sget_dsk: ** VMS ONLY ** CHARACTER*(*) dsk_name ! current working disk, OUTPUT CALL sget_dsk(dsk_name) ** showdefl: ** VMS ONLY ** CHARACTER*(*) dsk_name,dir_name ! disk & directory, OUTPUT INTEGER ldsk,ldir ! non-blank length of outputs, ! OUTPUT CALL showdefl(dsk_name,ldsk,dir_name,ldir) ** sset_cwd: ** UNIX ONLY ** CHARACTER*(*) dir_name ! directory to move to, INPUT INTEGER ierr ! completion code, OUTPUT, 0=OK. CALL sset_cwd(dir_name,ierr) Notes: A full VMS path specification usually has a form like: DSK1:[abc.def.xyz]. "sget_dsk" would return "DSK1:"; "sget_cwd" would return "[ABC.DEF.XYZ]"; showdefl would return both. UNIX directory paths lack the notion of a disk-- hence, no diskname translation. A VMS method for changing the current working directory has not been found. 5 Make_a_subdirectory ** gmkdir (subroutine): CHARACTER*(*) root ! root directory (must exist), INPUT CHARACTER*(*) path ! relative path from root, INPUT INTEGER istat ! completion code (returned), OUTPUT CALL gmkdir(root,path,istat) If the directory (root/path) does not exist, create it, along with any intermediate directories as needed to generate the entire path. "root" can be an environment variable or a string of the form a/b/c where "a" is an environment variable. The environment variable will be translated. 5 Access_to_Shell ** jsystem (integer function): CHARACTER*(*) cmd ! shell command to execute, INPUT INTEGER istat ! status code returned by jsystem istat = jsystem(cmd) This executes the command "cmd" in the shell of the current OS (UNIX: sh, VMS: DCL). The command must be valid in the language of this shell. The status code set by execution of this command is returned. 5 Build_Filename_from_Parts ** ufilnam: CHARACTER*(*) path ! directory path or start of filename, INPUT CHARACTER*(*) filename ! completion of filename, INPUT CHARACTER*(*) fullname ! complete file specification, OUTPUT CALL ufilnam(path,filename,fullname) This routine essentially concatenates two filename components to form a single, complete filename. On UNIX systems, an environment variable translation is tried on the "first part" of the "path" argument (see examples). On both VMS and UNIX systems, punctuation is added at the point of concatenation if it is deemed necessary. UNIX Examples (suppose the environment variable HOME translates to "/u/dmccune"): call ufilnam('/u/dmccune','foo.tmp',fullname) => fullname = '/u/dmccune/foo.tmp' call ufilnam('/u/dmccune/','foo.tmp',fullname) => fullname = '/u/dmccune/foo.tmp' (comment: a trailing slash in the path specification is optional). call ufilnam('HOME','foo.tmp',fullname) => fullname = '/u/dmccune/foo.tmp' call ufilnam('$HOME','foo.tmp',fullname) => fullname = '/u/dmccune/foo.tmp' call ufilnam('HOME/tmp','../foo.tmp',fullname) => fullname = '/u/dmccune/tmp/../foo.tmp' (equivalent to fullname = '/u/dmccune/foo.tmp' if /u/dmccune/tmp exists). (comments: if the first character of "path" is neither "~" nor "/", then name translation is tried on the leading part of "path" up to its first "/" character, if any. If in the leading part of path, and if HOME can be translated, then, "HOME" and "$HOME" are equivalent. The "path" argument does not have to be a full path to the target file; "filename" can include the trailing part of the path as well as the file name itself). VMS examples: call ufilnam('SYS$LOGIN','foo.tmp',fullname) => fullname = '$SYS$LOGIN:foo.tmp' call ufilnam('DSK1:[user.dmccune]','foo.tmp',fullname) => fullname = 'DSK1:[user.dmccune]foo.tmp' call ufilnam('DSK1:[user.','dmccune]foo.tmp',fullname) => fullname = 'DSK1:[user.dmccune]foo.tmp' (comments: if the last character of "path" is not one of {":",".","[","]","<",">"} then it is assumed to be a "disk like" logical name, and a colon is inserted between "path" and "filename" during the concatenation to form "fullname". Translation of logical names is never attempted, because if the fullname formed is ever used e.g. in a fortran OPEN statement, logical name translation will be handled as necessary by the VMS system). 5 Open_a_File ** genopen: The GENOPEN subroutine can be used for access to a standardized fortran OPEN statement, for STATUS='OLD', 'NEW', or 'UNKNOWN' fortran files of TYPE='ASCII', 'BINARY', or 'DIRECT'. The subroutine takes as arguments a fortran i/o "unit number", a filename, a file STATUS, a file TYPE, a record size (in 4 byte words) for direct access fixed record length i/o, and a status code is returned. For exact details: see the source code. The subroutine header follows: subroutine GENOPEN(lun,name,fstat,ftype,irecsz,ios) C integer lun ! logical unit number character*(*) name ! file name character*(*) fstat ! file status 'OLD' or 'NEW' or 'UNKNOWN' character*(*) ftype ! file type 'ASCII' 'BINARY' or 'DIRECT' integer irecsz ! record size ('DIRECT' files only) ** ierfnof (integer function): INTEGER ilun ! fortran i/o "unit number" to use, INPUT INTEGER ierf ! integer to hold value returned from ierfnof. ierf=ierfnof(ilun) ierfnof returns the error code which on the current system means "file not found". This it does by actually trying to open a file with a very strange name, 99.9999% sure not to exist, and then returning the resultant status code. This can be used to examine an error code returned from GENOPEN, to see if the error is "file not found" or some other error. ** nblkfac (integer function): INTEGER ierr ! status code returned, 0=OK, 1=warn, OUTPUT INTEGER iblkfac ! factor for specifying record length iblkfac = nblkfac(ierr) This function returns a "units conversion factor" for the value to be used in specifying record length in a fortran OPEN statement, on the current machine. On machines for which the record length specifier "units" are 4 byte words, nblkfac returns 1. On machines for which the record length specifier is given in bytes, nblkfac returns 4. Nblkfac returns ierr=1 if, at compile time, non of the cpp tests succeeded, such that the units conversion factor appears to be unknown. In this case the function value returned is 1. Usage: INTEGER iwords data iwords/150/ ! want fixed length 150 (4 byte) word records ifac=nblkfac(ierr) if(ierr.eq.1) write(6,*) > 'WARNING -- machine unknown, nblkfac=1 used.' OPEN(unit=lun,file=name,..., recl = ifac*iwords ) 5 Rename_or_Copy_or_Delete_a_File ** frename: CHARACTER*(*) oldname ! old filename, INPUT CHARACTER*(*) newname ! new filename, INPUT INTEGER ier ! status code returned, OUTPUT, 0=OK CALL frename(oldname,newname,ier) renames a file. ** fcopy: CHARACTER*(*) oldname ! old filename, INPUT CHARACTER*(*) newname ! new filename, INPUT INTEGER ier ! status code returned, OUTPUT, 0=OK CALL frename(oldname,newname,ier) copies a file. ** fdelete: CHARACTER*(*) filename ! file to delete, INPUT INTEGER ier ! status code returned, OUTPUT, 0=OK CALL fdelete(filename,ier) deletes a file. (On VMS, all versions of the file are deleted, unless a specific version of the file is specified in the filename argument). A file can also be deleted using fortran OPEN(...) and CLOSE(...,status='DELETE') statements, but then an i/o unit number must be allocated. 5 Wildcard_file_delete **fclean_dir: CHARACTER*(*) path ! directory path INTEGER ier ! status code returned, OUTPUT, 0=OK CALL fclean_dir(path,ier) ! delete all files in (path) ! note: subdirectories not touched ! note: hidden files ("." 1st character) not touched Error code is set if directory does not exist. If there are already no files in the directory, it is not considered an error. **fwc_delete1 CHARACTER*(*) wildcard_expr ! directory/wildcard expression INTEGER ier ! status code returned, OUTPUT, 0=OK CALL fwc_delete1(wildcard_expr,ier) ! delete indicated files Exactly one wildcard symbol "*" should appear in the filename field, optionally following a path expression. Examples of valid calls: CALL fwc_delete1('*.dat',ier) ! delete *.dat in current working dir CALL fwc_delete1('foo/zork/*.dat',ier) ! delete indicated files **fwc_delete CHARACTER*(*) path ! path to directory in which to delete CHARACTER*(*) wildcard_expr ! file wildcard expression INTEGER ier ! status code returned, OUTPUT, 0=OK CALL fwc_delete(path,wildcard_expr,ier) ! delete indicated files Exactly one wildcard symbol "*" should appear in wildcard_expr. Examples of valid calls: CALL fwc_delete(' ','*.dat',ier) ! delete *.dat in current working dir CALL fwc_delete('foo/zork','*.dat',ier) ! delete indicated files 5 Unique_Name_for_Temporary_File a valid filename for the operating system currently in use. **tmpfile_d: CHARACTER*(*) prefix ! filename prefix specification, INPUT CHARACTER*(*) filename ! full temporary filename, OUTPUT INTEGER ilfname ! length of filename returned, OUTPUT call tmpfile_d(prefix,filename,ilfname) in this routine, prefix contains a filename prefix only, without path information. UNIX: The filename returned will be of the form $TMPDIR/nnnnn.tmp (if TMPDIR is defined) or /tmp/nnnnn.tmp otherwise. VMS: The filename returned will be of the form JUNK:nnnnn.tmp, if logical name JUNK is defined, or SYS$LOGIN:nnnnn.tmp otherwise. 5 Is_File_ASCII Sometimes it is useful to know, before trying to open a file with fortran, whether it is an ASCII text file or not. CHARACTER*(*) filename ! file in question, INPUT ! Outputs: INTEGER IASCII ! =1: yes is ascii; =0: no, -1: error INTEGER IER ! 0 if OK, `C' errno error code if not OK call is_ascii(filename,iascii,ier) 5 Byte_Oriented_IO In the portlib source code, "cftsubs.for" contains an interface to C code for sequential binary i/o in 512-blocks. This can be a basis for a home-grown portable binary file format-- VMS receives binary files via ftp in 512-blocks. For details, see the source code. 5 Terminal_Oriented_IO ** term_str_out: CHARACTER*(*) str ! string to write, INPUT CALL term_str_out(str) The character bytes in str are output to the terminal, immediately, in a non-record-oriented way, and with buffer flushing. Thus, "str" can contain an "escape sequence" to cause a terminal to change modes, e.g. a VT100 switch into "reverse video" mode. Or, it can simply be used to generate a prompt for user input that does not end in a carriage return / line feed. ** term_char_in CHARACTER*1 achar ! character to read, OUTPUT CALL term_char_in(achar) "achar" is read in from the terminal, immediately, without echoing, and without the user being required to type a carriage return. 5 Go_To_Sleep ** gosleep: INTEGER isecnds ! time to sleep, seconds, INPUT CALL gosleep(isecnds) Put the current process in "sleep" state for the indicated number of seconds. 5 Floating_Point_Information portable, fortran callable c code versions of the standard functions integer function I1MACH(i) real function R1MACH(i) double precision function D1MACH(i) are provided. The meaning of the value returned is as described in the source code; further information can be found at http://www.netlib.org and at other standard sources as well. 5 String_Manipulation integer function str_length(str) ...returns the non-blank non-null length of a string. ...returns zero if the entire string is blank or null. subroutine str_pad(str) ...replaces all null characters with blanks in str. subroutine uupper(str) ...converts all alphabetic characters in str to uppercase. subroutine ulower(str) ...converts all alphabetic characters in str to lowercase. These routines are included in "portlib" to make the library be self-contained. subroutine cstring(str,buffer,cmd) character*(*) str ! fortran string byte buffer(*) ! C null terminated string **NOTE** character*(*) cmd ! "2C": fortran->C; "2F": C->fortran If cmd is "2C", copy contents of str into buffer and append a null; If cmd is "2F", copy contents of buffer into str, stopping when a null is encountered or len(str) is reached. blank-pad the string as needed. **NOTE** the byte array declaration shown in this example is not portable. A method for portable handling of byte arrays is covered in the section on byte arrays. 5 Floating_Point_Error_Handling Most systems allow the error handling utilized in floating point arithmetic to be controlled at compile time via the compiler command line. On SUN systems, however, an actual call is required inside the code. ** err_ini: CALL err_ini (called at program startup) causes SUN floating point error handling to be set up as follows: (a) signal abort on: overflow, divide by zero, invalid. (b) underflow to zero silently. On non-SUN systems, the call has no effect. ** err_end: CALL err_end (called just before program exit) restores SUN default floating point error handling. This prevents an obnoxious warning from being printed when the program exits. On non-SUN systems, the call has no effect. 5 Exit_a_Program ** good_exit: CALL good_exit causes a program to "call err_end" and then "stop", for a normal program termination. ** bad_exit: CALL bad_exit causes a program to "call err_end" and then "call exit(1)" (UNIX) or "call lib$stop( %val(2) )" (VMS), causing a status code to be set which can be detected by the shell from which the program was run. ** errmsg_exit: CHARACTER*(*) msg ! message to write before exiting, INPUT CALL errmsg_exit(msg) causes the message "msg" to be written, followed by a call to "bad_exit". 3 Byte_Arrays Problem: Some fortran-77 codes need byte arrays and scalars. These are useful e.g. for creating C-style character strings. But, there is no standard way to declare these arrays. Some compilers support "BYTE" as a data type; others support "INTEGER*1" (neither is standard but almost all popular fortran-77 compilers support one or the other). Some fortran-90 compilers support neither of these options and appear to allow no means of declaring a byte array. On UNICOS Crary machines, which have no fortran-77 compiler, CHARACTER*1 can be used. => general solution: stick to fortran-77. Use the cpp include file "byte_declare.h" (available in the NTCC fpreproc module). Use the fpreproc package for fortran preprocessing (more information on fpreproc elsewhere in the help document). In the source code, where a byte array must be declared, use "BYTE_DECLARE" to declare it: #include "byte_declare.h" ... BYTE_DECLARE buffer(512) Byte_Declare buffer2(512) BYTE_DECLARE is a macro which will be replaced with either BYTE or INTEGER*1, depending on the system type. Note that with fpreproc, fortran source code (excepting quoted strings and comments) is folded to uppercase prior to preprocessing, so that "Byte_Declare" will have the same effect as "BYTE_DECLARE". The fpreproc respects the fortran tradition of case independence. => comment: it is constructive to look inside "byte_declare.h": /* define BYTE_DECLARE as a macro for declaration of a byte array */ #ifndef BYTE_DECLARE #if __SUN || __SUNOS || __HP #define BYTE_DECLARE byte #elif __CRAY #define BYTE_DECLARE character*1 #else #define BYTE_DECLARE integer*1 #endif #endif ...note that the macro name, BYTE_DECLARE, is longer than either possible substitution string; therefore, the cpp substitution will not result in the lengthening of a fixed format fortran source line across the 72 character limit. => recommendation: use "byte_declare.h" rather than explicit cpp code to handle byte array declarations. 3 Integer_and_Logical_DataTypes Generally, portable code should use the bare datatypes INTEGER LOGICAL rather than INTEGER*8, INTEGER*4, INTEGER*2, INTEGER*1 and LOGICAL*1, LOGICAL*2, LOGICAL*4. The INTEGER*n and LOGICAL*n declarations are non-standard, and not supported by all manufacturers. (There is a portable way to create byte arrays which are INTEGER*1 on some systems. See the section on byte arrays). Any LOGICAL function must be declared LOGICAL in the program unit from which it is called. The results of logical expressions should be stored in LOGICAL variables, and the results of INTEGER expressions in INTEGER variables; the types should not be mixed. The conditional clause in an IF statement should be a LOGICAL expression, not an INTEGER expression. Code which blurs the distinction between INTEGER and LOGICAL is generally not reliably portable. 4 int_precision.sub This file, distributed with fgtok, can be used to clean up INTEGER and LOGICAL declarations: # map all INTEGER*n to INTEGER # map all LOGICAL*n to LOGICAL # map INTEGER*1 to BYTE_DECLARE # # fgtok -s int_precision.sub # integer*1 BYTE_DECLARE integer*2 INTEGER integer*4 INTEGER integer*8 INTEGER logical*1 LOGICAL logical*2 LOGICAL logical*4 LOGICAL logical*8 LOGICAL Caution -- some f77 compilers allow integers of various sizes to be declared on a single line, as in INTEGER ii,jj*2,kk*4,mm(3)*1 such code is not portable, and is not cleaned up by using int_precision.sub or intcon.csh. 4 intcon.csh This script, distributed with fgtok, can be used to clean up a source code, with respect to INTEGER declarations. It will also insert the appropriate cpp #include file reference, if BYTE_DECLARE is used. #! /bin/csh -f #! #! from $1 make a source code that only declares INTEGER and LOGICAL #! (no INTEGER*n or LOGICAL*n). If INTEGER*1 is detected replace this #! with BYTE_DECLARE. At the end, check for BYTE_DECLARE; if it is #! present, insert the appropriate #include file reference. #! #! the output is written to $2. If the optional argument $3 is #! specified, this is a user supplied substitution table or list. #! @ ier = 0 if ( $#argv < 2 ) then echo "?intcon.csh: usage:" echo "> csh -f intcon.csh old-source-file new-source-file [s-file]" @ ier++ endif # if ( $#argv > 0 ) then if ( ! -f $1 ) then echo "?intcon.csh: not found: $1" @ ier++ endif endif # if ( $?S_TABLE_DIR == 0 ) then echo "?intcon.csh: set environment variable S_TABLE_DIR to point" echo " to the directory containing standard substitution table files:" echo " int_precision.sub, etc." @ ier++ else if ( ! -f $S_TABLE_DIR/int_precision.sub ) then echo "?intcon.csh: S_TABLE_DIR = $S_TABLE_DIR" echo " environment variable may be incorrectly set." echo " $S_TABLE_DIR/int_precision.sub: file not found." @ ier++ endif endif # if ( $?CPP_HFILE_DIR == 0 ) then echo "?intcon.csh: set environment variable CPP_HFILE_DIR to point" echo " to the directory containing standard .h files for library" echo " name substitions: byte_declare.h, etc." @ ier++ else if ( ! -f $CPP_HFILE_DIR/byte_declare.h ) then echo "?intcon.csh: $CPP_HFILE_DIR = $CPP_HFILE_DIR" echo " environment variable may be incorrectly set." echo " $CPP_HFILE_DIR/byte_declare.h: file not found." @ ier++ endif endif # if ( $ier > 0 ) then exit 1 endif # cp $1 $1.tmp0 # if ( -f $2 ) then echo "%intcon.csh: old version of $2 moved to $2~" mv $2 $2~ endif # if ( $#argv >= 3 ) then set s0 = (int_precision.sub $3) else set s0 = int_precision.sub endif # # OK start working. # 1. standardize all INTEGER and LOGICAL declarations, # fgtok $1.tmp0 $1.tmp \ -p $S_TABLE_DIR -s $s0 -cmt "intcon.csh conversion" # # 2. if the source code uses BYTE_DECLARE to create byte arrays, # insert the approriate header file. # set libtest = \ `cpcheck $1.tmp6 -p $CPP_HFILE_DIR -incl_cpp byte_declare.h` if ( "$libtest" == "present" ) then fgtok $1.tmp $1.tmp7 -incl_cpp library_names.h else cp $1.tmp $1.tmp7 endif # # 3. final step: cleanup; warn about line length # mv $1.tmp7 $2 rm $1.tmp* # fgtok $2 -w72 3 Floating_Point_Precision The problem here is that fortran-77 standard does not define the actual floating point precision associated with the data type declarations "REAL" and "DOUBLE PRECISION". Thus, on Cray machines, "REAL" maps to a 64 bit word, while on conventional unix workstations, "REAL" maps to a 32 bit (IEEE) floating point word. To have any hope of creating numerical codes which yield equivalent accuracy on all platforms, it is necessary to systematically use declarations for floating point arrays and scalars which have the same meaning, in terms of precision, on all machines. Given the emergence of 64 bit architectures and the ever increasing speed of current hardware, the time has arrived where one should insist that 64 bit arithmetic be used in all numerical codes for which the attributes "portable" and "reliable" are to be claimed. Numerical software vendors (e.g. NAG) made this choice a long time ago, by no longer supporting 32 bit versions of their numerical subroutine libraries on most brands of unix workstations. This decision is well founded-- the labor involved in coping with problems arising from insufficiently accurate computation in numerical simulations is no longer justified by the modestly greater speed and smaller memory requirements of 32 bit arithmetic. Many authors have tried to standardize on 64 bit arithmetic by using "autodouble" compiler options ("-r8" on many unix systems) to automatically convert codes to double precision at compile time. The problem is, this approach is not portable, because support for "autodouble" compile time switches are not consistently provided on all machines. For example, use of "autodouble" compilers affects the size of INTEGER as well as REAL declarations, but, not consistently from machine to machine. Also, experience shows that intractible and confusing problems can arise when one tries to put together large codes from interdependent fortran subroutine libraries, some of which are compiled with an "autodouble" switch, and some without. ***The use of "-r8" or other compiler "autodouble" switches is not recommended! In fortran-90, the language standard provides a mechanism for portably specifying floating point precision: the KIND= qualifier in floating point data type declaration statements. This is a good solution and should be used in fortran-90 codes. In fortran-77, there is no good standards conforming solution, but, in practice, a universally effective solution does exist: * declare all floating point reals using REAL*8 * declare all complex numbers using COMPLEX*16. There is some subtlety involved in the conversion of fortran-77 codes to follow this practice, but, the portability tools provided in the NTCC modules library do permit a largely automated and reliable method of conversion. It is a multi-step process, detailed in the subtopics. If all steps are indicated, a script can be invoked (see the section on automatic conversion). Caution: the methods described are for fortran-77 codes only; however, fixed format fortran-90 sources that are written in a fortran-77 style and only use one or two fortran-90 features, may also be convertible by the means described here. 4 Automatic_Conversion The NTCC module `ftoken' includes tools for converting source code. Included with the `fgtok' tool are the scripts, r8con.csh -- convert source files r8con_incl.csh -- convert INCLUDE files which will carry out "complete" conversions of fortran-77 source code to REAL*8/COMPLEX*16 precision. The procedure for using the script is as follows: (1) define the environment variable S_TABLE_DIR to point to the directory containing the "standard" fgtok substitution tables r8_precision.sub, etc. This is usually the same as the directory containing the r8con.csh script itself. (2) define the environment variable CPP_HFILE_DIR to point to the directory containing "standard" cpp .h files used for fortran preprocessing at compile time. This would normally be the home directory of the NTCC `fpreproc' module, and will contain files such as "blas_names.h"-- a header file that forces codes to use single precision or double precision BLAS libraries, depending on the precision class of the current machine architecture. (3) invoke the script with the command: > csh -f r8con.csh input.f output.f user_subs.table CONST where "input.f" is the input, unconverted source file, "output.f" is the output, converted source file. The arguments "user_subs.table" and "CONST" are optional. The user may specify 0 or more user supplied `fgtok' name substitution table filenames. The presence of the argument CONST means, add a header file to the code to define the CONST macro, and represent floating point constants e.g "1.2345d-15" instead as CONST(1.2345,-15). This will expand to "1.2345D-15" or "1.2345E-15", depending on which form specifies a 64 bit floating point constant in the current environment. In a sense CONST gives the most precise portability conversion for floating point constants, but, it has the disadvantage of significantly expanding the lengths of code lines, causing many to wrap beyond the 72 character limit. The default (no CONST argument) simply maps floating point constants to their D representation; on Crays these can be mapped to E (64 bit) form, using a compiler switch. For convenience the user may want to write a jacket script that carries out the above steps (1) and (2) in a way appropriate to the local environment, and then runs (3). The script r8con.csh carries out the following steps. step 1: using the `fgtok' program, redeclare all floating point scalars and arrays to REAL*8 and/or COMPLEX*16. Also carry out any user specified name substitutions. --see "Declarations". step 2: Use the `idecl' program to generate explicit declarations for all symbols not already explicitly declared. --see "Make_Explicit" step 3a: Insert the "IMPLICIT NONE" directive in every program unit, and remove any pre-existing IMPLICIT statements. `idecl' should run before this step, because it extracts data from IMPLICIT statements other than "IMPLICIT NONE". step 3b: using fgtok, convert all floating point constants to "D-exponent" form, e.g. 1.0 => 1.0D0. --see "Constants" step 4: using fgtok, convert all type conversion intrinsic functions (REAL, FLOAT, AIMAG, CMPLX, etc). to a standard form. These instrinsics will require further conversion at compile time (using fpreproc), because on each machine the correct intrinsic name depends on whether REAL*8 is single precision (i.e. Crays), or double precision (i.e. most other machines). --see "Conversion_Intrinsics" step 5: using fgtok, convert all non-generic intrinsic functions to their generic form (i.e. DSQRT -> SQRT, etc.). --see "Generic_Intrinsics" step 6: deal with standard numerical subroutine library calls (BLAS, LAPACK, SLATEC, etc). For this, fpreproc will be used at compile name, because the correct name choice is different between Crays and most other machines. Standard #include .h files are provided with the fpreproc module, to define the necessary library name mappings. --see "Libraries" step 7: check for source code lines of length greater than 72 characters (use fgtok -w72); correct such any such lines with a text editor, or try fgtok -fix72. --see "Line_length" This sequence is applied once to the source code and INCLUDE files, to put it permanently and maintainably into a portable state. The r8con_incl.csh script carries out a subset of the above steps, appropriate for INCLUDE files. 4 Declarations (step 1 of the automatic conversion script). The requirement here is to explicitly convert every floating point declaration type in the code to REAL*8 / COMPLEX*16. The table driven name substitution program `fgtok', part of the NTCC `ftoken' module, is used. Example of use: fgtok input.f output.f -p $FGTOK_DATA -s r8_precision.sub The switch -s specifies a substitution table file; the switch -p specifies a path to a directory containing substitution table files. `r8_precision.sub' is distributed with the fgtok program; its contents are: # map all real & complex types to real*8 and complex*16 real REAL*8 1 real*4 REAL*8 1 real*8 REAL*8 1 double precision REAL*8 1 complex COMPLEX*16 1 complex*8 COMPLEX*16 1 complex*16 COMPLEX*16 1 double complex COMPLEX*16 1 To complete the conversion, the above fgtok command should be applied to all components of a given code-- not overlooking INCLUDE files. As the next step, all implicit declarations will be made explicit, and the "IMPLICIT NONE" statement will be inserted in all program units. 4 Make_Explicit (step 2 of the automatic conversion script). The conversion of a code to IMPLICIT NONE naturally requires that anything previously implicitly declared must now have an explicit declaration generated. This is tedious to do by hand. It can also be tricky to automate: an automation tool must be able to parse input fortran code sufficiently to figure out (a) which symbols exist, and (b) which are user symbols that need to be declared. In doing this, it needs to look at INCLUDE files, so that symbols declared in INCLUDE files are not incorrectly re-declared. The NTCC modules library tool `idecl' (in the `ftoken' module) will do the trick for fixed format fortran codes: idecl input.f output.f -r8 For codes using the fortran default implicit rules, this generates declarations of undeclared symbols in each program unit, by the rule that any symbol whose first letter is I,J,K,L, M or N is declared INTEGER, and all others are declared REAL*8. If the "-r8" switch is omitted, the non-INTEGERs are simply declared REAL. `idecl' also supports a -I switch, through which paths to INCLUDE files may be specified. For codes with non-default IMPLICIT rules, `idecl' parses any IMPLICIT statements it finds, and modifies its generated declarations accordingly. This means `idecl' should be used BEFORE using `fgtok' to replace the non-default IMPLICIT statements with "IMPLICIT NONE" statements. 5 IMPLICIT_NONE After making all declarations explicit, implicit data declarations should be banned, by inserting "IMPLICIT NONE' in all program units. If this is not done, then, under the default fortran rules, variables added to the program which are implicitly declared will have the wrong precision. After using `idecl' to generate explicit declarations, using any IMPLICIT rules found in the source, use fgtok input.f output.f -impno to get rid of all pre-existing IMPLICIT statements and insert an IMPLICIT NONE statement at the head of each program unit. 4 Constants (step 3b of the automatic conversion script). When converting a code to REAL*8 precision, hard coded constants should also be converted. Failure to do this results in a portability hazard: if a hard coded constant in a subroutine argument list is taken as REAL*4 by a compiler, and this is passed to a routine which expects REAL*8, the result is unpredictable-- a bug that can be quite hard to find. To avoid this, there are two recommended approaches. One is to use "fgtok -expd", which converts all constants to explicit "D-exponent" form, e.g. "1.0D0" rather than just "1.0". fgtok can do this as follows: fgtok input.f output.f -expd -w72 The -expd switch causes fgtok to convert all constants in input.f, writing the results in output.f. The -w72 option can be used to check for source lines that are widened beyond the 72 character limit. Incidentally, on Cray/UNICOS, a compiler switch is used to prevent the "D-exponent" constants from being interpreted as quad-precision words. A second recommended approach is to use a CONST macro expansion, which can be applied by: fgtok input.f output.f -const -incl_cpp fp_const.h -w72 This command will insert a reference to the (fpreproc) "fp_const.h" header file which defines the CONST macro, and, convert all floating point constants to the form CONST(mantissa,exponent). The macro will expand at compile time to either E or D depending on which format gives a 64 bit representation on the current machine. From the point of view of robust portable representation of floating point constants, use of the CONST macro is probably superior. It has the drawback, however, of significantly lengthening source lines containing arithmetic with many hand coded floating point constants. Such lines will need manual repair to maintain readability (one can try the fgtok -fix72 option but hand repair is generally superior). The -expd switch can also lengthen source lines past the 72 character limit, but the amount of lengthening is much less. 4 Conversion_Intrinsics (step 4 of the automatic conversion script). The fortran-77 conversion intrinsics are a set of functions used to convert between data types, form complex numbers from two reals, or extract the real or imaginary part of a complex number to a real. These functions are: REAL, DBLE, FLOAT, DFLOAT, AIMAG, DIMAG, CMPLX, DCMPLX (DIMAG and DCMPLX are non-standard but widely available). These present a portability problem with respect to control of precision, because, on systems where REAL*8 counts as double precision, typically the intrinsics DBLE, DFLOAT, DIMAG, and DCMPLX must be used to avoid loss of precision in floating point calculations; on systems where REAL*8 counts as single precision, REAL, FLOAT, AIMAG and CMPLX should be used, and, DIMAG and DCMPLX (which are non-standard) do not always exist. To make matters worse for authors of automated code conversion tools, the intrinsic function "real" represents a name conflict with "real" as used to declare symbols. The recommended portability conversion solution is as follows: (a) convert the code to use as standard names AREAL, AIMAG, and CMPLX, for these functions, and, (b) use cpp pre-processing to map AREAL to REAL or to DBLE depending as REAL*8 counts as single or double precision, and similarly to redefine (or not) AIMAG and CMPLX. The NTCC ftoken module's fgtok program includes a substitution table file "r8_icomplex.sub" to implement part (a). Because of the "REAL" name conflict issue, this should only be applied AFTER the basic REAL*8 conversion of the code has been done, otherwise one risks ending up with code which tries to declare symbols of type "AREAL". For part (b), the NTCC fpreproc module contains a cpp .h file "f77_dcomplx.h" which conditionally defines AREAL, AIMAG and CMPLX name substitutions at compile time. To set up the code to use both parts (a) and (b) do this: fgtok input.f output.f \ -p $FGTOK_DATA -s r8_icomplex.sub -incl_cpp f77_dcomplx.h This finds and applies the "r8_icomplex.sub" substitution table (in directory $FGTOK_DATA); it also inserts the line #include "f77_dcomplx.h" at the head of the source file. At compile time, a copy of the #include file will have to be placed where the preprocessor can find it (or use the preprocessor's -I switch to specify directories to search for user #include files). 5 r8_icomplx.sub This substitution table comes with the fgtok program. # map all real <--> complex conversion intrinsics to standard names. # source code should # # #include f77_dcomplx.h # # this is REQUIRED because the non-standard name AREAL is used (to undo # fortran's name conflict btw the REAL intrinsic and REAL data type). # # for environment-dependent compile time remapping. # # usage: # fgtok -s r8_icomplex.sub -w72 # # means the old version fortran source; # means the new version; these should be distinct files. # # if is omitted, stdout is used. If both # and are omitted, stdin and stdout are used. # # recommended switches: # -w72 ... warns of any occurrences of line length .gt. 72 characters # # when converting codes to real*8 usage, use r8_precision.sub *first* # to remove all REAL data type declarators, *before* using this # substitution table. # # ... D. McCune 2 Apr 1999 real AREAL dble AREAL areal AREAL float AREAL dfloat AREAL aimag AIMAG dimag AIMAG cmplx CMPLX dcmplx CMPLX 5 f77_dcomplx.h #ifndef f77_dcomplx /* cpp code for rename of double complex */ #define f77_dcomplx /* fortran intrinsic functions */ #ifndef __SINGLE_PRECISION #ifndef __DOUBLE_PRECISION #ifdef __CRAY #define __SINGLE_PRECISION #else #define __DOUBLE_PRECISION #endif #endif /* ifndef __DOUBLE_PRECISION */ #endif /* ifndef __SINGLE_PRECISION */ #ifdef __DOUBLE_PRECISION /* workstation f77 -- double precision */ #define AREAL DBLE #define AIMAG DIMAG #define CMPLX DCMPLX #else /* CRAY f77 source -- single precision */ #define AREAL REAL #undef AIMAG #undef CMPLX #endif /* DOUBLE or SINGLE_PRECISION */ #endif /* ifndef f77_dcomplx */ 4 Generic_Intrinsics (step 5 of the automatic conversion script). Fortran-77 contains a large number of intrinsic functions for basic math: SQRT, EXP, MIN, MAX, etc. These come in "generic" forms which adapt to the precision of the input variables given, and "non-generic" forms which do not adapt. For portability of REAL*8 precision code, the "generic" forms MUST be used. The use of a non-generic intrinsic like DSQRT creates a problem, because on a Cray machine, a DSQRT with a REAL*8 argument will be interpreted as a user defined function which will cause an undefined reference at load time. To avoid this, use fgtok to convert all intrinsic function references: fgtok input.f output.f -p $FGTOK_DATA -s r8_intrinsic.sub where $FGTOK_DATA gives the directory containing the "standard" fgtok substitution table files (distributed with the fgtok NTCC module). 4 Libraries (step 6 of the automatic conversion script). It is likely that a REAL*8 code will have to link to libraries which were not built with the REAL*8 style. Typically, this means that at compile time, a portable source needs to specify a call to a different routine, according as REAL*8 is single precision or double precision in the current environment. For example, a REAL*8 application would want to use the BLAS routine DAXPY on workstations and linux machines, but it would need to use SAXPY for correct results on Cray machines. The recommended way to cope with this is to use cpp-style name substitution at compile time. This amounts to preprocessing with logic as follows: #if _DOUBLE_PRECISION #define SAXPY DAXPY #else #define DAXPY SAXPY #endif This is fine, except that of course one would prefer to have a single cpp #include file which appropriately renames all of the BLAS routines. This is provided in the fpreproc NTCC module as "blas_names.h" Similar #include files are provided for the LAPACK library. A single header file, "library_names.h", is provided which will #include all available library names conversion header files. Many authors have resorted to imbedding REAL*8 converted copies of BLAS and LAPACK routines in their source code, usually converted with a -r8 switch at compile time. This is not recommended for several reasons: (a) the code fails to use the locally available math library routines with architecture specific optimizations, (b) the author has extra code to maintain, and (c) if the author was not careful to rename the routine, this creates a name conflict with the originally library, which could cause a serious and hard to find bug in a larger program which sought to incorporate the author's work as a subprogram. Unfortunately, rename #include files do not yet exist for every library. The NTCC modules library seeks contributions from the user community (see Email addresses at top of document). Scripted implementation of this step: the program `cpcheck', part of the ftoken module, is used to determine if a source code needs to use the "library_names.h" header file. If so, it is inserted using fgtok input.f output.f -incl_cpp library_names.h It is worth making one forther remark on the topic of numerical libraries. Many numerical codes use the routines "R1MACH" and "D1MACH" to determine the local floating point arithmetic environment. In REAL*8 precision codes, one wants to use either R1MACH or D1MACH, depending as the current machine has REAL*8 as single or double precision. Therefore, the mapping of the R1MACH and D1MACH names is also included in the blas_names.h and lapack_names.h files. Portable versions of "R1MACH" and "D1MACH" are in "portlib" as C code. Use of these versions is strongly encouraged. 4 Line_Length_Check (final step of the automatic conversion script). The various transformation operations described in this section can sometimes cause the length of a line of code to be increased. If it increases beyond the 72 character limit of fixed format fortran, a repair is necessary. To find such lines: fgtok input.f -w72 One can also use `fgtok' to do an automatic fix to such lines, but, for reasons of readability, a hand edit is usually preferable. If the fgtok fix (which involves breaking a line at a word boundary and inserting an extra continuation line) must be used, one can try fgtok input.f output.f -fix72 but it is recommended to examine the results for readability. 4 Bugs The user should view the `fgtok' and `idecl' tools alluded to in this section as "beta" code. Although a large amount of fortran source has been run through these tools, it is not possible to guarantee that the tools will produce satisfactory results for every fixed format fortran source in existence. Indeed there are undoubtedly codes out there that will cause problems for these tools. Users should exercise due caution. Please notify the author of bugs (dmccune@pppl.gov). 4 Unconverted_Libraries It may not be practical to convert all fortran source libraries to REAL*8 form. Nevertheless, one might want to call routines in an unconverted library. For example, the ancient Ufiles library implements a single precision file format for numerical data. This library is coded with REAL precision and will not be changed. (The Ufiles library is available as an NTCC library module). REAL*8 codes might need access to Ufiles. It would be convenient to be able to generate an REAL*8 interface to Ufiles. For this reason, the `ftoken' module incorporates a code generator tool `r8real'. This program uses an analysis of the target library source code to generate a fortran-90 REAL*8 interface to a non-REAL*8 library. It does this by writing a subroutine which has REAL*8 arguments and arrays, but allocates local copies of the arguments of type REAL. The REAL*8 input data is copied into the local arrays, the original subroutine is called using the local arrays, and then output data are copied from the local arrays back into the REAL*8 data arrays passed to the interface routine. 5 example Suppose we want to call this routine: REAL function foosum(v1,n) INTEGER n REAL v1(n) foosum=0.0 do i=1,n foosum=foosum+v1(i) enddo return end The fortran-90 generated interface would look like this: !---------------------------------------------------------- ! r8real -- generated (f90 fixed form) real*8 interface to: FOOSUM REAL*8 FUNCTION R8_FOOSUM( > R8_V1,N) external FOOSUM REAL FOOSUM ! argument declarations REAL*8 R8_V1(N) INTEGER N ! local (automatic array) declarations integer alloc_stat REAL, pointer :: V1_act(:) REAL, pointer :: V1_ref(:) ! allocation of working arrays... ALLOCATE(V1_act(N) > ,stat=alloc_stat) if(alloc_stat.ne.0) call errmsg_exit( > ' ?R8_FOOSUM -- V1_act ALLOCATE error!') ALLOCATE(V1_ref(N) > ,stat=alloc_stat) if(alloc_stat.ne.0) call errmsg_exit( > ' ?R8_FOOSUM -- V1_ref ALLOCATE error!') ! executable code: copy for input V1_act=R8_V1 V1_ref=R8_V1 ! call to original routine: FOOSUM R8_FOOSUM=FOOSUM( > V1_act(1),N) ! copy back outputs if modified. where(V1_act.ne.V1_ref) R8_V1 = V1_act end where DEALLOCATE(V1_act) DEALLOCATE(V1_ref) ! exit return end Of course, precision is lost-- but this is unavoidable, if the starting premise is that we are not going to convert our single precision code. 5 Procedure To create a REAL*8 interface to a single precision REAL library, use the "r8igen.csh" script provided with the `r8real' program in the `ftoken' module. In a directory containing the sources for the single precision library: > csh -f r8igen.csh r8_output.f r8_output.sub For each global name in source files *.f in the source file directory, an interface routine r8_ is generated and appended to r8_output.f. The file r8_output.sub will contain a substitution table file (suitable for use by fgtok) with a mapping from the original names to the generated r8_ prepended names. Or, to create a REAL interface to a double precision REAL*8 library, use the similar procedure > csh -f r4igen.csh r4_output.f r4_output.sub 5 R4_option The original and default standard use of `r8real' is to create a REAL*8 interface to REAL code, to make it easier to call subroutines in legacy 32 bit libraries. However, the program can also be used to generate a REAL interface to REAL*8 code. This is done by invoking the "-r4" switch. Thus: > r8real foo_sub.f r4_foo_sub.f -r4 generates in r4_foo_sub.f a REAL interface to presumed REAL*8 code in foo_sub.f. One can also use the r4igen.csh script provided with the software. 5 Caveats The r8real program has the following limitations: (1) it cannot handle interfaces defined by fortran-77 ENTRY statements. (2) it needs to know the full shape of each array argument. Therefore, REAL arrays declared with a trailing "*" array dimension result in code that is not immediately usable-- it must be edited first. For the method to work, the size of the array must be known at runtime (so that a matching size local copy can be allocated). (3) the generated code is inefficient, because the code generation is done without knowledge of which arguments of the interfaced routine are input and which are output. So, it must assume that all are both input and output. Where efficiency is a concern, the programmer may want to edit the r8real output generated code. 5 Actual_Usage_Experience The r8real program was used to generate REAL*8 interfaces to the single precision NTCC modules "Ufiles" and "TrGraf". These interfaces were then tested with REAL*8 converted versions of the test driver programs "ugraf1" and "ugraf2", and they work. Although the generated i/o routines are "inefficient" in terms of causing some unnecessary copying of data (since r8real does not know what is input and what is output), this does not have a noticeable impact on performance, since the routines are actually generating reads/writes from/to disk or from/to a graphics terminal, which is much slower yet. In the "Ufiles" and "TrGraf" modules source code, there are routines r8*.for which contain the r8real generated code. 2 Mixed_Language_Issues The emphasis here is on going back and forth between fortran and C or C++. There are two main issues: (1) making the right global names visible at load time, and (2) passing compound data types (fortran-77 CHARACTER variables, and fortran-90 compound types). These are considered in the subtopics. Problem (2) is only partially addressed just for CHARACTER variables. Generally, there are no standards for implementations of fortran compound data types, and the implementations vary from vendor to vendor. Mixed language programming exposes these variations, creating serious portability problems. The most practical advice is to try to avoid passing fortran compound data types through mixed language interfaces. 3 Global_Names Problem: handling global names in the context of mixed language fortran and C programming. On many systems, C routine names need to be "decorated", typically by appending an "_", in order for the name to be resolved at load time. On other systems, the trailing "_" must NOT be present. => example: fortran code: program foo external icsum ii = icsum(2,2) ! add 2 and 2 write(6,*) ii ! ...print 4. stop end C code: /* trailing underscore required or forbidden */ /* (depending on system) for fortran code to find... */ int icsum_(ia, ib) /* icsum(ia, ib) on some systems */ int* ia; int* ib; { int isum; isum = *ia + *ib; return isum; } => general solution: use cpp. An include file "f77name.h" is provided in the NTCC fpreproc module. It defines a macro, F77NAME, which expands to a name with or without decoration as needed for the current machine environment. Thus: #include "f77name.h" int F77NAME(icsum)(ia, ib) int* ia; int* ib; { int isum; isum = *ia + *ib; return isum; } => comment: if the C routine is also called from C code, then, not just the declaration of the routine but every C code reference to the routine needs to be imbedded in the F77NAME(...) macro. => another comment: Cray environments require the C code's function name to be in upper case, for it to be visible to fortran codes. The cpp preprocessor cannot quite do the case conversion. Instead, on Crays only, a mini perl script is run at compile time, to upper case convert anything between the parentheses of an F77NAME(...) macro in C code. 3 Fortran_CHARACTER_variables Problem: passing fortran character strings to C subroutines. The implementation of fortran CHARACTER variables is not standardized; this lack of standardization is exposed when one tries to call C code with fortran CHARACTER string arguments. => solution. There are various levels of sophistication available in dealing with this problem. For an advanced approach, see e.g. http://home.online.no/~arnholm/cppf77.htm If, however, the need to pass CHARACTER data type between fortran and C code is rare, a simpler, more explicit fortran-77 approach might suffice. This consists in explicitly copying the Fortran string into a null terminated C string, in a byte array declared for this purpose. Then, the C routine is called; any output string is also written to a byte array. If the output string is needed as a fortran CHARACTER variable, it is explicitly copied in. "portlib" contains a routine, CSTRING, for doing this copying back and forth. The following code illustrates its usage to do an environment variable name translation [see NOTE]: #include "byte_declare.h" integer bufsize parameter (bufsize=512) byte_declare buffer1(bufsize),buffer2(bufsize) character*200 homedir ... c c copy 'HOME' into a null terminated C style string in buffer1(...) c '2C' specifies fortran -> C call cstring('HOME',buffer1,'2C') c c call the "portlib" C subroutine call cget_env(buffer1,buffer2,bufsize,bufsize) ! C code c c copy the C style null terminated string (buffer2, set in cget_env) c into the fortran CHARACTER variable "homedir". c '2F' specifies C -> fortran call cstring(homedir,buffer2,'2F') ... after the second cstring call, the fortran character string "homedir" contains the user's home directory. NOTE: this code example is for illustration only. It is a lot easier to use "portlib" routine "sget_env" for environment variable (or VMS logical name) translation in real applications. => comment: this solution is simple and robustly portable, but may not be adequate in a case where fortran/C character string translation requirement exists in a performance critical part of a user application. 2 PreProcessing The portability strategy outlined in this document relies heavily on the idea of using the familiar C preprocessor cpp to implement conditional compilation, macro expansion (name substition) and the like. The use of cpp on fortran code is not as straight-forward as one might think, for a number of reasons (see cpp_problems). To avoid these problems, we adopt the following strategy: (a) always use gnu cpp -- it is free, standard, and available on all systems as part of the gnu gcc C compiler. (b) use perl scripts to pre-preprocess and post-preprocess the fortran code being run through gnu cpp. This is done for protection of comments and quote strings, uppercase conversion, and squeezing out of extra blanks inserted by cpp. The pre-preprocessor modifies the source code to make the comments and quote strings safe from CPP, and converts all code statements to uppercase. The post-preprocessor reconstitutes the comments and quoted strings to their original form. These perl scripts prefpp and postfpp, as well as a collection of cpp #include files which are useful for portability, are included in the NTCC module fpreproc. Typical usage takes the form: perl prefpp my_code.f | \ gcc -E -P -undef -D__UNIX -D__RS6000 -I[include file directories] - | \ perl postfpp > compilable_code.f f77 [switches] compilable_code.f The intermediate file compilable_code.f may want to be retained for debugger access to the source code (as compiled). 3 cpp_problems Here are some hazards discovered through recent experimentation with various versions of cpp on a variety of systems... (1) vendor cpp programs vary a lot, creating the hazard of cpp derived portability problems. example: gnu cpp allows indentation of clauses, as in #ifdef _CRAY #define DAXPY SAXPY #else #define SAXPY DAXPY #endif but not all vendor cpp programs allow this. (2) cpp is case sensitive, but fortran is not. this is a problem, the obvious solution to which explodes: #define daxpy SAXPY #define DAXPY SAXPY #define Daxpy SAXPY #define dAxpy SAXPY ...etc... (3) cpp does not recognize fortran syntax for comments example: the use of an apostrophe in a comment, as in "Joe's code", can cause cpp to interpret the whole rest of the input source file as a part of a quoted string-- not the desired result. (4) various versions of cpp sometimes does fortran-unfriendly things such as dropping "//" and all subsequent characters from a line, lengthening code lines by appending unnecessary blanks in macro expansion (creating a 72 character hazard), and (on VMS systems) moving code text around horizontally e.g. into a statement label field, rendering it unfit for compilation. 3 fpreproc_module The NTCC modules library contains a module fpreproc which is made available to assist with cpp pre-processing of fortran source code. This module contains python script to pre-encode and post-decode fortran source to enable safe use of "gnu" cpp, and: f77_dcomplx.h -- #include file for dealing with conversion intrinsics CMPLX, AIMAG, etc. blas_names.h -- #include file for mapping BLAS names to single or double precision form, as needed on the current system. lapack_names.h -- #include file for mapping LAPACK names to single or double precision form, as needed on the current system. linpack_names.h -- #include file for mapping LINPACK names to single or double precision form, as needed. library_names.h -- #include file to reference an #include file for each of several popular math libraries. byte_declare.h -- #include file for system dependent definition of "BYTE_DECLARE" which can then be used to declare byte variables and byte arrays. fp_const.h -- #include file which defines the CONST macro, for mapping floating point constants to E- or D- exponent form as appropriate for the current architecture. and the following is for C code: f77name.h -- #include file F77NAME(...) which is convenient for making C code visible to fortran on all systems. In the C code one writes e.g. #include "f77name.h" ... void F77NAME(fsub)(arg1,arg2,...) { ... } and the global symbol "fsub" will be visible to fortran code. In practice this means appending an "_" (underscore) on some systems, but not on others. NOTE: to make this work on Crays a modification to the makefile is needed; see the section on Makefiles. additional useful #include files will be added as these are developed. 3 special_macros In cpp terminology, any name that is detectable with #ifdef, or is defined with #define, is a "macro". The cpp #include code provided with fpreproc makes use of several macro names: __CRAY -- this is a CRAY environment __DOUBLE_PRECISION -- this is an environment in which REAL*8 is double precision __SINGLE_PRECISION -- this is an environment in which REAL*8 is single precision additional cpp macros may be created by the code author to deal with various code portability / conditional compilation requirements. It is recommended that the convention of a leading "__" (two underscores) be used. This greatly reduces the chance that the CPP name conflicts with a symbol name found in a source code; it is also compliant with the ANSI standard for cpp names (this standard being a way to address the hazard of name conflicts). Other typical choices for macro names to be used by code developers' cpp code might be: __UNIX -- this is a UNIX environment __VMS -- this is a VMS environment __RS6000 -- this is an RS6000 workstation __OSF -- this is a DEC alpha workstation __SGI -- this is an SGI computer etc., etc. It is really up to the code author to invent cpp macro names as needed. However, the code author will then have to provide for their conditional use, on appropriate systems, in a compilation script or makefile. 2 MakeFiles One of the most obvious places where portability issues come up is in the makefile used to compile and load a fortran application. As a practical matter, every fortran compiler ends up requiring a different set of switches for proper functioning. There are many ways to use makefiles and scripts to deal with this system to system variability in compilers. This document does not propose to recommend any one approach. However, several guiding rules are to be suggested: (1) use gnu make. vendors' make programs vary too much. (2) consider portability issues when designing and writing a makefile. (3) document the makefile. The question to ask, when looking at a makefile design is this: How hard would it be to add support for a new machine? Could a reasonably knowledgeable programmer read the makefile, documentation, and make the change within a few minutes, assuming he/she knows the necessary compiler switches on the new target system? If not, the design should be re-examined. 2 Portability_Tools In the course of the development of the porting strategy described in this document, it became evident that various portability code conversions are rather mechanical, and well suited to automation. There arose out of this realization several utilities, which are available from the NTCC modules library inside the `ftoken' module: fgtok -- table driven "token finder / token substitution" tool with fortran-77 specific features. idecl -- make implicit INTEGER and REAL declarations explicit, in order to convert a code to compile with IMPLICIT NONE. cpcheck -- scan a named cpp header file for macros; scan a named fortran source. If the named macros occur in the named source print "present" otherwise print "absent". r8real -- generate a REAL*8 interface to a single precision REAL code (which we don't intend ever to convert to REAL*8), or, generate a REAL(*4) interface to a double precision REAL*8 code. cface -- generate a C-callable interface to a fortran routine with INTEGER, REAL, and CHARACTER*(*) arguments. iface -- generate a fortran-90 interface module from f77 code. All these tools support command lines of the form tool-name [input-file [output-file]] [switches...] or tool-name [input-file] [switches...]. It is recommended that the fixed format fortran source files input-file and output-file be specified first, because, some of the switches take filename arguments, leading to the potential for ambiguity if the source filenames input-file and output-file are specified after the switches. If output-file is omitted, stdout is used; if input-file is omitted, both stdin and stdout are used. The description of the individual tools follow. 3 fgtok fgtok -- table driven "token finder / token substitution" tool with fortran-77 specific features. fgtok can be used for the detection and remediation of common portability problems in typical legacy fortran-77 code. The program works by detecting "tokens" which are generally variable names, subroutine names, constants, and fortran keywords, i.e. words delimited by whitespace and/or fortran arithmetic operators such as "+", "-", "*", etc. The fgtok program has some fortran specific knowledge: tokens inside comments and quote strings are ignored; continuation lines are recognized; the program knows whether or not a given token is the first token in a fortran statement. Certain phrases such as "REAL*8" and "DOUBLE PRECISION" are considered as individual tokens. There are two basic modes of use for the fgtok program. Its "information" mode produces output giving information on an input fortran source file. Its "transformation" mode takes a fortran source file as input and writes a transformed fortran source file as output. These modes will be illustrated by example. A complete list of fgtok switches are given in the "summary" subtopic. 4 Driver_Scripts The fgtok program comes with several driver scripts, intcon.csh, r8con.csh and fgtok_list.csh. "intcon.csh" is described under Fortran Issues -- Integer and Logical Data Types. 5 r8con.csh r8con.csh implements a thorough conversion of a source code to REAL*8 precision. This is described in detail elsewhere in this document, under the discussion of fortran portability issues-- floating point precision. 5 r8con_incl.csh A real*8 / complex*16 conversion script for INCLUDE files; see also "r8con.csh". 5 fgtok_list.csh fgtok_list.csh will run "fgtok -f -upper" on each filename in the list (in a simple foreach loop). To get a sorted list, do: fgtok_list.csh | sort To get a double column list (useful as a template for building a name substitution table), do: fgtok_list.csh | sort | sed 's#.*#& &#' 4 Information_Mode general form of an "informational" fgtok command: fgtok input-filename [] or fgtok [] < input-filename In the examples that follow, the first from will be used. Let "foo.f" be a fortran source for which information is desired. Examples of usage of fgtok: fgtok foo.f # get list of global names ...outputs a list of global names in foo.f; i.e. names of programs, subroutines, functions, and block data modules. fgtok foo.f -w72 # warn about length > 72 chars ...interspersed inside the list of global names, this command outputs a list of source lines in foo.f, for which the executable statement text extends beyond character position 72 (the portion of the statement beyond this position is in danger of being ignored by most fortran-77 compilers). An example with a ficticious foo.f: saturn.pppl.gov> fgtok foo.f -w72 foo **file: foo.f line no. 4 line length .gt. 72 characters warning: abc = a*b*c*d*e*f*g*(aaaaaaaaaa+bbbbbbbbb+ccccccccc+dddddddddddd)*3 fgtok foo.f -wfp # warn about floating point constants. fgtok foo.f -wfp4 # warn about non-double-precision constants. ...hard coded constants are a hazard when converting to a standard floating point precision-- because the compiler will make certain assumptions about the precision of the constant. If these assumptions are applied to a constant in a subroutine argument list, the result can be a type mismatch with the called routine, leading to unpredictable results at runtime. An example of fgtok output: [saturn.pppl.gov|106] fgtok foo.f -wfp ecnter **file: foo.f line no. 106 warning: hard coded floating point constant: IF(ZTEST1.GE.0.0) THEN ___________________/ **file: foo.f line no. 110 warning: hard coded floating point constant: IF(ZTEST1.GT.0.0) ISTAT=ISTAT+4 _____________________/ fgtok foo.f -wcplx # warn about fp intrinsics ...warn about floating point type conversion intrinsics. These can be a portability hazard when creating a numerical code that should port between 64 bit Cray and 32 bit workstation environments. Use of the wrong intrinsic leads to loss of precision at runtime. Example of output: [saturn.pppl.gov|107] fgtok foo.f -wcplx ecnter **file: foo.f line no. 119 warning: floating point conversion intrinsic: IR=IFIX(1.0+(FLOAT(NF)-1.0)*(RAD0-R1)/(R2-R1)) _____________________/ fgtok foo9.f -wfmt # warn about D/E/F/G items in FORMAT stmts ...warn about floating point specifications in FORMAT statements. when converting a code to REAL*8 precision, one might want to increase the number of digits displayed in the output of floating point numbers. Example of output: [saturn.pppl.gov|110] fgtok foo9.f -wfmt foo9 **file: foo9.f line no. 6 warning: floating point FORMAT item: 1000 format(' z = ',1pe11.4) _____________________/ Note that format specifications can also be imbedded in character strings. ** fgtok cannot detect this ** because it does not look inside quoted strings. All -w* options can accept filenames as arguments; in which case the information output is rerouted from stdout to the indicated file. fgtok outputs, in lowercase, global names found in the source file, one per line, in the order they are found. A global name is a name following any one of the following keyword sequences: BLOCK DATA FUNCTION PROGRAM SUBROUTINE In the processing of the source code, keyword match tests do not depend on upper/lowercase coding style. If the output global names are desired in uppercase, add the "-upper" switch: fgtok -upper 4 Transformation_Mode General forms of the "transformational" fgtok command line: fgtok input-file output-file [switches] or fgtok input-file [switches] > output-file or fgtok [switches] < input-file > output-file ... the first command line form will be used in the examples. Note that informational mode switches can be added; this results in output of information / warnings to a separate file. The user should take care that the information output file and the source code output file are not the same. Subtopics show specific transformations that are available. 5 comment To have fgtok insert a comment at the end of the output file, as a record of fgtok processing of the source, do: fgtok foo.f foo.new -cmt "this is a comment" Then foo.new is the same as foo.f except with a comment of the form ! 30apr1999 fgtok "this is a test" added at the end. Normally, -cmt "comment text" is used in conjunction with some other transformation option. These additional options are also inserted in the comment string. For example, fgtok foo.f foo.new -impno -cmt "this is a comment" results in a trailing comment of the form ! 30apr1999 fgtok -impno "this is a test" (and the processing associated with -impno is also performed). 5 Token_substitution fgtok input-file output-file -s substitution-table-file Generally, "substitution-table-file" is a file containing two columns of information: a column of "from" tokens and a column of "to" tokens. Usually, tokens are blank delimited, but certain phrases like "double precision" are counted as a single token. "substitution-table-file" can also have a one of two possible one character flags in the third column: "1" means only apply the substitution if this is the first token in the statement -- recommended for transformation of type declarations, because, "REAL", not in the first token position, is an intrinsic function reference. "*" means apply the substitution inside comments and quote strings. It might be appropriate, when changing the name of a subroutine, to change references to that name in comments and quoted strings (e.g. error messages) as well. a "#" character in the first character position *only* is taken as a comment in a substitution table file. ** example ** fortran source input file: foo.f program foo real a,b double precision foosum complex z a=1.0 b=foosum(a,2.0) z=cmplx(a,b) stop end real function foosum(arg1,arg2) real arg1,arg2 foosum=arg1+arg2 return end substitution file: "r8_precision.sub" which comes with the fgtok program: # map all real & complex types to real*8 and complex*16 # ... D. McCune 2 Apr 1999 real REAL*8 1 real*4 REAL*8 1 real*8 REAL*8 1 double precision REAL*8 1 complex COMPLEX*16 1 complex*8 COMPLEX*16 1 complex*16 COMPLEX*16 1 double complex COMPLEX*16 1 example of execution: [saturn.pppl.gov|134] fgtok foo.f foo.new -s r8_precision.sub [saturn.pppl.gov|135] cat foo.new program foo REAL*8 a,b REAL*8 foosum COMPLEX*16 z a=1.0 b=foosum(a,2.0) z=cmplx(a,b) stop end REAL*8 function foosum(arg1,arg2) REAL*8 arg1,arg2 foosum=arg1+arg2 return end Notes: (1) "foo.new" has a bug in it: the hard coded constant passed as an argument to foosub. See the neighbouring subtopic on conversion of hard coded floating point constants. (2) other standard substitution table files included with fgtok: r8_intrinsic.sub -- map non-generic intrinsic functions to intrinsic form (e.g. DSQRT -> SQRT). r8_icomplex.sub -- map type conversion intrinsics to a standard subset of names (see subtopics). (3) the fgtok -s option can take multiple filenames, in which case the substitution tables are merged. However, it is the user's responsibility to avoid name conflicts in the "from" column. (4) do not use r8_precision.sub and r8_icomplex.sub in the same fgtok command, because both remap the token "REAL". 6 r8_instrinsic.sub This substitution table comes with the fgtok program. [saturn.pppl.gov|142] cat r8_intrinsic.sub # map all intrinsics to generic intrinsics (where possible) in fortran # source code -- for use with D. McCune's fgtok program. # # usage: # # fgtok -s r8_intrinsic.sub -w72 # # means the old version fortran source; # means the new version; these should be distinct files. # # if is omitted, stdout is used. If both # and are omitted, stdin and stdout are used. # # recommended switches: # -w72 ... warns of any occurrences of line length .gt. 72 characters # # note that the floating point / complex number conversion intrinsics # REAL DBLE AIMAG DIMAG CMPLX DCMPLX FLOAT DFLOAT are not mapped here: # see -- r8_icomplex.sub # dsqrt sqrt qsqrt sqrt csqrt sqrt cdsqrt sqrt alog log dlog log qlog log clog log cdlog log alog10 log10 dlog10 log10 qlog10 log10 dexp exp qexp exp cexp exp cdexp exp dsin sin qsin sin csin sin cdsin sin dcos cos qcos cos ccos cos cdcos cos dtan tan qtan tan dasin asin qasin asin dacos acos qacos acos datan atan qatan atan datan2 atan2 qatan2 atan2 dsinh sinh qsinh sinh dcosh cosh qcosh cosh dtanh tanh qtanh tanh iiabs abs jiabs abs dabs abs qabs abs cabs abs cdabs abs iint int jint int iidint int jidint int iiqint int jiqint int dint aint qint aint inint nint jnint nint iidnnt nint jidnnt nint iiqnnt nint jiqnnt nint dnint anint qnint anint iifix ifix jifix ifix dconjg conjg imax0 max jmax0 max imax1 max jmax1 max amax1 max dmax1 max qmax1 max max0 max max1 max amax0 max aimax0 max ajmax0 max imin0 min jmin0 min imin1 min jmin1 min amin1 min dmin1 min qmin1 min min0 min min1 min amin0 min aimin0 min ajmin0 min iidim dim jidim dim ddim dim qdim dim imod mod jmod mod amod mod dmod mod qmod mod iisign sign jisign sign dsign sign qsign sign 6 r8_icomplex.sub ** caution ** this remaps REAL declarations -- convert these to REAL*8 first, before using this substitution table! [saturn.pppl.gov|143] cat r8_icomplex.sub # map all real <--> complex conversion intrinsics to standard names. # source code should # # #include f77_dcomplx.h # # this is REQUIRED because the non-standard name AREAL is used (to undo # fortran's name conflict btw the REAL intrinsic and REAL data type). # # for environment-dependent compile time remapping. # # usage: # fgtok -s r8_icomplex.sub -w72 # # means the old version fortran source; # means the new version; these should be distinct files. # # if is omitted, stdout is used. If both # and are omitted, stdin and stdout are used. # # recommended switches: # -w72 ... warns of any occurrences of line length .gt. 72 characters # # when converting codes to real*8 usage, use r8_precision.sub *first* # to remove all REAL data type declarators, *before* using this # substitution table. # # ... D. McCune 2 Apr 1999 real AREAL dble AREAL areal AREAL float AREAL dfloat AREAL aimag AIMAG dimag AIMAG cmplx CMPLX dcmplx CMPLX 6 More_on_r8_icomplex.sub Typical use of r8_icomplex.sub would be as follows: fgtok foo.f foo.new -s r8_icomplex.sub -incl_cpp f77_dcomplx.h With foo.f containing: [saturn.pppl.gov|180] cat foo.f program foo real*8 a,b complex*16 z a=real(1) b=float(2) z=dcmplx(a,b) z=z*z a=dble(z) b=dimag(z) stop end the above fgtok command creates: [saturn.pppl.gov|183] cat foo.new C! 30apr1999 fgtok -s r8_icomplex.sub #include "f77_dcomplx.h" program foo real*8 a,b complex*16 z a=AREAL(1) b=AREAL(2) z=CMPLX(a,b) z=z*z a=AREAL(z) b=AIMAG(z) stop end The #include file "f77_dcomplx.h" (from fpreproc) contains: #ifndef _F77_DCOMPLX #define _F77_DCOMPLX #ifdef __CRAY #define __SINGLE_PRECISION #else #define __DOUBLE_PRECISION #endif #ifdef __DOUBLE_PRECISION /* workstations */ #define AREAL DBLE #define AIMAG DIMAG #define CMPLX DCMPLX #elif __SINGLE_PRECISION /* Cray */ #define AREAL REAL #undef AIMAG #undef CMPLX #endif #endif /* _F77_DCOMPLX */ and this causes the correct floating point type conversion intrinsic to be used for each type of machine architecture. Not using the correct intrinsic leads to loss of precision. 5 Implicit_None Portability and reliability are more easily maintained in programs which use "IMPLICIT NONE". This forces all symbols to be explicitly declared. Use `idecl' to generate explicit declarations of undeclared variables, then use `fgtok' to insert IMPLICIT NONE statements. Example: [jupiter.pppl.gov|85] cat > foo.f program foo y=1.0 z=2.0 x = foofun(y,z) call foosub(x,y,z) stop end function foofun(x,y) foofun=2*x+y return end subroutine foosub(x,y,z) write(6,*) x,y,z return end [jupiter.pppl.gov|86] idecl foo.f foo.tmp -r8 [jupiter.pppl.gov|87] cat foo.tmp program foo C============ C idecl: explicitize implicit REAL declarations: REAL*8 y,z,x,foofun C============ y=1.0 z=2.0 x = foofun(y,z) call foosub(x,y,z) stop end function foofun(x,y) C============ C idecl: explicitize implicit REAL declarations: REAL*8 x,y,foofun C============ foofun=2*x+y return end subroutine foosub(x,y,z) C============ C idecl: explicitize implicit REAL declarations: REAL*8 y,z,x C============ write(6,*) x,y,z return end [jupiter.pppl.gov|88] fgtok foo.tmp foo.new -impno [jupiter.pppl.gov|89] cat foo.new program foo C============ C idecl: explicitize implicit REAL declarations: IMPLICIT NONE REAL*8 y,z,x,foofun C============ y=1.0 z=2.0 x = foofun(y,z) call foosub(x,y,z) stop end function foofun(x,y) C============ C idecl: explicitize implicit REAL declarations: IMPLICIT NONE REAL*8 x,y,foofun C============ foofun=2*x+y return end subroutine foosub(x,y,z) C============ C idecl: explicitize implicit REAL declarations: IMPLICIT NONE REAL*8 y,z,x C============ write(6,*) x,y,z return end 5 Conversion_of_Constants Use the fgtok -expd option to convert all floating point constants to D-exponent form. Use -expe to convert to E-exponent form. Use -const to convert to CONST macro form. Example: [saturn.pppl.gov] cat foo.f program foo data abc/1.3/ xyz=5.0e1 call foosub(xyz,1.e+15) stop end [saturn.pppl.gov|230] fgtok foo.f foo.new -expd [saturn.pppl.gov|231] cat foo.new program foo data abc/1.3D0/ xyz=5.0D1 call foosub(xyz,1.D+15) stop end [saturn.pppl.gov|230] fgtok foo.f foo.new -const -incl_cpp fp_const.h [saturn.pppl.gov|231] cat foo.new #include "fp_const.h" program foo data abc/CONST(1.3,0)/ xyz=CONST(5.0,1) call foosub(xyz,CONST(1.,+15)) stop end The "fp_const.h" header file defines the CONST macro, which maps at compile time to a D-exponent or an E-exponent floating point constant as appropriate for the current architecture. 5 Insert_cpp_include_file_ref Use fgtok foo.f foo.new -incl_cpp [filename(s)...] To insert one or more lines of the form #include "filename" at the head of the file. An example of this is shown under the "Token_Substitution" subtopic "More_on_r8_icomplex". The 'cpcheck' program can be used to test a source, to see if it needs a given #include file. 5 Detab Use fgtok foo.f foo.new -detab To "detab" source code. There is a non-standard extension to fortran-77 that allows a tab character (instead of six blanks) to be used to cross the statement label field. This causes problems for some compilers. The -detab option replaces tab initiated statements and continuation lines with lines in the standard format. It is recommended to also use -w72 with this command, because some compilers allow the tab to increase the number of characters available for code; with -detab this increase is lost. 5 Fix72 For sources containing code statements exceeding the 72 character limit, one can use fgtok foo.f foo.new -fix72 to generate an automatic line break / continuation line insertion. The line break will not be done in the middle of a quote string or token name. With an overly long quote string, this means that the -fix72 option might not be able to fix all instances. ** use this option with care ** For purposes of maintaining readability of the source code, one might prefer to break over the limit lines by hand with the editor, using fgtok -w72 to find the over long lines. 4 Summary_of_fgtok_command_line fgtok [input-file [output-file]] [switches] ... if output-file is omitted, stdout is used for output. ... if both input-file and output-file are omitted, stdin is used for input, stdout for output. => switches that produce information on the source file. If the source file is transformed, the information generated applies to the output file. Optionally, a file may be specified to receive the informational output. -w72 [warnings-filename] ...warn about lines .gt. 72 characters long. -wfp [warnings-filename] ...warn about any hard coded floating point floating point constants. -wfp4 [warnings-filename] ...warn about hard coded floating point floating point constants with no exponent or an "E" exponent -- i.e. single precision constants. -wcplx [warnings-filename]...warn about floating point type conversion intrinsics. -wfmt [warnings-filename] ...warn about floating point output specification in FORMAT statements. => switches that cause a modified output source file to be written. -s [substitution-table-filenames(s)...] do token substitution, based on the data in the substitution table files provided. -cmt "cmt-string" ...insert a comment at the end of the output file. -incl_cpp [filename(s)...]...insert #include "filename" at the head of the output file. Generate a separate #include line for each "filename" specified. -impno ...insert IMPLICIT NONE at the head of each program unit. -expd or -expe or -const ...convert hard coded floating point constants into D- or E-exponent form, or CONST(mantissa,exponent) macro form. -detab ...detab the source -fix72 ...if a line of length .gt. 72 characters is found, try to fix it by inserting a break and adding a continuation line. 4 Caution A word of caution about the fgtok program. Caution-- the in the fortran-77 standard, whitespace is not significant; the code lines abc = wx +yz ! fgtok sees tokens: abc, wx, yz and ab c = w x +y z ! fgtok sees tokens: ab, c, w, x, y, z are equivalent. **fgtok ignores this** and in the second line it considers ab, c, w, x, y, and z each to be separate tokens. Any fortran style book strongly discourages leaving imbedded blanks inside identifiers; fgtok assumes this style advice has been followed. If this assumption is incorrect, fgtok could produce unexpected results. In fortran-90 (free format source), whitespace *is* significant and *does* delimit tokens. Therefore, the f77 code with imbedded spaces in its identifiers would not port to a fortran-90 only environment (such as the NERSC Cray machines). 3 idecl idecl -- make implicit declarations explicit, in order to convert a code to compile with IMPLICIT NONE. The idecl program can be used to make explicit any implicit declarations in a fortran source code. This is useful if one wants to convert to using "IMPLICIT NONE", to improve the reliability and maintainability of a source code. An example of the use of the idecl program is shown under the description of fgtok (transformation subtopic, then implicit_none subtopic). In determining what symbols to declare, idecl prepares two lists: => an exclusion list: symbols that are fortran reserved names or that are already declared in the source code. If a symbol is referenced in an INCLUDE file, it is presumed to have been declared: an INCLUDE file in an IMPLICIT NONE code should declare the data type of all its symbols. For maintainability reasons this should be done in the INCLUDE file itself, and not in the program(s) that use the INCLUDE file. => a declarations list: symbols that appear to require declaration. When the end of a program unit is found, the missing declarations are generated and inserted into the output file at the head of the program unit being processed. Then, the rest of the program unit is copied (from a temporary file). Then, `idecl' procedes by searching for the next program unit. Unless it finds IMPLICIT statements which change the implicit data type rules, `idecl' uses the standard fortran implicit rule for declarations: symbols starting with i,j,k,l,m, or n are declared INTEGER, the rest are declared REAL (or REAL*8 if the -r8 switch is used). `idecl' accepts the following switches: -r8 -- declare reals as REAL*8 (if present). Also declares quantities as COMPLEX*16 if they are implicitly declared under an IMPLICIT COMPLEX statement in the original source. -I include-file-directory [include-file-directories...] -- a list of one or more include-file-directories. This tells `idecl' where to find INCLUDE files referenced by the source code being converted. `idecl' needs to scan these INCLUDE files for symbol declarations. -move -- option to re-order declarations if necessary. Sometimes, an array dimension, say "N", is used before declared, so that under IMPLICIT NONE a compiler error would occur. The -move option requests that idecl attempt to move the declaration of "N" before the first line where "N" is used. This is not always possible, because other items in the declaration line containing the declaration of "N" might depend on earlier declarations, making the move unsafe. 3 cpcheck The program `cpcheck' scans a named input source file for occurrences of names that are specified in a named cpp header (.h) file. If matching names are found, cpcheck writes "present"; otherwise it writes "absent". If an error occurs, it writes "error" followed by text containing an error message. The cpcheck command line is: csh> cpcheck [source-filename] [-p path [paths...]] \ -incl_cpp header-file [header-files...] For example: csh> cpcheck userprog.for -p $CODESYSDIR/source/incl_cpp \ -incl_cpp blas_names.h lapack_names.h The optional -p switch defines a path (or multiple paths) to a directory (directories) containing the named .h file(s). The required -incl_cpp switch is used to name one or more header files. If the header file(s) contain #include statements which refer to other header file(s), these too are checked. The code will recursively follow nested #include statements to any depth, while keeping a list of scanned files so as to avoid any possibility of an infinite loop. In the case of header files included with the `fpreproc' module, one coulde use "-incl_cpp library_names.h" instead of "-incl_cpp blas_names.h lapack_names.h", because library_names.h #include's the blas_names.h and lapack_names.h files. If source-filename is omitted, stdin is used. The cpcheck program is used in fgtok's "r8con.csh" source code conversion script, to determine wheter a math library name substitution header file should be inserted in the output source file. 3 r8real The program `r8real' can be used to automatically generate an interface from a REAL*8 precision code to a single precision fortran-77 REAL subroutine library, or from a REAL precision code to a REAL*8 subroutine library. Usage: csh> r8real [ -r8 | -r4] [ -tex ] input.f output.f90 > name.sub where: -r8 ... gen REAL*8 interface to REAL library routines -r4 ... gen REAL interface to REAL*8 library routines (-r8 is the default if neither are specified) -tex ... source code is mixed (La)tex and source; process the source only input.f ... is the single precision REAL input code output.f90 ... is the generated REAL*8 -> REAL interface to input.f name.sub ... is a two column name substitution file defining the name mapping from input.f subroutines to the generated interface routines. The name.sub file is suitable for use with `fgtok'. The generated interface routines have the routine names occurring in the input file, with the string "R8_" prepended (REAL*8 interface) or "R4_" prepended (REAL interface). Thus, in the above example if input.f contained subroutine ABC and function XYZ, and the option -r8 is in effect, then the interfaces R8_ABC and R8_XYZ would be generated. Note that interfaces are generated ONLY for routines which actually have non-REAL*8 floating point arguments. COMPLEX to COMPLEX*16 mappings are also handled. But, if a routine only has INTEGER and CHARACTER arguments, no R8_ interface is generated. The interfaces generated by r8real, while close, are not always defect free. In particular, if the dimensioning of an array argument is not fully specified, r8real cannot generate the complete interface, and r8real will write a #-comment with a warning to that effect in the output file (name.sub in the example). The interface will then require a hand edit. Additional edits may also be desirable to improve the efficiency of the interfaces. For a detailed discussion, see the section on porting fortran code / dealing with floating point precision / interfacing to unconverted single precision libraries. The NTCC modules UFILES and TRGRAF include r8real-generated interface routines, to support calls from REAL*8 codes to i/o and graphics routines in these single precision libraries. These interfaces contain minor hand edited modifications. The `r8real' program comes with a driver script, "r8igen.csh". This can be used to generate an interface to a library whose source code is scattered over a collection of source files in a single directory. The "r8igen.csh" script is invoked as follows: unix> csh -f r8igen.csh output.f90 output.sub The input files are *.f, *.for, and *.F in the current directory. The output files are: output.f90 -- the generated interface (R8_ routines). output.sub -- an fgtok style name substitution files, mapping from the original names to the R8_ prepended names. Caution-- this file can also contain warning messages. r8igen.csh generates REAL*8 interfaces. For REAL interfaces, use unix> csh -f r4igen.csh output.f90 output.sub 2 Tool_Use_Example The NTCC library module PSPLINE (which supports 1d 2d and 3d spline and hermite interpolation methods) contains two sets of interfaces, one for REAL and one for REAL*8 codes. Both interfaces support the same functionality (but different precision) and both are used and need to be supported. But, it is highly desirable from a code maintenance standpoint that the REAL*8 interface be generated automatically from the REAL interface. Otherwise, any change to the PSPLINE software must be hand coded twice, and there is a danger that the REAL and REAL*8 routines' functionality would diverge. By using the tools described in this document, it was reasonably straight-forward to create a shell script to automatically generate the REAL*8 precision interface from the REAL precision source code. Here is a sketch of how it was done (the script, pspline_genr8.csh, is included in the PSPLINE module). (1) Use r8real to find all interfaces with floating point arguments and to generate a mapping between standard and "R8" names. The r8real fortran-90 interface code is discarded; here we just want the name mapping. (2) Apply the "r8con.csh" script from fgtok to actually carry out the conversion steps: conversion of floating point declarations, application of name mapping, insertion of IMPLICIT NONE, make explicit any pre-existing implicitly declared names, deal with hard coded constants and non-generic intrinsic functions, etc. (3) The generated file r8_names.table is kept with the PSPLINE source code. If an application which currently uses single precision REAL and calls the PSPLINE REAL interface is to be converted to REAL*8, r8_names.table can be used to change all the PSPLINE calls in the application. (4) This is actually done to create the REAL*8 PSPLINE test driver program "pspltes8.for" from the single precision test driver program "pspltest.for".