12 Mar.87 HvdS R T F / 6 8 K ============= Real-Time Fortran-77 for 68K Processors ======================================= Manual of Compiler and Run-Time Library --------------------------------------- (Version 2.13) H.von der Schmitt Physikalisches Institut Universitaet Heidelberg March 12, 1986 ========================= DRAFT - not yet completed ========================= RECORD OF CHANGES Release Version Changes to former version --------- ------- ------------------------- 25 Oct 83 1.0 20 Dec 83 1.6 Last update of versions 1.x 30 Aug 84 2.0 Versions 2.x start with full F-77, NS16081 support, IEEE 32-bit FP numbers, position independent option, dynamic linkage capability. 10 Dec 84 2.1 In-line subroutines and functions added: _MOVs, _CMPs, OWNER. 01 Mar 85 2.2 Preparation for 68020 with 68881 FPCP. Optimizations in register save/restore; the syntax of the KEEP statement has changed accordingly. Pointer-based COMMON blocks added. Support for a FASTBUS coprocessor added. 25 Mar 85 2.3 Optimizations in array index and statement function evaluation. Corrections to CHARACTER handling. Dynamic storage allocation implemented. 16 Jul 85 2.4 Changed address alignment for 1-byte items. Options SAVE and INTSIZE added. Unified version of runtime library: unformatted, block-oriented I/O and REWIND, BACKSPACE, ENDFILE added. CACHECONTROL statement for 68020 added. RECORD declaration added. Names may contain $ now. In-line routines _SETs added. 27 Sep 85 2.5 Updates in documentation. Residual position-dependence in software floating point emulation of runtime library removed. Native (68000) implementation compatible with APPLE Macintosh. Initialization routine _SYI now returns via A0 instead of via stack. Changes to compiler and library towards zero-length character strings. Format descriptor Iw.n added. Absolute address of COMMON block can be PARAMETER now. NORD style octal constants no longer supported. 20 Oct 85 2.6 Generalizations in pointer-based COMMON blocks. 22 Dec 85 2.7 Run-time variable formats implemented. EQUIVALENCE with status register provided. Improvements in EQUIVALENCE and DATA . 19 Jun 86 2.8 Bugs removed in IAND etc. generic functions. Manual updated. New options CODE=AZTEC and NUMPAR=OFF added. Option CHECK=ON also checks stack limit now at routine entry. CPU=68020 generates position independent code also in the case SIZE=LONG. Bug removed in indexing variable-sized arrays. 11 Oct 86 2.9 Full compatibility with VERSADOS assembler. Improvements in memory indirect addressing for the 68020. Output of version number. Bug removed in Integer*2 Multiply/Divide and string concatenation on the 68020. EXTERNAL now also applicable for variables and Common blocks. Multi-level indirection for RECORDS and pointer-based COMMONs possible now. Integer expressions get definite type when used as actual arguments. 20 Nov 86 2.10 Code generation option for OS9 systems. Cross compiler available now on IBM VM/CMS. Some minor compiler bugs fixed. Static formats checking eliminated. 16 Jan 87 2.11 Changed calling sequence for CHARACTER funct- ions. For OS9, calling sequences compatible with C language. Bug fixed in KEEP statement. RETURN/STOP eliminated before END. EVENTFLAG directive added. Extra OS9 option to switch off C compatibility. ASSIGN statement also usable for dynamic equivalence. Bug fixed in using MOV_s with RECORD arrays. 11 Mar 87 2.12 Effect of EVENTFLAG changed for OS9. New Fastbus hardware subroutines implemented. 12 Mar 87 2.13 Looping mode primitives extended to 32-bit loop count. New option REGUSE introduced to use less address registers within routines. ACKNOWLEDGEMENT --------------- RTF/68K emerged almost entirely from applications in various physics experiments. Most features (adaptation to run-time environment, code efficiency, portability of the compiler, extensions of the language) are due to the suggestions of various collaborators in these experiments, as well as to efforts of people working with 68K processors in other fields; namely V.Mertens, L.Nellen from TASSO at DESY/Bonn W.Jank, S.Cittolin, M.Demoulin, R.Wilcke, D.Samyn, P.Giacomelli, K.Bos, J.Dorenbosch, B. Haynes, J.-P.Porte from UA1 at CERN K.Einsweiler from UA2 at CERN M.LeVine, C.Watson from E802 at BNL E.Elsen, G.Eckerlin, P.v.Walter from JADE at DESY/Heidelberg A.Lacourt, S.Marchioro, W.v.Ruden from ALEPH at CERN Y.Bertsch from L3 at CERN/LAPP Annecy E.Lesquoy from OPAL at CERN J.Hooper from DELPHI at CERN/Copenhagen M.Freisberg from ELTEC Elektronik/Mainz J.Schanzenbach from Univ. Kaiserslautern. The most painful contributions of the people mentioned above and many others had to go into testing RTF/68K. The author is very grateful for all these contributions and, last but not least, for the possibility to start this project (which is rather unusual in the context of a physics institute). This is due to E.Hilger from the Physikalisches Institut Universitaet Bonn. CONTENTS: --------- 1. RTF/68K AT A GLANCE . . . . . . . . . ok 1. Implementation and availability . . . . . ok 2. Syntax . . . . . . . . . . . . . ok 3. Why Fortran with memory-mapped I/O . . . . ok 2. THE COMPILER . . . . . . . . . . . . 1. Source format . . . . . . . . . . . ok 2. Constants . . . . . . . . . . . . ok 3. Data types . . . . . . . . . . . . ok 4. Declarations . . . . . . . . . . . ok 1. Global and local declarations . . . . . ok 2. PARAMETER declarations . . . . . . . ok 3. Data type declarations . . . . . . . ok 4. DIMENSION declarations . . . . . . . ok 5. COMMON declarations . . . . . . . . ok 6. SAVE declarations . . . . . . . . . ok 7. EQUIVALENCE declarations . . . . . . ok 8. EXTERNAL declarations . . . . . . . ok 9. DATA statements . . . . . . . . . ok 10. INCLUDE directive . . . . . . . . . ok 11. Pointers . . . . . . . . . . . . ok 12. RECORD declarations . . . . . . . . ok 5. Subroutines and functions . . . . . . . ok 1. General overview . . . . . . . . . ok 2. Stack assignment during calls . . . . . ok 3. ENTRY specification . . . . . . . . ok 4. ASSEMBLER entry specification . . . . . ok 5. Statement functions . . . . . . . . ok 6. Intrinsic functions and subroutines . . . ok 7. Obtaining the actual number of arguments . ok 8. KEEP directive . . . . . . . . . . ok 9. INTERRUPT and EVENTFLAG directives . . . ok 10. CPU time required for subroutine calls . . ok 11. Type conversions for integer arguments . . ok 6. Other language elements . . . . . . . 1. Arithmetic, logical, and character string expressions . . . . . 2. Assignment statements . . . . . . . 3. Label and variable ASSIGN statements . . ok 4. DO loops . . . . . . . . . . . . ok 5. IF statements . . . . . . . . . . ok 6. GOTO statements . . . . . . . . . ok 7. Input/output statements . . . . . . . ok 8. PRIORITY statement . . . . . . . . ok 9. CACHECONTROL statement . . . . . . . ok 7. Use of pointers . . . . . . . . . . 1. Subroutine arguments as an example . . . 2. Obtaining and changing addresses . . . . 3. RECORDs and pointer-based COMMON blocks . 4. Dynamic storage allocation . . . . . . 5. Autoincrement/decrement addressing . . . 6. EQUIVALENCE with formal arguments . . . 7. Dynamic equivalence with ASSIGN . . . . 8. Registers and absolute addresses . . . . 1. Fields of application . . . . . . . 2. How to use registers . . . . . . . . 3. Saving/restoring registers . . . . . . 4. How to use absolute addresses . . . . . 9. Output from the compiler . . . . . . . ok 10. Compiler directives . . . . . . . . . ok 11. Compilation error messages and warnings . . ok 3. THE RUN-TIME LIBRARY . . . . . . . . . 1. Main features . . . . . . . . . . 2. Details of specific routines . . . . . 1. Floating point arithmetic . . . . . . 1. Software emulation . . . . . . . . ok 2. Code generated for the 68881 FPCP . . . 3. Code generated for NS16081 FPP . . . . 2. Input/output package . . . . . . . . 3. Dynamic interrupt handling . . . . . . 4. CAMAC support . . . . . . . . . . 5. FASTBUS coprocessor support . . . . . 6. Connection to operating system services . 4. IMPLEMENTATION DETAILS . . . . . . . . 1. The compiler entry points JFOR/CONFOR . . 2. Interactive compiling . . . . . . . 3. Representation of data in memory . . . . 4. Initialization at start of program . . . 5. Mechanism for dynamic linking . . . . . 5. USING RTF/68K ON SOME HOST AND NATIVE SYSTEMS . 1. Description of RTF/68K distribution media . 1. Cross-software package . . . . . . . 2. Native software package . . . . . . . 2. Installation and execution on VAX/VMS . . . 3. Installation and execution on CP/M-68K . . 4. Installation and execution on OS9-68K . . . 5. Flow of information . . . . . . . . . A. APPENDIX . . . . . . . . . . . . . 1. Syntax charts of RTF/68K . . . . . . . 2. Current developments . . . . . . . . 3. Associated documentation . . . . . . . 1. RTF/68K AT A GLANCE ====================== RTF/68K means 'Real-Time Fortran 77 for 68K Processors' and consists of a compiler and a run-time library. It provides full (almost full as of today) Fortran 77 for 68K series microprocessors while fulfilling some additional demands, namely: o compatibility with VAX-11 Fortran (which is a superset of the standard ANSI X3.9-1978), o very good overall run-time efficiency, o easy and efficient applicability to real-time and system implementation problems. The entire 68K processor family is supported: 68000/08/10/12 and 68020. While the latter is object-code compatible with the former group, the compiler optionally produces code which utilizes the additional features of the 68020. A variety of floating point options is covered: the MC68881 FP coprocessor, the often-used NS16081 FP peripheral processor, as well as FP software emulation - always obeying IEEE data format. 1.1 Implementation and availability ----------------------------------- RTF/68K is, for the purpose of easy adaptability, written in a comprehensive 'very-high-level' language (4000 lines of source code for the compiler). Nevertheless, the compiler is portable because a translator generates Fortran code out of this source. Thus, the compiler is also available in Fortran form (the same is true for the translator). RTF/68K runs on VAX and NORD as a cross-compiler and on 68K as a native compiler. RTF/68K produces an intermediate assembly code as output. This is then further processed by one of the available assemblers, e.g. AS68 of CP/M or M68MIL. The connection to linkers, e.g. CUFOM, is thus obtained automatically. Calling sequences and debug information generated by RTF/68K conform to the CERN standard for 68K processors. The RTF/68K run-time library is to 95% written in RTF/68K (2000 lines). The small rest, which provides the interfacing to the environment (hardware setup and operating system, if any) is assembler. Thus, RTF/68K is essentially self-contained and easily adapted to any software environment. The RTF/68K package is available free on distribution tape or Floppy disk - ready to be used with VAX computers running VMS and/or the ELTEC E-3 processor running CP/M-68K (see section 5.). Compiler and run-time system can be adapted to other 32-bit systems with little effort. Note that the CERN products M68MIL and CUFOM are not contained in the RTF/68K package. They can be obtained through CERN, Div. DD (free of charge for non-profit organizations). The following table shows the various 68k and other computer systems RTF runs on. 'Compile' means that the compiler itself runs on the system to generate code for either the same system and/or as a host for other systems. 'Exec' means that code generated by RTF can be executed on the system. - Maybe RTF has been transported to additional systems unknown to the author. Computer Operating system Compile Execute -------- ---------------- ------- ------- VAX VMS X NORD SINTRAN X IBM VM X PCS MUNIX X CSEE UNIX X X ELTEC CP/M X X " OS9 X X MicroSys CP/M X X " OS9 X X Macintosh MacSys X X " Aztec-C X X Motorola VersaDOS X X Aleph EVB OS9 X X Eltec E3 -none- X SAC -none- X MicroSys CPU06 -none- X CPU07 -none- X DataSud CPUA1 -none- X Motorola MVME130 -none- X XYCOM IPROTO -none- X Aleph EVB -none- X Amsterdam FAMP -none- X Robcon CPU020 -none- X 1.2 Syntax ---------- Since VAX computers are widely used in fields where 16/32-bit micros might also find their place, and since VAX-11 Fortran is friendly in that it digests virtually every Fortran, the RTF/68K syntax has been made as similar as possible to VAX-11 Fortran. RTF/68K is best described by stating the omissions and extensions as compared to VAX-11 Fortran. The formal syntax description is found in the appendix which is a copy of the syntax charts given in the ANSI X3.9-1978 standard. Omissions are indicated by blobs which close the 'railroads' for the non-implemented constructs. Extensions are extra rails drawn as fat lines. Omissions: - The input/output statements are not yet fully implemented; namely, direct-access read/write and INQUIRE are missing. - The EQUIVALENCE statement does not automatically increase storage allocation in case of size incompatibility. Moreover, only pairs of two variable names are accepted. - DATA statements may contain only one variable per constants list. - COMPLEX data are not supported, and REAL is restricted to single precision (32-bit IEEE format). - Arrays may have up to five indices only. Some important VAX-11 Fortran features are explicitly implemented, e.g. the syntax of hexadecimal and octal numbers; INCLUDE statements; short (one and two byte) data types; and end-of-line comments. Extensions: + Pointer-based variables are available. + Explicit access to the processor (and co-processor) registers is possible under Fortran variable names. + Explicit access to absolute addresses is possible under Fortran names, allowing easy and efficient memory-mapped I/O. + EQUIVALENCE is allowed with pointers and thus also with formal arguments. + Entire COMMON blocks may be based on pointers and can thus be assigned to memory dynamically. This offers almost everything that is available with 'structs' or 'records' in other languages. For convenience such COMMON blocks may be called RECORDs. + Entire COMMONs may also be placed at absolute addresses, e.g. for memory-mapped I/O to 'structured' sets of hardware registers. + Arguments may be passed by value as well as by reference. + Global variables are possible; e.g., all COMMON blocks could be stated once at the beginning of a file and are then valid for all routines that follow. + The data types INTEGER*1,*2 are supported. These are useful for accessing some hardware devices, and may as well serve for saving the famous factor of two or four, e.g. in big pattern recognition tasks. + 'Strong typing' may be forced by IMPLICIT NONE. This is highly recommended as a big step towards writing correct programs. In any case, warnings are given by the compiler if undeclared variables are definitely read-accessed. Misspelt names on the left hand side in assignment statements, however, require IMPLICIT NONE to be detected. + Besides DO index=.. ENDDO and DO WHILE, DO UNTIL and DO FOREVER are available. + The number of routine arguments may vary and can be obtained inside the routine by CALL NUMPAR(num). If this statement is omitted, however, and the actual number of arguments differs from the declared one, a run-time message is issued. + The generated code is position-independent. + All routines are re-entrant. Recursive calls are possible. Libraries are sharable. + Subroutines can serve as interrupt handlers without any software overhead between interrupt vector and routine entry. Also, they may be used as event flag handlers known under some operating systems. + Integer constants may be written in decimal, hexadecimal, octal, binary, and ASCII form. + Dynamic storage allocation is provided with the ALLOCATE ... ENDBLOCK construct. + All the ISA bit-handling functions are implemented with fast in-line code. + Special in-line subroutines and functions provide for efficient block-moves, bit handling, and semaphore control for system implementation. + Embedded assembly code is possible. + 'Hardware subroutines' (implemented as special coprocessors) are supported with generation of fast in-line code. Since some of these concepts are unusual in Fortran (but look a bit like Pascal or C), a few examples might illustrate their use. Remember that without the additional features used here you would have to write it in assembler or use external assembler routines (the calling of which costs extra time). Note that some of the examples below are intentionally quite tricky. equivalence (VIOR,'FFF980'X) ! this states the address of integer*2 VIOR ! .. the hardware register VIOR=0 ! issue command STATUS=VIOR ! .. to read hardware status integer function FAK(N) ! this is the most obsolete if(N.le.1) then ! .. example of recursion FAK=1 else FAK=N*FAK(N-1) endif end subroutine TERMIO ! this routine is activated implicit integer*1 (a-z) parameter (RxD_ready=3) equivalence (STAT,'FF2000'X), * (DATA,'FF2001'X) ! .. whenever interrupt with interrupt 122 ! .. vector #122 occurs if(btest(STAT,RxD_ready)) then I=I+1 BUF(I)=DATA ! .. and then reads some hardware ... logical function THERE(ADDR) ! tests if address ADDR is there integer*2 ADDR,JUNK,SSAVE,S ! (in supervisor mode) integer*4 IVEC(0:255),IVECSAV equivalence (IVEC,0),(S,SR) IVECSAV=IVEC(2) ! save old vector contents SSAVE=S ! .. and status register assign 1000 to IVEC(2) ! link bus errors to label JUNK=ADDR ! test if address responds THERE=.true. goto 1 1000 THERE=.false. ! go here if bus error 1 IVEC(2)=IVECSAV S=SSAVE ! the UNLK instruction will end ! .. clean up the stack program Dyn_Storage real @FIELD(0:3,N,N) ! dynamic storage allocation accept *,N ... allocate 4*4*N**2 for FIELD ! allocate proper number FIELD(0,2,3)=PHI ! of bytes for FIELD ... endblock allocate * for FIELD ! exactly the same as above FIELD(0,2,3)=PHI ! * allocates amount stated ... ! in declaration end block The next example shows how a pointer-based COMMON or RECORD may be used to describe data structures which don't have a fixed location in memory. In this example, the entire structure is even a formal subroutine argument. subroutine ANALYZE(EVENT,...) record /EVENT/ TOF(32),CALO(128),PAT,CNT(6) --or-- common /@EVENT/ TOF(32),CALO(128),PAT,CNT(6) integer TOF,CALO,PAT,CNT ... if(iand(PAT,'11'O) .ne. 0 .and. CALO(2).ge.MIN) ... This is an example where a COMMON block is bound to a fixed absolute address: common/'100000'X/ VMX_SPACE(0:'3FFFF'X),EXTRA_VMX('40000'X) RTF/68K has been used to implement a wide range of different applications, such as the FASTBUS routine package, Mini-GD3, CAMAC readout tasks, a complex VME multiprocessor system for driftchamber readout and on-line analysis, the RTF/68K runtime library, and last but not least the RTF/68K compiler itself systems. 1.3. Why Fortran with memory-mapped I/O --------------------------------------- Given that RTF/68K is made primarily for real-time applications of moderately to very complex microprocessor systems with close connections to hardware ('embedded systems'), it may be worthwile to summarize the arguments for specific extensions offered by RTF/68K. The most crucial and least usual extension is the provsion for memory mapped I/O, and the arguments for this are as follows. a) In the environment of physics experiments, the problem-oriented part of the application (e.g. analysis algorithms) usually will be, or is already, programmed in Fortran. b) One of the essentials of microprocessors with big address space is the simplicity and efficiency of memory-mapped I/O. c) The access to the hardware is only in simplistic cases a small subset of the whole application. Instead, a proven way to keep the hardware simple is by interaction with a programmable device like a microprocessor - hence the name 'embedded system'. The processors (and their programs) act as an interface with one leg deep inside the hardware. d) Complete hardware-independence of the application program is an illusion for this reason. There is no argument why only programs that are anyway unreadable (assembler) should be allowed to be hardware-dependent, unless one wants to maintain admired 'experts' and grateful 'users' that are supposed to understand nothing and hence are not allowed to really exploit the system. e) The access to static absolute addresses is very simple to understand for normal (not 'real-time') Fortran users because Fortran always had a 'hardware-dependent' component - the possible knowledge of how variables are arranged in memory. This access fits directly into the ordinary Fortran syntax. The extension to dynamic addressing by pointers is well known from other high-level languages. Both concepts are straightforward to implement into Fortran. f) A memory-mapped software interface to the hardware is by orders of magnitude faster than even the optimally coded usual subroutine/call interface - just because the processor hardware is able to do it directly in one instruction. g) In summary, Fortran with extensions like in RTF/68K is suited for problem and system programming. It allows to write the entire application in a single high-level language. Non-experts find an easy way to system programming. In fact, at least one very complex readout and analysis system with 34 tightly coupled (shared memory) VME processors has been written in RTF/68K. It was not necessary to use assembler statements. None of the participants started as an expert in this project, and yet is was finished in a very short time. 2. THE COMPILER --------------- This section gives a brief description of the RTF/68K syntax. Mainly the deviations (extensions and omissions) from standard Fortran-77 are reported here. As a reference we take the VAX-11 Fortran which is available on VAX systems and is an extension to standard Fortran-77 insofar as it supports DO-ENDDO, DO WHILE, IMPLICIT NONE etc. as well as compatibility with old Fortran (Hollerith, hexadecimal and octal constants). The reason is that VAX computers will very often act as hosts for, or will be used in close collaboration with, 68K microprocessor systems. The complete definition of the syntax of RTF/68K may be found in the appendix. The deviations from the ANSI standard X3.9-1978 (extensions and omissions) are obvious from the syntax charts reproduced there. 2.1. Source format ------------------ The RTF/68K source code is essentially free-format. Some formatting is required, however, in order to be compatible with the Fortran conventions. The following are extensions to the standard: a) Source lines may start in any column except column six (conflict with marker for continuation lines). They should also not start in column one (possible confusion with comment line marker). b) Continuation lines are marked in column six with a non-blank and non-zero character as usual. However, this mark may also be omitted (continuation is always obvious from context). c) Comment lines are marked by a "*" or "C" or "c" in column 1. "D" and "d" also denote comment lines unless the DEBUG=ON option is selected; then they are proper code lines. d) More than one statement may be given per source line. They must be separated by blank(s), or optionally by ";" for better readability. e) End-of-line comments may be given. They must be preceded by "!". f) Source lines may be up to 132 characters long. Thus, source line numbering in columns 73-80 is not available unless the compilation option LENGTH=72 is chosen. g) Lowercase letters are accepted and shifted to uppercase before interpretation unless they occur within strings. h) Variable names may contain the special characters underline "_" and '$'. Thus, e.g. Start_Time and SYS$SYSTEM are legal names. For determining the implicit data type, the first alphabetic character is taken. i) Names may have any length. All characters are relevant to the compiler. For names belonging to external objects (COMMON blocks, subroutines, functions), length restrictions might arise from the subsequent assembler and linker steps. j) Lines beginning with a "+" in column one are transferred to the output without changes (just stripping off the "+"), thus allowing for embedded assembly statements. The following remarks also refer to the source format, but deal with deviations from the standard, rather than extensions. a) Blanks are meaningful in RTF/68K. For example, DO100 will not be recognized correctly because a blank is required between keyword and label. GO TO and GOTO, END DO and ENDDO, etc. are however explicitly considered. b) The first TAB character met in a source line, if it is within the first eight columns, steps to column 10. Later TABs are converted to blank. The purpose of handling blanks in the described way is to eliminate some of the huge redundancy of the Fortran language; namely, the kind of redundancy which gives a very wrong semantic meaning to statements with small misprints which are yet formally correct. Maybe you know the example of the Mariner mission which failed due to the Fortran statement DO 10 I=1.3 .... where of course a loop was the programmer's intention. But "DO 10 I" is a valid variable name in the standard and was interpreted in that way (because of the "."). - It is invalid and would have been flagged as an error in RTF/68K. 2.2. Constants -------------- According to the data types discussed in the next chapter, there are four types of constants: Integer, Floating Point, Boolean, and Character String. Constants may be placed directly in the code, or they can be accessed by names assigned to them with PARAMETER statements (see 2.4.2.). Not much needs to be said about the latter three types of constants; examples are: Floating Point: 123.456 0.00000123456E8 123456e-3 Boolean: .TRUE. .true. .FALSE. .False. Character String: 'this is the same thing' 22Hthis is the same thing !..but not recommended 'THIS IS not THE SAME THING' Integer constants may be decimal, bit-pattern, or ASCII oriented: 1 decimal; value fits into one byte -128 256 -330 (two bytes) -32768 65535 2000000000 (four bytes) 'FFFF'X hexadecimal (two bytes) '1000'X 'ABCDEF'X (four bytes) '377777'O octal (four bytes) '111'O (two bytes) '101010'B binary (one byte) '1000110001'B (two bytes) 'ADAM'A right-justfied ASCII (four bytes) '0'A (one byte) Integer constants in a context where Floating Point is required are converted at compile-time. Use of integer constants requiring more bytes than are available in the context is flagged as an error; e.g. I1=1000 where I1 is an INTEGER*1 variable. Note that the types Arithmetic (i.e. Integer or Floating Point) Boolean Character string must not be intermixed, as opposed to VAX Fortran where Integer and Boolean may be mixed. RTF/68K would flag this as an error. Integer, floating point, and logical constants used as subroutine arguments are passed as 4-byte entities, unless word-size modifier intrinsic functions or the option INTSIZE=2 are used (see 2.5.6. and 2.10.). 2.3. Data Types --------------- The following data types are available in RTF/68K. a) Integer data types: INTEGER denotes 4-byte signed integer values by default or 2-byte signed integer values with option INTSIZE=2 INTEGER*1 denotes 1-byte signed integer values INTEGER*2 " 2-byte " " " INTEGER*4 " 4-byte " " " DOUBLE INTEGER " 4-byte " " " BYTE " 1-byte " " " NOTE that the integer data types denote always signed values. In operations with operands of different length (e.g. addition of a INTEGER*4 value and an INTEGER*2 value), the shorter lenghts are sign-extended to the longest length occuring. This may cause undesired effects in non-arithmetic operations sensitive to bit patterns (e.g. IOR, IAND etc.). b) Floating point data types: REAL denotes 4-byte IEEE floating point values REAL*4 " 4-byte " " " " ** REAL*8 " 8-byte " " " " ** DOUBLE PRECISION " 8-byte " " " " ** DOUBLE REAL " 8-byte " " " " NOTE that the 8-byte values are not yet included in RTF/68K. They are treated as 4-byte values and a warning is given. c) Boolean data types: LOGICAL denotes 4-byte boolean values by default or 2-byte boolean values with option INTSIZE=2 LOGICAL*1 denotes 1-byte boolean values LOGICAL*2 " 2-byte " " LOGICAL*4 " 4-byte " " Boolean data are determined in any case by one byte only; the most significant byte in memory or the least significant byte in registers, resp. An all-zero byte means .FALSE., any other value, preferrably all ones, means .TRUE. This is in accordance with the "set according to condition" instructions of the 68K family. d) Complex data types: ** COMPLEX denotes a pair of 4-byte IEEE FP values ** COMPLEX*8 " " " 4-byte " " " NOTE that complex data are not yet included in RTF/68K. e) Character string data types: CHARACTER denotes 1-byte ASCII character values CHARACTER*n " n-byte " " " CHARACTER*(*) " m-byte " " " where m is determined by the calling routine for formal arguments; else m=256 by default. Character variables are represented in memory just as the ASCII characters, without any additional information. Thus EQUIVALENCE of character and other variables is possible. Whenever a character variable is accessed, however, a descriptor containing the necessary extra information is formed. Note that character constants, other than variables, are represented as null-terminated strings. Details on the memory representation of the various data types may be found in chapter 4.4. 2.4. Declarations ----------------- Declarations are the field where most of the deviations - extensions and omissions - between RTF/68K and the standard show up. The reason is that on one hand, the aim was to introduce the extensions as far as possible by means of declarations, rather than by modifications in the executable statements. This is a rather obvious method and offers a way to enhance existing programs mainly by adding declarations. On the other hand, the most irregular structures of Fortran are found among the declarations. Some of these structures, e.g. recursion of EQUIVALENCE statements, have been omitted because of their irregularity. Other omissions (lack of some data types) are intended to keep the entire RTF/68K system small, with the on-line applications in mind. 2.4.1. Global and local declarations - - - - - - - - - - - - - - - - - - - A nice way to repeat a common set of declarations (e.g. COMMONs) in many subroutines is with INCLUDE files. Apart from the fact that this is not standard Fortran, the widely used implementations support it. RTF/68K also does. The other way to introduce globally valid declarations is normally offered by block-structured languages like Pascal etc. Fortran knows one level of declarations: the subroutine level. Subroutines have no block level above them (apart from the semantic binding of COMMONs which is recognized only during linking time) nor below them (apart from statement functions). The advantage is that subroutines can be compiled separately. Disadvantages are that conflicting declarations cannot be recognized by the compiler, and that identical declarations have to be repeated in different subroutines as mentioned above. Block structured languages allow for several, nested block levels. The scope of validity of a declaration is the block where the declaration is made and all lower-level blocks contained in it. If a variable already declared is re-declared on a lower block level, the latter declaration is valid within the lower block. RTF/68K ranges between Fortran and block-structured languages: 1) It accepts usual Fortran. The declarations made within subroutines are called 'local declarations' in what follows. 2) It allows for one level above the subroutine level: the 'global declarations'. These are usual Fortran declaration statements which are written once in front of a set of subroutines in a file. The global declarations are valid within all of these subroutines. 3) In order to exclude an obvious source of errors, it is not allowed to re-declare global variables locally. 4) In order that the compiler can recognize global declarations and distinguish them from local declarations within a main program, the PROGRAM statement is mandatory. 5) There is also one extra level of declarations below the subroutine level which allows for dynamic storage allocation (see 2.7.4. for the ALLOCATE-ENDBLOCK construct). Some examples might illustrate the use of global declarations. (a) 'Standard' example. Without use of global declarations, a set of routines would look like: PROGRAM P \ INCLUDE 'I.FOR' | INTEGER II,JJ | Main program REAL ... | ... | END / SUBROUTINE S \ INCLUDE 'I.FOR' | DATA A /1.0/ | Routine 1 ... | END / FUNCTION F(X) \ INCLUDE 'I.FOR' | Routine 2 ... | END / where the INCLUDE file contains e.g. COMMON /A/ A,B,C COMMON /S/ I,J,K PARAMETER (PI=3.14) Using global declarations, one can write it as follows: INCLUDE 'I.FOR' Global declarations PROGRAM P \ INTEGER II,JJ | REAL ... | Main program ... | END / SUBROUTINE S \ DATA A /1.0/ | Routine 1 ... | END / FUNCTION F(X) \ ... | Routine 2 END / Notes: 1) The PROGRAM statement is required in RTF68K. Without it, the local declarations of P would be taken as global, and the first executable statement of P would be flagged as an error. 2) Local declarations stay within each routine. 3) Global declarations of course need not be contained in INCLUDE files. The contents of I.FOR could be given in place of the INCLUDE statements in the example above. 4) The global variable A has been preset with DATA locally. This is possible but bad style. The preferred method would be to give the DATA statement globally. (b) In the above example, A is a global variable. Redeclaration of A like (referring to the example above) FUNCTION F(X) DIMENSION A(100) ... END is illegal and flagged as an error. (c) Global variables should be normally in COMMON blocks, but this is not necessary in principle. Consider, again referring to the example (a), the following contents of I.FOR: REAL A,B,C INTEGER I,J,K PARAMETER (PI=3.14) Still, the variables A,B,C,I,J,K and the constant named PI are globally defined. However, the variables reside now not in the memory for COMMON blocks, but in the space drawn from the stack when the program is initialized. Thus, the connection to separately compiled files which are linked with the example file is no longer as obvious as would be the case for COMMON blocks. In fact, non-COMMON global variables from separate files will normally overlap in memory, unless special care is taken (see chapter 4.4.). The use of such global variables should thus be restricted to system libraries. (d) Also other kinds of declarations may be written in the global declarations part, e.g. the special ones EQUIVALENCE (ILOOP,D7) KEEP D7 The effect is that ILOOP is now a globally declared variable which resides in register D7 for speed, and is saved/restored on each routine's entry/return (see chapter 2.5.8.). Apart from the scope of a declaration, the scope of validity of the value carried by a declared variable has to be considered. The former has to do with the static nesting of blocks as written down in the program source, the latter deals with the dynamic nesting of blocks during execution. Fortran usually disallows recursivity, i.e. a subroutine cannot call itself (neither directly or indirectly through another routine). At least, results of such calls are inpredictable. On the other hand, the standard says that the values of local variables of a subroutine are lost when you leave the routine and enter it again, unless the variables are in COMMON or SAVE. Nevertheless, many Fortran implementations leave those values unchanged. RTF/68K offers an option (SAVE=ALL) to guarantee this, too. Now note the following: recursivity has the consequence that the values of local variables must be lost when leaving the routine. Re-entrancy, which is a requirement in interrupt-driven real-time environments, is a more general case of recursivity. Thus, if you want to use RTF/68K in the field it was made for, the values of local variables are for sure lost when leaving a routine (COMMON or SAVE variables must be used with great care in cases where re-entrancy is required). The reason is of course that space for local variables has to be allocated on the stack, and this space is re-used for other variables after leaving the routine. One further consequence is on DATA statements: variables which are preset with DATA cannot be on the stack. They must be SAVEd or in COMMON. Routines which alter the values of DATA variables are not reentrant. If the values are not altered, the variables are constants and should better be declared with PARAMETER. 2.4.2. PARAMETER declarations - - - - - - - - - - - - - - - PARAMETER declarations assign names to arithmetic, logical, or character string constants. These names can be used in place of the constant, e.g. also as array bounds in DIMENSION declarations. They offer the possibility to define a constant, which is used in many places within a program, in one central place only. Whenever a value is really constant during program execution, PARAMETER is very much preferred over DATA statements. PARAMETER statements may be given globally or locally. Like on the VAX, PARAMETER statements may be written with (standard) or without parentheses, e.g.: PARAMETER (pi=3.14159,max=500,assert='YES') or PARAMETER pi=3.14159,max=500,assert='YES' The data type associated with the name is always the data type of the constant, e.g. REAL for pi, CHARACTER*3 for assert in the example above. Please refer to 2.2. for the various forms of integer constants. One may also state the data type in addition with a type declaration, but only before the PARAMETER statement. The type declaration, if given, must agree completely with the constant used in the PARAMETER statement. E.g., REAL pi; INTEGER max; CHARACTER*3 assert PARAMETER (pi=3.14159,max=500,assert='YES') is legal, while REAL pi,amax; CHARACTER*5 assert PARAMETER (pi=3.14159,amax=500,assert='YES') contains two errors. 2.4.3. Data type declarations - - - - - - - - - - - - - - - The data types available have already been listed in 2.3. above. Consider to be one of INTEGER, INTEGER*1, INTEGER*2, INTEGER*4, DOUBLE INTEGER, BYTE REAL, REAL*4, REAL*8, DOUBLE PRECISION, DOUBLE REAL LOGICAL, LOGICAL*1, LOGICAL*2, LOGICAL*4 COMPLEX, COMPLEX*8 CHARACTER, CHARACTER*n, CHARACTER*(*). Then a data type declaration has the form ([..] denotes optional syntax elements): [,...] where is one of: [] or @ [] . The former is the standard Fortran-77 variable or array declaration, while the latter declares a pointer-based variable or array (@ is the symbol for 'pointer to' or 'address of'). is a usual Fortran name of any length and may, apart from uppercase letters and digits, also contain lowercase letters, underline, and dollar characters. Pointer-based variables have their address assigned or changed during execution of the program; please refer to 2.4.11. and 2.7. for further explanation. Note here that in case of pointer variables or arrays of any data type, four bytes are always reserved for the address of the variable. No space is allocated for the values. The data type, and accordingly the word size, is an attribute of the value of a variable and has nothing to do with its address. 2.4.4. DIMENSION declarations - - - - - - - - - - - - - - - This declaration is in principle redundant in Fortran because array bounds may also be given together with other declarations, e.g. of data type. The syntax is: DIMENSION [,...] where is one of or @ . The former is the standard Fortran-77 array declaration, while the latter declares a pointer-based array. The array bounds have the usual form: ([,...]) with the following choices for : or or . In RTF/68K, up to five are permitted; i.e. tensors of rank five can be handled. The third of the above forms of provides variable array bounds. While normally restricted to formal arguments of routines, RTF/68K allows variable bounds also for pointer-based arrays; see the example of chapter 1.1. above. The mechanism is clear: bounds may be variable because in both cases memory is not allocated during compilation, but only during program execution. 2.4.5. COMMON declarations - - - - - - - - - - - - - - As in the standard, COMMON declarations serve are used to make storage blocks accessible globally for several routines. Storage allocation is usually performed by the linker. In addition, RTF allows to allocate storage under the programmer's control: either to a fixed address known when writing the program or to a variable address known only during execution. For the latter, see also the RECORD declaration below. The syntax of COMMON declarations is on of COMMON [,...] COMMON // [,...] COMMON // [,...] . See 2.4.3. above for . The first and second forms both denote 'blank COMMON'. The latter form denotes 'named COMMON' as usual, but in RTF is also used for 'absolute COMMON' and 'pointer-based COMMON', depending on the from of which is one of: or or @ . The first form results in standard 'named COMMON' and space is allocated by the linker which will normally give an absolute base address to the COMMON block at link-time. If the option COMMON=INDIRECT is chosen, the form has the same effect as the @ form (see below). Still, the operating system takes care of space allocation, but this can then be delayed until load-time (necessary e.g. on the MacIntosh). The second form results in 'absolute COMMON'. Here, the programmer determines the base address of the COMMON block at programming time. The block can by this means placed into special hardware, e.g. an extra fast memory module. The integer constant may also be a PARAMETER declared before the COMMON declaration, e.g. Parameter (VectorBase='100000'X) Common /VectorBase/ IVector(0:255) or just Common /'100000'X/ IVector(0:255) . The system gets no notice about absolute COMMONs. The third form denotes 'pointer-based COMMON'. Here, the allocation of space for the COMMON block can be made more dynamically - at load-time or at run-time, depending on how the pointer is declared. If @ is not declared explicitely somewhere, then the system will take care of space allocation at load-time. For the user, this looks much like ordinary named COMMON, apart from the fact that the base address can still be modified at run-time. The form with option COMMON= INDIRECT has the same effect as the @ form without explicit pointer declaration. In both cases, the base address is held in a storage word addressed relative to the program counter at the end of a program module. Thus, care must be taken when changing this storage word: the base address will be altered only in this program module. Other, separately compiled modules will not be affected (an appropriate waring is given by the compiler). Briefly speaking, @ without explicit pointer declaration is meant for load-time allocation of space as required by some operating systems, and not primarily for run-time allocation under the user's control. If, however, the pointer is declared explicitely before the COMMON declaration, the operating system does not get notice of the COMMON block, and the user has to allocate space at run-time. This is done with an assignment statement for the pointer, e.g.: Integer @Struct,CpuIndex,CpuBase Common /@Struct/ Xlow(1000),Xup(1000) ... @Struct=CpuIndex*'800000'X+CpuBase Xup(I)=XLow(I)+123.4 This can be applied for shifting data structures around in memory. The purpose of the RECORD statement (see below) is exactly the same. The 'scope' of the space allocation is the same as the scope of the declaration of the pointer; i.e. if @Struct in the above example is a local variable of a routine, the entire pointer-based COMMON is defined only in this routine. On the other extreme, if @Struct is itself in an ordinary COMMON block, then the definition is global - i.e., the effect of the @Struct=... statement is global. 2.4.6. SAVE declarations - - - - - - - - - - - - - According to the standard, the SAVE declaration is used to maintain the values of variables when leaving and reentering routines. For many Fortran implementations, this is achieved even without SAVE. In RTF, however, and in full accordance with the standard, SAVE is required if values of local variables of a routine are to be maintained during leave from that routine. Without SAVE, those variables would reside on the stack for re-entrancy and hence their values are lost on exit from the routine. COMMON variables are always maintained. The syntax of the SAVE declaration is: SAVE [,...] or SAVE // . Note that local variables can be preset with DATA statements in RTF only if they appear in a SAVE declaration. There are no such restrictions for COMMON variables. A global SAVE for all local variables can be enforced with the option SAVE=ALL. This may be useful when porting programs from the usual (but non-reentrant) Fortran environments to RTF. 2.4.7. EQUIVALENCE declarations - - - - - - - - - - - - - - - - EQUIVALENCE declarations are the Fortran programmer's way to control storage allocation of variables. In the standard, they can be used to control memory addresses of variables relative to each other; e.g. variables of different data types and formats can share the same storage. As such, EQUIVALENCE is considered bad practice. In RTF, EQUIVALENCE can (and is meant to) be used to control the absolute positions rather than the relative positions of variables, although the latter is also possible. The syntax of EQUIVALENCE in RTF is: EQUIVALENCE (,) or EQUIVALENCE (@,) where is the name of a variable to which storage is not allocated by other means than the EQUIVALENCE statement (e.g., COMMON variables not allowed here). may be one of or or or @ . is the name of a simple variable, array, or an array element reference. This form is used for the standard EQUIVALENCE statements. Note that only pairs of arguments may appear in RTF. is used to give a fixed absolute address to a variable. This is useful for accessing special hardware locations easily, e.g.: Integer*1 IOReg Equivalence (IOReg,'FF0021'X) ... IOReg=15 . is used to force a variable in a register of the CPU or a coprocessor. The following symbols are reserved words when appearing as the second argument of EQUIVALENCE: D0, D1, D2, D3, D4, D5, D6, D7 for the data registers of the 68K processor A0, A1, A2, A3, A4, A5, A6, A7 or SP for the address registers of the 68K processor F0, F1, F2, F3, F4, F5, F6, F7 for the floating point registers - valid for the MC68881 coprocessor, the NS16081 processor, and software emulation SR for the status register of the 68K processor. Variables of any data type (1, 2, or 4 bytes long) except CHARACTER, as well as pointers, may reside in the data or address registers. Typically, values should be kept in data registers, and pointers in address registers. The floating point registers are useful only for variables of type REAL. Variables EQUIVALENCEd with the status register must be declared INTEGER*2. The purpose of putting values or pointers in registers is twofold: 1. a considerable gain in speed for frequently used variables or pointers, 2. a way of interfacing e.g. with operating systems that expect or deliver parameters in registers. Note that registers used by the programmer via EQUIVALENCE, unlike registers allocated automatically by the compiler, are not saved on entry and restored on exit from the routine that uses them. The KEEP directive (see below) has to be used for this purpose if it is desired to keep register contents unaltered for the outside world. This is a must for interrupt service routines and normally true also for usual routines. @ is used for EQUIVALENCEing with pointers. This is useful e.g. when making an equivalence with a formal argument which is passed by reference. Note that whenever variables are pointer-based, only their pointer may be used in EQUIVALENCE because the allocation of variables are not known at compile time. Examples of EQUIVALENCE: 1 ...the standard way: INTEGER*1 I1(4); INTEGER*4 I4 COMMON /XX/ I4 EQUIVALENCE (I1,I4) 2 ...with an absolute address: INTEGER*2 RegisterFile(0:15) EQUIVALENCE (RegisterFile,'3400006'O) ... RegisterFile(0)=1 3 ...with data registers: Function Dot(N,A,B) Integer A(N),B(N),N,I,S Equivalence (I,D7),(S,D6) Keep D7,D6 S=0 Do I=1,N S=S+A(I)*B(I) EndDo Dot=S End 4 ...with address registers in addition: Function Dot(N,A,B) Integer A(N),B(N),N,I,S,AA(N),BB(N) Equivalence (I,d7),(S,d6),(@AA,a4),(@BB,a3) Keep a3,a4,d6,d7 @AA=@A @BB=@B S=0 Do I=1,N S=S+AA(+)*BB(+) EndDo Dot=S End Note the special array indexing used in the formula. Writing S=S+AA(I)*BB(I) or S=S+A(I)*B(I) would both give exactly the same result, but the 'autoincrement' form chosen is by far the fastest. It is only available if the pointer to the array resides in an address register. The compiler generates the post-increment mode of addressing of the 68K processor for it. The code generated from S=S+AA(+)*BB(+) is (option CPU=68020): MOVE.L (A4)+,D2 MULS.L (A3)+,D2 ADD.L D2,D7 . Using as an array reference e.g. BB(-) results in the pre-decrement addressing mode. In the above example, it is in principle not necessary to copy the pointer of array A into an address register. The first two formal arguments of routines are copied to address registers automatically by the compiler in order to have fast access to them (unless option REGUSE= OFF is used). Thus also the special addressing mode is available for A. But beware: the pointer is of course changed by using the special addressing mode! This does no harm in the example because the pointer is no longer used afterwards. 5 ...with floating point registers: Equivalence (X,F7),(X2,F6) Keep F6,F7 ... X2=X**2 V=exp(X2-sin(X2)*cos(X)+sinh(X)) The code generated for the above formula is in the case of the 68881 coprocessor (option FLOAT=HARD): FMOVE.X FP6,FP0 FSIN.X FP6,FP1 FCOS.X FP7,FP2 FMUL.X FP2,FP1 FSUB.X FP1,FP0 FSINH.X FP7,FP1 FADD.X FP1,FP0 FETOX.X FP0,FP1 FMOVE.S FP1, The advantage is obvious from this code: the coprocessor is utilized optimally. Apart from fetching the result, no operand moving between CPU and coprocessor takes place. 6 ...with the status register: Equivalence (CpuStatus,SR) Integer*2 CpuStatus ... If(CpuStatus.lt.0) ... ! check if in TRACE mode Note that access to the status register is only allowed in supervisor mode. The PRIORITY=... statement (see below) can be used if only the priority part of the status word should be affected. Also note that there is no KEEP for the SR. Explicit save/restore to e.g. a local variable must be used for temporary changes to the status register. In interrupt service routines, the status before interrupt occurance is restored automatically on routine exit. 7 ... with formal arguments: Subroutine S(X) Real X(100) Integer IX(100) Equivalence (@IX,@X) ... Subroutine T(%VAL(Y)) Real Y Integer IY Equivalence (IY,Y) ... Note that Equivalence (IX,X) which semantically looks the same would not be accepted by the compiler. Only items which occupy memory with addresses known at compile time can be equivalenced; in the first example above, those items are the pointers to X and IX, while in the second example, the values of Y and IY. As can be seen from the examples above, registers used by the programmer with EQUIVALENCE should be allocated starting from the upper end of the available sets of registers, i.e. starting with D7, D6... for the data registers, A4, A3... for the address registers, and F7, F6... for the floating point registers. A7, A6, and A5 are reserved for use by the system (thus there is often a shortage of address registers). The compiler starts allocating registers from the lower ends of the sets. If a clash between user's and compiler's allocation occurs, a warning is given. The restrictions in the standard use of EQUIVALENCE will be removed in a later release of RTF. 2.4.8. EXTERNAL declarations - - - - - - - - - - - - - - - EXTERNAL declarations are normally used to state that a name refers to a subroutine or function in cases where this is not obvious from the context, i.e. when such names are used as actual arguments of routines. This is also the case in RTF. In addition to that, also variables and even entire COMMON blocks can be made to refer to external objects (e.g., data which are located at an entry point of an assembler routine). This corresponds to the IMPORT and EXPORT variables known in Pascal. In order to distinguish those variables from standard EXTERNALs and to provide an access method by the standard linkers, they have to be pointer-based, i.e. their names have to be preceded by '@' in the EXTERNAL declaration. Examples: External @Iarr Integer Iarr(20,20) ... Iarr(5,5)=0 External @Com,@I Common /@Com/ X,Y,Iz(100) ... X=Y+Iz(I) In these examples, the addresses of the external variables (Iarr and X,Y,Iz,I) are equated by the linker to entry points in other code modules. 2.4.9. DATA statements - - - - - - - - - - - - DATA statements belong to the declarations part of a routine and are used to preset variables with values once prior to execution of the user's code. Values may be changed afterwards; if not, i.e. if the variables are in fact constants, PARAMETER declarations should be used whenever possible (e.g. not for arrays). DATA statements result either in blocks of data put into the appropriate storage location by the loader, or in pieces of code executed after loading and before the user's program is entered, depending on the operating environment. DATA statements must be put between the last of the other declarations and the first executable statement, statement function definition, or INTERRUPT attribute of a routine. In RTF, DATA statements have no purpose in addition to the standard one. Instead, there are some restrictions for the variable list. The syntax in RTF is: DATA //[[,]/...] . stands for a simple variable, an array name, an array element reference, or a pointer. Note that only one item, not a list, is allowed for . Implied-Do lists are not allowed. Moreover, must be declared SAVE if it is a local variable. Only global, COMMON, or SAVE variables can be preset with DATA. Example: Subroutine Junk Logical FirstTime Integer Arr(0:9,2) Save FirstTime, Arr Data FirstTime/.true./, Arr/10*0,10*1/ ... is legal in RTF, while omission of the SAVE or writing Data FirstTime,Arr/.true.,10*0,10*1/ are not. The restrictions will be removed in a later release of RTF. 2.4.10. INCLUDE directive - - - - - - - - - - - - - The INCLUDE directive is an extension to the standard present in most Fortran implementations. It is useful for inserting a piece of source text from a different file, especially one which contains all COMMON declarations used by a set of subroutines. The syntax in RTF is: INCLUDE '' (VAX style) or $INCLUDE (NORD style). The syntax and interpretation of depends on the operating system under which RTF is executed. E.g., under CP/M, the drive identifier of the main source file is prefixed to the file name of the INCLUDE file as the default drive. 2.4.11. Pointers - - - - - - - - - Pointers contain the addresses of variables rather than the values, which are carried by the variables themselves. The concept of accessing values indirectly through pointers is not unknown to Fortran users; it is used all the time when replacing the formal arguments of a subroutine definition with the actual arguments of a subroutine call. By means of the subroutine call, the programmer determines the addresses of the formal arguments. This is the only occurence of pointers in Fortran. The additional feature available with pointer variables as in RTF (or C, Pascal) is that the programmer has full control on the addresses of variables, not only of formal arguments. Many illustrations for pointers were used already on the previous pages; in summary, pointers are adequate for variables or data structures whose position in the 68K's address space is determined only at run-time. Paragraph 2.7. is devoted to the use of pointers. Only one important rule that must be obeyed for declaring pointer variables is stated here: If the address of a variable is to be changed in the executable part of a routine, the variable must either be declared as a pointer variable, or not declared at all. Examples: Integer @ITDC @ITDC=ICamAd(B,C,N,A,F,T) JTDC=ITDC or @ITDC=ICamAd(B,C,N,A,F,T) JTDC=ITDC are both correct and give the same result. It is not correct to declare ITDC without the pointer mark '@': Integer ITDC @ITDC=ICamAd(B,C,N,A,F,T) JTDC=ITDC The compiler gives an error message in this case for the second statement which tries to modify the address of a variable which is not pointer-based, but has a fixed address assigned by the compiler. (Semantic meaning: in the above examples it is assumed that the CPU has memory-mapped access to a CAMAC system, and that the function ICamAd returns the address of a CAMAC object with given branch B, crate C, station N, subaddress A, function F, and wordsize T. Once the address is assigned to the pointer variable ITDC, the access to it, i.e. the I/O to the CAMAC system, is as simple and as fast as the access to a normal variable in memory.) Note also that there exists in RTF an extension to the ASSIGN statement which is normally used to handle addresses of statement labels. Here it can be used for addresses of variables or array elements as well. This provides the func- tionality of the 'abbreviations' known from the transputer language OCCAM. For example, ASSIGN A(I,J,K) to B(0) B(0)=B(1)-B(-1) has the effect that a linear array B is defined which has the same data type as A and where the element B(0) has the same address as the element A(I,J). B can the be used as a shorthand form for A(I,J,K), B(1) for A(I+1,J,K), etc. This speeds up access very much in cases where A(I,J,K) and elements with constant offset relative to A(I,J,K) are accessed frequ- ently - especially if @B is EQUIVALENCEd with an address register. 2.4.12. RECORD declarations - - - - - - - - - - - - - - The pointer-based COMMON blocks have been described already in paragraph 2.4.5.; a RECORD is exactly the same as a pointer-based COMMON whose pointer is explicitely declared. Syntax: RECORD // [,...] is the name of a variable not declared so far, or declared as a pointer variable. Included in the latter case are formal arguments of routines. As mentioned earlier, the purpose of RECORDs is to have data structures that can be imposed on any block of storage, just by setting the address of the RECORD properly. Examples: 1 ...the pointer is not declared elsewhere, hence it is allocated on the stack (local variable): Subroutine Junk Record /D/ Flags,X,Y,Z Integer Flags; Real X,Y,Z @D=... ! set base address of RECORD If(Btest(Flags,0)) X=... 2 ...the pointer is declared as a COMMON variable: Subroutine Junk Common /Bases/ @D ! assume @D is set elsewhere Record /D/ Flags,X,Y,Z Integer Flags; Real X,Y,Z If(Btest(Flags,0)) X=... 3 ...the pointer is declared as a formal argument: Subroutine Junk(D) ! @D supplied by subroutine call Record /D/ Flags,X,Y,Z Integer Flags; Real X,Y,Z If(Btest(Flags,0)) X=... In the second example, it would be illegal to omit the '@' in the COMMON declaration. 2.5. Subroutines and Functions ------------------------------ This chapter is intended to provide details of the implementation with respect to subroutine and function definitions and calls rather than the language-guide type of information on this topic. Also, the extensions available for interrupt handling etc. are described. 2.5.1. General overview - - - - - - - - - - - - Passing of arguments from the calling program to a subroutine or function can be done in RTF by three different means: by VALUE - the value of a variable, constant, or expression is passed; by REFERENCE - the address of a variable, array, or auxiliary variable containing the value of a constant or expression is passed; by DESCRIPTOR - the address of a descriptor block which contains further information is passed (details are found below). The default method is passing by reference for all objects except for character variables or character expressions which are passed by descriptor. Arguments optionally may be passed by value. In the call as well as in the subroutine/function declaration, they have to be prefixed by a "#" for this purpose. This calling method is restricted to simple variables and constants. It is slightly faster than call by reference and may be necessary for compatibility with routines written in other languages calling or called by RTF routines. Trailing actual arguments may be omitted (short parameter list) if precautions in the called routine are taken. All arguments are transferred via the stack. Arguments are pushed on the stack before the call and removed from the stack after the call by the calling program. The first two arguments (i.e., their addresses when called by reference) are copied to address registers inside of the called routine in order to have fast access to them. With the option REGUSE=OFF, this optimization is switched off. This may be useful in cases where the compiler runs out of address registers within subroutines. Function results are returned via the register D0 if the value of the result fits into 32 bits (so far, everything except type CHARACTER). Otherwise, the stckframe contains an address, provided by the caller, which points to where the value has to go. When passed by reference, actual arguments may be simple variables, expressions, arrays (those three not of type CHARACTER), EXTERNALs, and constants (including string constants). Formal arguments may be simple variables, arrays (both not of type CHARACTER), or formal subroutines or functions. Passing by descriptor applies for actual arguments which are simple variables or expressions of type CHARACTER, and any formal arguments of type CHARACTER. When passed by value (i.e. with the '#' in front) actual arguments may be simple variables, constants, or expressions. Formal arguments may then only be simple variables. Type CHARACTER is not allowed for call by value. Side exits from subroutines are denoted in the standard way by "$" or "*". Secondary entry points are defined by ENTRY as usual. In addition, entry points coded with embedded assembler statements (and thus invisible to the compiler) can be declared with the "ASSEMBLER name" statement (see below). If inside of a function definition its declared name appears with an argument list, e.g in expressions, this results in a (legal) recursive call of the function. Occurence of the function name inside the function without an argument list is handled in the standard way, i.e. as the present value of the function identifier. All registers explicitely used by the compiler are saved during subroutine calls. This is required for reentrancy. The registers referenced by the user with EQUIVALENCE (variable,register) are, however, not automatically saved. The KEEP statement must be used to save these registers unless the routine is intended to change one or all of these registers. Subroutines may be statically designated to handle certain interrupts. This is requested with the "INTERRUPT vectornumber" statement which has to follow immediately all declaration statements of the subroutine. The specific meanings of vectornumber values is hardware dependent. The effect is to modify the appropriate interrupt vector cell during initialisation such that it points to the routine. The current priority of a task may be changed with the statement PRIORITY=prio, where prio is a constant between 0 and 7. The user should be aware of the effects resulting from priority changing. In routines serving interrupts, the priority is automatically reset to its value prior to the interrupt when returning from the routine. The code generated by RTF for subroutine definitions and calls is compatible with the 'CERN calling conventions for 68k processors' with the exception that registers are saved by default inside of the called routine instead of by the caller. This scheme is necessary for direct interrupt server routines. A compilation option, REGSAVE= OUTSIDE, forces RTF to stick to the CERN scheme also for register saving. In addition to, but compatible with the CERN scheme, the actual number of arguments is passed to the called routine via D0 to be able to handle short argument lists. For the option CODE=OS9, register assignments stick to OS9 rules instead of CERN conventions; the actual number of arguments may then be passed via the condition code register, CCR. 2.5.2. Stack Assignment during Calls - - - - - - - - - - - - - - - - - - - In the following, AG denotes the base register for the global/ static variables, and AL denotes the base register for local variables. For all options but OS9, AG is A5 and AL is A6. For OS9, ist is just the other way around: AG is A6 and AL is A5. Actual parameters (addresses, 32-bit values) are pushed on the stack by the calling program in the reverse order of the argument list. Note that pushing uses pre-decrement addressing mode which lowers the stack pointer. After that, the call (with assembler instruction JSR) pushes the return address on the stack. In the called routine, the LINK instruction saves the address register AL on the stack and reserves space for local variables. Local variables are addressed relative to AL, i.e., with the d(AL) mode. Then, the entry address to the routine and an all-zero word are written to locations -4(AL) and -12(AL) (-8(AL) is unused), resp., providing information for the symbolic debugger and traceback. This is done only if the option DEBUG=ON is chosen. Following these words, the contents of the necessary data-, address-, and floating point registers are saved within the local space. Local program variables are allocated from -204(AL) downwards. It is normally the calling program's responsibility to remove the arguments from the stack after return from the call, by moving the stack pointer with an LEA d(A7),A7 instruction. If the called routine takes a side exit, i.e. it does not return into the proper point of the calling sequence, it removes the arguments itself. In this case, short argument lists are not allowed, because the number of arguments stated in the routine declaration is removed. If the called routine is a function, it returns its result via D0. If the result occupies 4 bytes or less, the value is returned directly in D0. If the result occupies more than 4 bytes, the value is returned to the memory location pointed to by D0. This is the case for type CHARACTER functions only. Note that those functions are thus not able to obtain the actual number of arguments in D0. The stack layout after entry into the called routine (more precisely, after registers are saved and debug information is stored) is shown in the figure below. If you require access to the stackframe from Fortran, use the predeclared INTEGER*4 array STACKFRAME with indices given as below; e.g., STACKFRAME(21) contains the return address. For non-OS9: | | | stack before call | | | |---------------------------| +(n+1)*4 | parameter n | STACKFRAME(n+21) |- - - - - - - - - - - - - -| : : |- - - - - - - - - - - - - -| +8 | parameter 1 | STACKFRAME(22) |---------------------------| +4 | return address | STACKFRAME(21) |---------------------------| A6 --> | link location | STACKFRAME(20) |---------------------------| -4 | entry address | STACKFRAME(19) |---------------------------| -8 | (undefined) | |---------------------------| -12 | (zero) | STACKFRAME(17) |---------------------------| -16 | A7 (if requested) | : . data and address : : . register save area : : . 4 bytes/register : -76 | D0 (always saved) | STACKFRAME(1) |---------------------------| -80 | FP7 (if required) | : . floating point : : . register save area : : . 12 bytes/register : -172 | FP0 (if required) | |---------------------------| -176 | | : (reserved) : -200 | | |---------------------------| -204 | | : space for local variables : A7 --> | | |---------------------------| | | | free stack after call | | | For OS9: | | | stack before call | | | |---------------------------| \ +(n-1)*4 | parameter n | STACKFRAME(n+19) | only |- - - - - - - - - - - - - -| | : : | if |- - - - - - - - - - - - - -| | +8 | parameter 3 | STACKFRAME(22) | n>=3 |---------------------------| / .... parameter 2 in D1 ..... .... parameter 1 in D0 ..... +4 | return address | STACKFRAME(21) |---------------------------| A5 --> | link location | STACKFRAME(20) |---------------------------| -4 | entry address | STACKFRAME(19) |---------------------------| -8 | (undefined) | |---------------------------| -12 | (zero) | STACKFRAME(17) |---------------------------| -16 | A7 (if requested) | : . data and address : : . register save area : : . 4 bytes/register : -76 | D0 (always saved) | STACKFRAME(1) |---------------------------| -80 | FP7 (if required) | : . floating point : : . register save area : : . 12 bytes/register : -172 | FP0 (if required) | |---------------------------| -176 | | : (reserved) : -200 | | |---------------------------| -204 | | : space for local variables : A7 --> | | |---------------------------| | | | free stack after call | | | Summary of register usage in connection with calls: D0: scratch register; on entry, contains number of actual arguments, or address of first parameter if OS9. On exit, contains result if function call, else the value on entry. D1: \ .. | working registers. For OS9, D1 contains address of 2nd parameter. D7: / A0: address of first actual argument (if >= 1 formal arg. present and REGUSE=ON; else work register). A1: address of second " " (if >= 2 formal args. present and REGUSE=ON; else work register). A2: \ A3: | working registers. A4: / AG: global space pointer (global user variables and system variables, d(AG) ). AL: local space pointer (local user variables inside routine ,d(AL) with negative offset). A7: stack pointer. Format of character string descriptors: Whenever a character VARIABLE is referenced, a descriptor of this variable is set up in the following format (3 longwords occupied): < - - - - 32 bits - - - - -> +--------------------------+ |address of first character| Regardless of substring indices |------------+-------------| |lower index | upper index| These are the substring indices |------------+-------------| |pointer to next descriptor| Zero if nothing concatenated. +--------------------------+ For example, assume CHARACTER*10 A,B. Then the descriptors look as follows for various character expressions: +--------------------------+ | address of A(1:1) | for A |------------+-------------| | 1 | 10 | |------------+-------------| | 0 | +--------------------------+ +--------------------------+ | address of B(1:1) | for B(2:5) |------------+-------------| | 2 | 5 | |------------+-------------| | 0 | +--------------------------+ +--------------------------+ | address of A(1:1) | for A(1:5)//B(6:10) |------------+-------------| | 1 | 5 | |------------+-------------| | address of descr.2 |-- +--------------------------+ | -- - - - - - - < - - - - - - - - | +--------------------------+ ->| address of B(1:1) | |------------+-------------| | 6 | 10 | |------------+-------------| | 0 | +--------------------------+ In calls, the address of the descriptor of character variables or expressions is given, with the sign bit of this address set to 1. This allows to distinguish between descriptors and normal addresses. For character CONSTANTS, the address of the first character is given, with the sign bit cleared as usual. Character constants are null terminated. As soon as an expression is formed with constants, e.g. 'ABC'//'DEF', however, a descriptor is set up. 2.5.3. ENTRY specification - - - - - - - - - - - - - - The ENTRY specification is used to provide alternative entry points into subroutines and functions; the same is true for RTF. The formal arguments of an ENTRY can differ from those of the main entry point in number, names, and order. In RTF, when a formal argument has the same name as one of the main entry or a previous ENTRY, a warning is given. After the ENTRY, the ordering of arguments stated there is maintained statically. Example: Subroutine Alfa(A,X,Y) Real A,B,X,Y A=X-Y Entry Beta(B,Y,X) ! CAUTION X and Y change roles B=X-Y End In this odd example, the following happens when entry Alfa is called with CALL ALFA(O,P,Q). First, O is assigned the value P minus Q. Then (after skipping the non-executable ENTRY specification) O is assigned the value Q minus P. That is of course not what you want - so be aware of this implicit EQUIVALENCE scheme when using ENTRY! 2.5.4. ASSEMBLER entry specification - - - - - - - - - - - - - - - - - - - This extension to Fortran is provided for pieces of code written in embedded assembly code. Other than writing the entry point declaration also in embedded assembly, the RTF ASSEMBLER entry specification makes it an 'official' entry point which fully sticks to the rules imposed by the interactive linker, symbolic debugger, and traceback. Formal arguments are not allowed at ASSEMBLER entry points. Example: Subroutine Dummy Assembler Kill + ILLEGAL Assembler Fiddle + MOVE.L #4711,A7 + RTS End 2.5.5. Statement functions - - - - - - - - - - - - - - Statement functions conform to the standard. They are implemented not like macros which are inserted at every call point, but are entered with JSR and left with RTS much like external functions. Only the way of argument passing is faster: the arguments of a statement function as well as its value are local variables of the program unit containing the statement function. In effect, arguments are passed by value. Also, time for saving and restoring registers is minimized. 2.5.6. Intrinsic functions and subroutines - - - - - - - - - - - - - - - - - - - - - - The following paragraph describes the functions and subroutines which are recognized by the compiler as non-external and are handled in a special way. The purpose is twofold: a) run-time efficiency, and b) special handling of involved data types (generic functions). 2.5.6.1. Intrinsic functions Note that the routine names specified in this chapter cannot act as substitutions for dummy routines in subroutine or function calls; i.e. they cannot be declared EXTERNAL. For example, CALL SUB(X,ABS) is NOT allowed. They may, of course, be used in expressions which are used as actual arguments: CALL SUB(ABS(X)) is allowed. The reason is that fast in-line code is generated for the routines described in this chapter rather than subroutine/function calls. For some of the routines, this is only true if certain compiler options are chosen. 2.5.6.1.1. Bit manipulation functions i = IAND(j,k) or I is the bit-by-bit product of j and k. i = AND (j,k) Both must be INTEGER (any size), as is the result. Example: MANTISSA=AND(IREAL,'7FFFFF'X). i = IOR (j,k) or I is the bit-by-bit sum of j and k. i = OR (j,k) Both must be INTEGER (any size), as is the result. Example: PATTERN=IOR(PATTERN,ENDFLAG). i = IEOR(j,k) or I is the bit-by-bit difference of j and k. EOR (j,k) or Both must be INTEGER (any size), as is the IXOR(j,k) or result. XOR (j,k) Example: ITOGGLE=EOR(ITOGGLE,1). i = INOT(j) or I is the bit-by-bit inverse of j. i = NOT (j) The data type of the result is that of j. Example: ZERO=IAND(MASK,NOT(MASK)). i = IBSET(j,k) I is the same as j but the k-th bit set. The data type of the result is that of j. The data type of k must be INTEGER (any size). K is evaluated modulo 32. Example: REALMINUS=IBSET(REAL,31). i = IBCLR(j,k) I is the same as j but the k-th bit cleared. The data type of the result is that of j. The data type of k must be INTEGER (any size). K is evaluated modulo 32. Example: REALABS=IBCLR(REAL,31). i = IBCHG(j,k) I is the same as j but the k-th bit inverted. The data type of the result is that of j. The data type of k must be INTEGER (any size). K is evaluated modulo 32. Example: REALINV=IBCHG(REAL,31). i = ISHFT(j,k) I is j shifted by abs(k) places to the left (if k positive) or right (if k negative). The bits shifted out are lost and zeros are filled in. The data type of the result is that of j. The data type of k must be INTEGER (any size). K is evaluated modulo 32. Example: IEXPO=ISHFT(IREAL,-23). if (BTEST(j,k)) ... BTEST is a logical function which is TRUE if the k-th bit of j is set. The data type of k must be integer (any size). K is evaluated modulo 32. Example: IF(BTEST(STATUS,12)) CALL ALARM. 2.5.6.1.2. Other functions that produce in-line code i = MOD(j,k) I is j modulo k. If k is a power of two, a AND is carried out, otherwise a divide. J and k must both be INTEGER (any size), as is the result. Example: IAND255=MOD(I,256). i = IABS(j) I is the absolute value of j. J must be INTEGER (any size), as is the result. r = ABS (s) R is the absolute value of s. S may be of type INTEGER (any size) or REAL. The data type of the result is REAL. i = IFIX(j) I is j, converted to INTEGER by truncating to the nearest integer closer to zero. J must have INTEGER (any size) or REAL data type. The type of the result is INTEGER. Example: TRUNC=IFIX(REAL). r = FLOAT(s) I is s, converted to REAL. S must have INTEGER (any size) or REAL data type. The type of the result is REAL. Example: ISAME=FLOAT(I). i = ICHAR(c) I is the binary representation of the first ASCII character of the string c. The result is type INTEGER. C must have type CHARACTER. Example: LOWERA=ICHAR('a'). c = CHAR (j) C is the ASCII string representation of the binary value j. J must have type INTEGER (any size). The result is type CHARACTER*1. Example: CHARSET(I:I)=CHAR(I). if (Lcc(c,d)) ... Lcc is a logical value stating the result of lexical comparison of two CHARACTER strings c and d. cc may be one of LE, GE, NE, LT, GT, or EQ. Example: IF(LEQ(INPUT,'EXIT')) STOP. Note: Lcc(c,d) generates the same code as c.cc.d . if (OWNER(sema))... OWNER is a access control primitive which uses the TAS instruction of the processor to test and set a semaphore. It is a logical function which is .TRUE. if the semaphore was free, i.e. the calling program owns it after the call. Sema must be variable (not expression) of any data type; only its first byte is accessed. A natural choice would be INTEGER*1. Releasing the resource then works with sema=0. Example: DO WHILE(.NOT.OWNER(BRANCH)) CALL WAIT ENDDO ... CALL ACCESS ... BRANCH=0 2.5.6.1.3. Standard functions r = func(s) Func is the value of the standard function acting on the argument s. S must have INTEGER (any size) or REAL data type. Func has data type REAL. Func stands for: SQRT, SIN, COS, TAN, ASIN, ACOS, ATAN, EXP, EXP10, ALOG, ALOG10, SINH, COSH, TANH, ATANH. r = RNDM(j) RNDM is a high-quality random generator with uniform distribution in (0,1). J must be declared as INTEGER*4 J(2). The two values should be initialized and are updated in each RNDM call. They can be saved as a starting point for later use. The type of the result is REAL. RNDM provides a period length of 2**64. r = RNORM(j) RNORM generates random numbers with normal distribution (derived from 12 calls to RNDM). Call similar to RNDM. i = LEN(j) LEN calculates the number of characters in the character string expression j. Example: CHARACTER*128 CH ... NONBL=LEN(CH(1:-1)) ! length without ... ! trailing blanks i=INDEX(j1,j2) INDEX calculates the character index in the character string expression where the character string expression j2 starts. Zero is returned if j2 is not found in j1. Example: I=INDEX(FILE,'.') IF(I.NE.0) THEN FILE1=FILE(:I)//'FOR' ELSE FILE2=FILE//'.FOR' ENDIF i = INT(x) I is x, truncated to the nearest integer closer to zero. X must have REAL data type, the type of the result is INTEGER. Same as IFIX above, but not a generic function. i = NINT(x) I is x, rounded to the nearest integer. X must be type REAL. The type of the result is INTEGER. Not a generic function. Example: IROUND=NINT(R). i = INCHR(lu) Single character input without wait. Lu is the logical unit (INTEGER*4). I (INTEGER*4) is zero if no character is present; else it has the integer value of the ASCII character entered. Example: do forever i=inchr(1) if(i.ne.0) then call action(i) else call background ... 2.5.6.1.4. Word size modifiers These serve as a shorthand alternative for mixtures of data type and EQUIVALENCE statements (especially when accessing the same hardware in different modes). Note that, if used on the left side of assignments, they are outside the Fortran standard. i = BYTE(j) Regardless of the data type specification BYTE(j) = i of j, only its first data byte is read or written, resp.; i.e., the result is as if j were declared as INTEGER*1. If BYTE is used for reading (on the right hand side or in arithmetic expressions), its argument may be a constant. i = WORD(j) The same for the first 2 bytes (word) of WORD(j) = i j; like INTEGER*2 declaration. i = LONG(j) The same for the first 4 bytes (longword) LONG(j) = i of j; like INTEGER*4 declaration. CAUTION: note the quite different (peek/poke type) meaning of the above intrinsic names in ABSOFT Fortran. Use EQUIVALENCE with absolute addresses or pointer variables if the effect of the ABSOFT BYTE, WORD, or LONG is desired. 2.5.6.2. Intrinsic subroutines Some in-line procedures have been added which are invoked with CALL statements. They perform simple, often-used operations at maximum speed without any subroutine call overhead. - In particular, one class uses instruction sequences which have 'looping capability' on the 68010, acting on blocks of data. - Other classes act as 'hardware subroutines' by activating peripheral processors or 68020 co-processors. 2.5.6.2.1. Looping mode routines Note: Due to the nature of the DBcc instruction of the 68K processor the block length l is evaluated modulo 2**16; i.e., the values 1 to 65536 are possible. Also note that the word sizes may apply for various data types; e.g. _MOVL may be used for INTEGER*4, LOGICAL*4, and REAL*4 data, _CMPB may be used for INTEGER*1 and LOGICAL*1, etc. call _MOVL(s,d,l) Moves a block of l words (size L or 4 bytes, call _MOVW(s,d,l) W or 2 bytes, B or one byte each) from s call _MOVB(s,d,l) to d, starting from the low addresses. Example: CALL _MOVB(ARRY(1),ARRY(1001),1000). call _SETL(v,d,l) Writes the value v (word size as above) into call _SETW(v,d,l) all l components of the block d. call _SETB(v,d,l) Example: CALL _SETL(3.14,PIARR,LPIA) call _CMPL(s1,s2,l) Compares two blocks of l words each (word size call _CMPW(s1,s2,l) as above), s1 and s2. Comparison starts from call _CMPB(s1,s2,l) the low addresses and terminates when either two words are not equal or all l words are done. The result appears in the condition code register of the processor and can be checked subsequently. Example: CALL _CMPW(A,B,10000) IF(.EQ.) THEN CALL IDENTICAL ELSE IF(.LT.) THEN ! i.e. A < B ... As an alternate notation, one can also use the following aliases: MOV_L, MOV_W, MOV_B _MOVL, _MOVW, _MOVB SET_L, SET_W, SET_B instead of, resp. _SETL, _SETW, _SETB CMP_L, CMP_W, CMP_B _CMPL, _CMPW, _CMPB. 2.5.6.2.2. Hardware subroutines a) FASTBUS. A subset of the standard Fastbus routines is implemented as a 68020 coprocessor. They are enabled by the option FB=HARD (see above). Then statements of the following form result in generating essentially one F-line coprocessor instruction instead of a subroutine call: call FBxx(p1,p2,...) Please refer to Ref.2 for a complete list of FB hardware subroutines. Example: CALL FBBRD(PRIMA,SECA,DEST,LENGTH) b) As the source of the compiler is easy to modify, more sets of special hardware routines can be made available for Fortran use without runtime overhead. 2.5.7. Obtaining the actual number of arguments - - - - - - - - - - - - - - - - - - - - - - - - As inidcated above, the caller passes the number of actual arguments to the called routines if both - the calling routine is compiled with option NUMPAR=ON (this is the default) and - the called routine is not a type CHARACTER function. Then the number of arguments is in register D0.L after entering the called routine. The called routine may transfer the number of arguments to a variable by using CALL NUMPAR(var) or CALL NOARG(var) as the first executable statement. The compiler then generates inline code for that of the kind MOVE.L D0,var . If the call is made without parameters, i.e. CALL NUMPAR() or CALL NOARG() no code is generated. If the call is completely left out, however, RTF generates code to check if the actual number of arguments is the same as the formal number of arguments: CMP.W #formal,D0 BEQ *+4 JSR _BADPAR . This check may be suppressed with the option CHECK=OFF, or with a call to NUMPAR/NOARG without arguments as described above. Calling NUMPAR or NOARG other than in the first executable statement results in a normal subroutine call to a user-supplied routine. 2.5.8. KEEP directive - - - - - - - - - - - The KEEP directive should accompany EQUIVALENCE with registers. Other than registers which are allocated by the compiler itself, the user-declared registers are not automatically saved at entry and restored at exit of the routine using them. If the contents of user declared registers is to be preserved for the outside world (which is normally the case, and is a must for interrupt handler routines), the names of those registers have to appear in the KEEP directive. Otherwise, the routine exits with the value of the register(s) altered - maybe useful only as an odd way of passing results. Also when modifying registers in embedded assembly code parts, these registers should appear in KEEP. The registers accessible to the user by this means are the processor data registers D0-D7, the processor address registers A0-A7, and the floating point coprocessor data registers F0-F7. The processor status register SR, which is accessible with EQUIVALENCE, cannot appear in KEEP, however. Example: Function ISum(IA) Integer*2 I,IA(100); Integer ISum,IS Equivalence (I,d7),(IS,d6) Keep D7,d6 IS=0 Do I=1,100 IS=IS+IA(+) EndDo ISum=IS End If one omits the KEEP in this example, the function should be better called ITrouble - because it will affect the caller's registers D6 and D7 in an uncontrolled manner! 2.5.9. INTERRUPT and EVENTFLAG directives - - - - - - - - - - - - - - - - - - - - - As mentioned previously, RTF subroutines can be used as interrupt handlers without any operating system overhead. For this purpose, the entry address of the subroutine is written into the appropriate longword of the CPU's interrupt vector. On occurance of the interrupt signal (an external hardware signal or software trap), the CPU's firmware saves the current CPU status word and program counter (or more for some special interrupts) on the stack and jumps directly to the subroutine's entry point. The saving of the rest of the CPU's current context (registers) is done by the subroutine internally. Thus, it takes typically only 20 usec on a 10 MHz 68000 from the interrupt to the first useful instruction of the subroutine (i.e., after the register saving). The exit from an interrupt routine is special as compared to a normal subroutine: not only the old program counter, but also the old CPU status word are restored from the stack. Thus the return from interrrupt has to use the assembler instruction RTE instead of RTS. Both setting up the interrupt vector location and preparing the proper return instruction is done by the INTERRUPT directive. It has to be the last of the declaration statements, i.e. it has to be placed between DATA and statement functions (if any). After the keyword INTERRUPT, either a integer constant in the range 2 to 255 or a '*' must follow. In the first case, the interrupt vector location with the given constant as index is set to the routine's entry address during program initialization, much as with a DATA statement. The interrupt vector location is not touched during initialization in the second case, but this is then left for runtime (see subroutine ACTIVATE below). In both cases, the proper return from interrupt is generated. Examples: Subroutine Iresp Byte reg Equivalence (reg,'ff3000'x) Interrupt 200 ! set vector 200 to addr(Iresp) reg=0 ! 1st executable instruction ... End ! exit with RTE Program Main External Iresp1 ... Call Activate(200,Iresp1) ! set vector 200 to addr(Iresp1) ... End Subroutine Iresp1 Byte reg Equivalence (reg,'ff3000'x) Interrupt * reg=0 ! 1st executable instruction ... End ! exit with RTE In the first example, the interrupt vector is set automatically during initialization, while this is done explicitely by the main program in the second example. If one uses the keyword EVENTFLAG instead of INTERRUPT, the compiler generates an RTR instruction instead of RTE. A vector number is not allowed then, only the '*'. This may be useful for returning from asynchronous event handlers supported by some operating systems (OS9, VersaDOS). Example: Common /Flags/ IDone Program Main External Iresp2 ... Call Enable(Iresp2...) ! tell OS to enable handler ... End Subroutine Iresp2 Eventflag * IDone=1 ! 1st executable instruction ... End ! exit with RTR 2.5.10. CPU time required for subroutine calls - - - - - - - - - - - - - - - - - - - - - - - - The caller has to push the addresses of the N actual arguments onto the stack (PEA intructions), move N to D0 (MOVEQ), perform the jump to subroutine (JSR and JMP), and to clean up the stack after return (ADDQ). The called routine has to setup the local variable area (LINK), save registers (MOVEM), optionally check the actual number of arguments and stack limit (two times CMP, Bcc), then after execution of the user's code restore the registers (MOVEM), remove the local variable area (UNLK), and return (RTS). On a 68000 processor, a subroutine call requires thus the following number of clock cycles: 96 + 16*(n+m) + 28*c where n is the number of actual arguments, m is the number of registers to be saved inside of the routine, and c is 1 if the option CHECK=ON is chosen, 0 else. For n=2, m=4, c=0 this sums up to 192 clock cycles or roughly 20 usec for a 10 MHz 68000. 2.5.11. Type conversions for integer arguments - - - - - - - - - - - - - - - - - - - - - - - - There are cases where the conformation of actual and formal subroutine arguments is not evident: for integer constants and integer expressions containing INTEGER*2 parts. For example, in INTEGER*2 I,J ... J=I+1 ... the expression I+1 is treated as INTERGER*2, i.e. as a 16-bit word. The same is true in Options MULDIV=SHORT ... INTEGER*4 I INTEGER*2 J ... J=I/5 ... for the expression I/5 as a consequence of the 68000's DIVS instruction, while in the case I/4 (use of shift) the type is INTEGER*4. The features mentioned above are justified by optimizing criteria but are not at all obvious to the user, and hence could lead to arbitrary results if such expressions are used as actual subroutine arguments. Therefore the following scheme is used to arrive at definite data types for integer actual arguments. (1) Simple variables and array elements are passed with the type imposed by type declaration or implication as usual. (2) Integer constants and integer expressions other than (1) are passed as INTEGER*4 when the option INTSIZE=4 (default) is chosen. (3) Integer constants and integer expressions other than (1) are passed as INTEGER*2 when the option INTSIZE=2 is chosen. A similar difficulty occurs in principle also for LOGICAL variables. However, due to the representation of logical values with the topmost byte only, confusion of LOGICAL*1, *2, and *4 actual arguments has no effect. 2.6. Other language elements ---------------------------- 2.6.1. Arithmetic, logical, and character string expressions - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2.6.2. Assignment statements - - - - - - - - - - - - - - - 2.6.3. Label and variable ASSIGN statements - - - - - - - - - - - - - - - - - - - - - - Label ASSIGN statements are used in Fortran in connection with assigned GOTO statements. The syntax is: ASSIGN