Appendix 1
OS-9 system calls from RTF

A subset of the OS-9 operating system calls which are described in detail in the "OS-9/68000 Operating System Technical Manual", called TM for further reference, is available in RTF. This subset is part of the general RTF library, the sharable library module RTFLIB, such that no further commands to the linker LNK are needed to use these system calls.

The subset contains the most important F$... type system calls, some I$... type I/O system calls, most of the EV$... event system calls, and some other general-purpose system services for simplified SHELL forking, etc. Also, other entry points like the random generator etc. are described. More calls could be added on demand in future releases of RTF.

The full system calls library, and more than that, are available in C language, as described in the "OS-9/68000 C Compiler User's Manual". They can, in a manner unusual to Fortran programmers, also be called from RTF - but why not learn C right away ? (This is a commercial of the OPAL Microprocessor team.)

Note that the letter case used in the entry point names has no effect in RTF, unlike C or assembler. RTF always converts the names to uppercase.

Another general point to mention is that whenever I/O channels are used as arguments, they are identified as Fortan logical unit numbers instead of OS-9 path numbers, unlike C or assembler. A function NUMPATH is provided which converts a Fortran logical unit number into an OS-9 path number if required for special purposes. Paths handed over to an RTF program have Fortran logical units opened automatically, i.e. unit 1 for standard input and output (paths 0 and 1), unit 2 for error output (path 2), and more from unit 3 upwards (unit equals path number) if provided.

In the description of the arguments, < denotes an input parameter and > an output result. <> denotes an updated argument.

1) F_ system calls

The following procedures are implemented as Integer Functions. They return the value 0 for success, the appropriate OS-9 error code else. Do not forget to declare the functions this way in the calling program in order to get proper error return codes. (Of course it is good practice to declare everything anyway, and to enforce this with IMPLICIT NONE - another commercial.) - An exception is F_Exit, which returns never to the caller and should be called as a subroutine.
subroutine f_exit(code)

integer code< exit code

Function (TM 16.13): Terminates the process and returns to the calling process which is normally the SHELL command interpreter of OS-9. An exit code 0 indicates normal termination, while all other values report abnormal termination to the calling process. OS-9 error codes should be used. OS-9 automatically closes files, returns system memory, etc. when a process terminates. The link counts of linked modules or events are however not decremented.

integer function f_link(name,expnt,modpnt)

character*(*) name < module name
integer expnt > address of module execution entry point
modpnt> module header address

Function (TM 16.23): Tries to find a module identified by the module name in memory. If successful, returns the address of the module's execution entry point (i.e., the address where useful data start in a data module, or the primary entry point of an executable program) as well as the start address of the entire module (header address). Increases the link count of the module to prevent it from being removed from memory.

integer function f_unlnk(name)

character*(*) name< module name

Function (TM 16.48): The inverse of f_link. Decreases the link count of the module and removes it from memory when the count reaches 0, unless it is a 'sticky' module or a module found during system boot.

integer function f_setpri(id,pri)

integer id,< process id number
(0=calling process)
pri< desired new priority
(0=lowest, 65535=highest)

Function (TM 16.34): Changes the execution priority of the process identified by the process id number. If the calling process is meant, use id=0. The priority of other processes can be changed only by the super user, or if the it has the same group/user id as the calling process.

integer function f_sigmask(level)

integer level< mask level
(0=clear, 1=set/increment, -1=decrement)

Function (TM 16.33.a): Sets or clears the signal mask which can be used to protect critical code from being interrupted by signals to its intercept routine. Signals are accepted only if the mask is clear, else they get queued. Note that intercept routines are executed with the mask set. Note also that the KILL signal (code 0) cannot be masked off, and that the WAKEUP signal (code 1) is never queued, i.e. it gets lost when the process is not sleeping.

integer function f_datmod(name,isiz,datpnt,modpnt,color)

character*(*) name < modules name
integer isiz, < module size (bytes)
datpnt,> address of data inside module
modpnt,> module header address
color < (optional) color code
(0 or omitted means any color)

Function (TM 16.7): Creates a data module identified by the module name, with size in bytes specified by module size. If the module exists already, an error is returned, and f_link should be used instead. If successful, returns the address where data start within the module, as well as the start address of the entire module (header address). Data modules are an easy and efficient means to communicate large amounts of data (e.g. event buffers) among processes. The link count is set to 1 and the data portion of the module is set to 0.

Note: This RTF function forces the data pointer to a longword boundary, for efficiency reasons on the 68020/30. This is different from the OS-9 system entry point F$Datmod.

integer function f_sleep(ticks,rest)

integer ticks,< duration in 1/100 sec
(0=indefinite, 1=give up time slice)
rest > remaining wait time if the process gets woken up early

Function (TM 16.33): Deactivates the calling process for the duration given, or until a signal is received, in order to allow other processes to proceed. A timed sleep (ticks > 1) can be used for timeout purposes. A timeout can be recognized by the remaining ticks being > 0. The sleep call is e.g used internally by I/O drivers to suspend the requestor until I/O completes. All signals wake up the sleeping process, but the wakeup signal is specifically made for this purpose.

integer function f_send(id,code)

integer id, < receiving process' id
(0: all processes of group/user)
code< signal code

Function (TM 16.30): Sends a signal specified by the signal code to a process identified by process id. If the process id is 0, the signal is broadcast to all processes with the same group/user id as the calling process, except the calling process itself. Predefined signal codes are: 0 for kill, 1 for wakeup, 2 for keyboard abort (usually CTRL-E), 3 for keyboard interrupt (usually CTRL-C). Codes 256-65535 are free for user definition. All signals except wakeup will kill the receiving process unless it has an intercept routine. Wakeup is not given to the intercept routine, nor is kill, which will abort the receiving process even if an intercept routine exists. Signals other than kill and wakeup are queued to the receiving process. Wakeup is lost if the process is not sleeping.

integer function f_time(format,time,date,day,tick)

integer format,< format of resulting data
(0=Gregorian, 1=Julian, 2=Gregorian with ticks, 3=Julian with ticks)
time, > current time
date, > current date
day > day of week
integer*2 tick(2)> (1) tick rate, (2) current tick

Function (TM 16.43): Returns the current time and date in either Gregorian or Julian format. The ticks information is returned only for formats 2 and 3. In Julian format (the unusual one), the current time is the seconds since midnight, and the current date is the Julian day number. In Gregorian format, the current time and date should rather be declared: byte time(4),date(4). Then time(2) is hour 0..23, time(3) is minute 0..59, time(4) is second 0..59, date(1)*256+date(2) is year e.g. 1987, date(3) is month 1..12, and date(4) is day 1..31. For day of week, 0 means Sunday and 6 means Saturday. The tick rate is 100/sec in OPAL, such that ticks(2) provides 10 msec units. - Refer to subroutine DAYTIME in chapter IV below which is less complete but easier to use.

integer function f_perr(error_lu,error_code)

integer error_lu, < log.unit of error text file
(0 means none)
error_code< OS-9 or user defined error code

Function (TM 16.26): This is OS-9's error reporting facility. It prints an error message with the given error code to the standard error output. Optionally, if a non-zero logical unit number of an error text file (format see TM) is given, the explanatory text for the given error code is also printed. Files with user-defined error texts can easily be generated with the text editor.

integer function f_fork(mod_type,mem_size,cmnd_size,nb_path,prior,name,cmnd,id)

integer mod_type, < desired module type/revision code
(0 means any - usual case)
mem_size, < additional memory for new process
cmnd_size,< size (bytes) of command given to new process
(0 means no command)
nb_path, < number of I/O paths to be copied to new process (usually 3)
prior < desired execution priority on new process
(0 = same as caller)
character*(*) name, < module/file name of new process
cmnd < command string to be given to new process
integer id > process id number of new process

Function (TM 16.15): This creates a new process as a child of the calling process. If a module identified by the module/file name is in memory, this one is executed. If not, the module/file name is taken as name of a file containing an executable module which is then loaded into memory and executed. If desired, a module of specific type and revision code may be requested. Additional memory (in kbytes) to what is specified in module's header may be passed to the new process. A command string, which is specified by cmds_size and cmnd, may be passed to the new process. The number of I/O channels open to the calling process and being handed over to the new process may be specified; this in usually 3 (standard input, standard output, and standard error output). The execution priority of the new process may be specified; if 0, the caller's priority is taken. The default priority in OPAL is 128. If successful, the process id number of the new process is returned for further reference to that process. Refer to the subroutine SHELL in chapter IV below which is less complete but easier to use.

integer function f_wait(id,code)

integer id, > id number of terminating child
code > exit code of terminating child

Function (TM 16.49): Deactivates the calling process until a child process created by the calling process terminates. The process id number and the exit code are returned to the caller. If the calling process has several child processes, it should make one f_wait call per child. When a child terminates before f_wait is issued, f_wait will then return immediately.

integer function f_id(id,g_u,prior)

integer id, > id number of calling process
g_u, > group/user number of calling process
prior > execution priority of calling process

Function (TM 16.20): Allows to determine the calling process' id number, group/user number, and priority. The id number can be used for further reference in other system calls.

integer function f_gprdsc(id,nb,descr)

integer id, < desired process id number
(0=calling process)
nb, < size (bytes) of buffer
descr > buffer to receive the information

Function (TM 16.19): Issues a copy of the descriptor of the process identified with the id number. Id=0 means the calling process. All details about the process can be found in this descriptor information. See TM and /dd/defs/procs.a for the descriptor format.

integer function f_icpt(ent)

integer ent < entry point address of intercept subroutine

Function (TM 16.21): Installs an intercept routine for the calling process which can catch signals which would otherwise terminate the process. The intercept routine executes asynchronously to the main process, and has access to the same global data. It receives the signal code in the lower half of register d1. The routine may be written in RTF (see manual) or assembler. Be sure to declare the intercept routine's name EXTERNAL in the routine which calls f_icpt. This is a usual Fortran requirement. Note that in OS-9 there is at most one intercept routine per process, which has to handle all signals. New signals are masked off during execution of the intercept routine.

integer function f_load(file,color)

character*(*) file, < name of file containing the module to be loaded
integer color < (optional) color code of target memory
(0 or omitted means any color)

Function (TM 16.24): Tries to load a memory from a file according to the path name. The search starts in the current execution directory unless a full pathname is given. The file must have the exec attribute.

integer function f_permit(addr,size,permit)

integer addr < starting address of area to grant access to
(not a pointer, but a constant or variable holding the address)
integer size < size of area (bytes)
integer permit < permission code
0: read/execute only
1: read/execute/write

Function (SSM manual): works only for systems which include the SSM (system security module). Grants access by the calling process to the address range described by the parameters. A process can make several f_permit calls. Most implemenations of the SSM require that the calling process runs in group 0 (super user group).

integer function f_protect(addr,size)

integer addr, < starting address of area to disallow access to
(same note as above)
integer size < size of area (bytes)

Function (SSM manual): Same remarks as above. Disallows access by the calling process to the address range described by the parameters. A process can make several f_protect calls. Most implemenations of the SSM require that the calling process runs in group 0 (super user group).

2) I_ system calls

The following procedures are implemented as Integer Functions. They return the value 0 for success, the appropriate OS-9 error code else. Do not forget to declare the functions this way in the calling program in order to get proper error return codes.
integer function i_write(lu,nbytes,buff,nwritten)

integer lu, < logical unit number to write to
nbytes < number of bytes to be written
byte buff(nbytes) < buffer containing the data
integer nwritten > number of bytes actually written

Function (TM 17.29): Performs a binary write of the specified data to lu. Returns the number of bytes actually written, which should normally be equal to the number requested.

integer function i_writln(lu,nbytes,buff,nwritten)

integer lu, < logical unit number to write to
nbytes < number of bytes to be written
byte buff(nbytes) < buffer containing the data
integer nwritten > number of bytes actually written

Function (TM 17.30): Performs an ASCII write of the specified data to lu. Bytes are written until a carrige-return is met, or nbytes are done. Returns the number of bytes actually written, excluding the carriage-return.

integer function i_read(lu,nbmax,buff,nread)

integer lu, < logical unit number to read from
nbmax < maximum number of bytes to be read
byte buff(nbmax) > buffer to hold the data
integer nread > number of bytes actually read

Function (TM 17.16): Performs a binary read of the specified data from lu. At maximum nbmax bytes are read, which is the size of the buffer. The number of bytes actually read is returned in nread.

integer function i_readln(lu,nbmax,buff,nread)

integer lu, < logical unit number to read from
nbmax < maximum number of bytes to be read
byte buff(nbmax) > buffer to hold the data
integer nread > number of bytes actually read

Function (TM 17.17): Performs an ASCII read of the specified data from lu. Bytes are read until a carriage-return is met,or the maximum of nbmax bytes are read, which is the size of the buffer. The number of bytes actually read, excluding the carriage-return, is returned in nread.

integer function i_ssig(lu,sig)

integer lu, < logical unit number to be monitored
sig < signal code to be sent

Function (TM 17.24): This is OS-9's means of asynchronous input from a terminal or pipe. It requests the system to send a signal to the calling process as soon as data become available on the unit, e.g. a character has been entered on the keyboard. Once the signal has been delivered, the i_ssig call has to be made again to prepare for receiving a new signal. When characters are already waiting in the buffer or pipe when the call is made, i_ssig returns their number in negative, and the request to the system is not made. As usual, a zero result means success and positive means an OS-9 error code. When using the wakeup signal (1), keep in mind that this signal will be lost when this signal arrives before the process calls f_sleep. It is better to use a different signal code, which requires then the existence of an intercept routine.

integer function i_gpos(lu,pos)

integer lu, < logical unit number
pos > current file position
(bytes; 0 means first byte in the file)

Function (TM 17.11): Returns the current position in the file identified by the logical unit number.

integer function i_seek(lu,pos)

integer lu, < logical unit number
pos < new file position
(bytes; 0 means first byte in the file, -1 means current end of file)

Function (TM 17.11): Sets a new position in the file identified by the logical unit number. Pos=-1 positions to the current end of file. Pos=0 rewinds the file. It is allowed to position beyond the current end of file. Subsequent reads eill then return an end of file error, while writes will expand the file. Note: i_seek may be used for random-access files with variable record length. It also has to be used for fixed record length because the syntax for random-access is omitted from RTF. If records of size lng (bytes) are used, i_seek(lu,lng*(nrec-1)) positions to record nrec.

integer function i_gsize(lu,size)

integer lu, < logical unit number
size > current size of file (bytes)

Function (TM 17.11): Returns the current size of the file identified by the logical unit number.

integer function i_ssize(lu,size)

integer lu, < logical unit number
size < new size of file (bytes)

Function (TM 17.11): Sets a new size of the file identified by the logical unit number. Size=0 shrinks the file to empty. i_ssize(lu,0) makes an existing file appear like a freshly created one.

integer function i_ready(lu,nbytes)

integer lu, < logical unit number
nbytes > number of bytes present

Function (TM 17.10): Determines the number of bytes currently present in a pipe or from a terminal. Nbytes=0 says there is no data present. May be used e.g. before writing to a pipe or reading from a terminal, in order to avoid blocking of the read or write call.

integer function i_chd(path)

character*(*) path < path name of new data directory

Function (TM 17.2): Changes the current data directory to the one indicated by path.

integer function i_chx(path)

character*(*) path < path name of new execution directory

Function (TM 17.2): Changes the current execution directory to the one indicated by path.

3) Ev_ system calls

The following procedures are implemented as Integer Functions. They return the value 0 for success, the appropriate OS-9 error code else. Do not forget to declare the functions this way in the calling program in order to get proper error return codes.
integer function ev_creat(value,wait_inc,signal_inc,name,id)

integer value, < initial value of event variable
wait_inc, < auto-increment value after wait
signal_inc < auto-increment value after signal
character*(*) name < name of event
integer id > identifier of event

Function (TM 15.5): Creates a named OS-9 event-flag and associates it with a numerical identifier for further reference. The link count is set to 1. Events are normally used as counter variables. The initial value is set. Each time the event is signalled, the value is incremented by the signal auto increment (see ev_signl). Each time an event wait expires, the value is incremented by the wait auto-increment (see ev_wait). Usual values are 0 for the initial value, 1 for the signal increment, and -1 for the wait decrement.

integer function ev_delet(name)

character*(*) name < name of event

Function (TM 15.5): Removes an event definition from the system. Successful only if the link count is zero.

integer function ev_link(name,id)

character*(*) name < name of event
integer id > identifier of event

Function (TM 15.4): Connects the calling process to the named event flag, for further reference with the numerical identfier. The link count is incremented by 1.

integer function ev_unlnk(id)

integer id < identifier of event

Function (TM 15.4): Disconnects the calling process from the event by decrementing the link count, to allow deletion of the event as soon as the link count reaches zero. Note that events are not automatically unlinked when a process using the event terminates.

integer function ev_read(id,value)

integer id, < identifier of event
value > current value of event variable

Function (TM 15.7): Returns the current value of the event variable without waiting or affecting the value.

integer function ev_wait(id,min,max,value)

integer id, < identifier of event
imin, < lower bound of window
imax, < upper bound of window
value < actual value of event variable

Function (TM 15.6): Waits until the value of the event variable is within the specified window. The actual value of the variable is returned. The wait auto-increment is then applied to the variable. Usual values are 1 for the lower bound and a big number for the upper bound. The wait condition also expires when a signal is received by the process. This case can be distinguished from the desired case by the actual event value being not within the window.

integer function ev_signl(id,ms)

integer id, < identifier of event
ms < broadcast flag (0 to signal only the first process in the event wait queue, else signal all processes in the queue successively)

Function (TM 15.9): Applies the signal auto-increment to the event value. The the queue of processes waiting for this event is inspected. The first in the queue is activated if the new value is within its window. The wait auto-increment is the applied to the value. This procedure commences through the entire queue if the broadcast flag is not zero. See also ev_set and ev_pulse, but ev_signl is the standard way to change event variables.

integer function ev_set(id,ms,new_value)

integer id, < identifier of event
ms, < broadcast flag
new_value < new value of event variable

Function (TM 15.10): Similar to ev_signl, but the new value of the event variable is set to a given value rather than being obtained from the old value plus the signal auto-increment.

integer function ev_pulse(id,ms,temp_value)

integer id, < identifier of event
ms, < broadcast flag
temp_value < new temporary value of event variable

Function (TM 15.10): Similar to ev_signl, but the new value of the event variable is set to a given value rather than being obtained from the old value plus the signal auto-increment. After looking through the wait queue and possibly activating processes, the old event value is restored.

4) Other system-related calls

integer function numpath(lu,io)

integer lu, < logical unit
io < input/output flag (1=input, 2=output)

Function: Returns the OS-9 path number associated with a given Fortran logical unit number. In principle, the input and output parts of a logical unit could be different, but normally this is not the case; io=1 or 2 give the same result. A -1 is returned for logical units which have no paths associated.

integer function memall(nb,color)

integer nb, < number of bytes requested
color < (optional) color code of desired memory
(0 or omitted means any color)

Function: Requests from the system a contiguous piece of memory with the given length (bytes). Returns the start address of the memory if successful, and -1 if not enough memory is available. E.g., the address can then be used to set a pointer variable which then provides access to the piece of memory by Fortran means.

integer function memrel(nb,adr)

integer nb, < size of block to be released (bytes)
adr < start address of block

Function: Releases a block of memory to the system obtained previously with memall. Both the size and the address must conform to those used in memall.

subroutine mapto(pnt,new)

integer pnt, < old address of a pointer-based COMMON
new < new address

Function: Released the memory which has previously been allocated to a pointer-based COMMON block automatically at program start. Then remaps the COMMON block to the new start address. Note that this differs from mere pointer assignment by releasing the old memory block.

subroutine outchr(lu,ch)

integer lu, < logical unit number
ch < character to be output

Function: Writes a single character to the logical unit. The character is taken from the least significant byte of ch.

integer function inchr(lu)

integer lu < logical unit number

Function: Reads a single character from the logical unit without waiting. Zero is returned if no character is ready. Else the ASCII code is returned in the least significant byte of the result.

subroutine delete(file,ier)

character*(*) file < file name
integer ier > error code

Function: Deletes a file. Wildcards are disallowed. Zero is returned in ier if successful, else the OS-9 error code.

subroutine rename(old_name,new_name,ier)

character*(*) old_name, < old name of file
new_name < new name
integer ier > error code

Function: Renames a file. Zero is returned in ier if successful, else the OS-9 error code.

subroutine shell(cmnd,ier)

character*(*) cmnd < command string to be executed
integer ier > error code

Function: Provides an easy to use means of executing shell commands. The command string may contain anything which is usually typed in interactively. It can be used to fork a process through the shell, exploiting the shell's command interpretation. A wait for completion of command execution is done implicitely. Zero is returned in ier if successful, else the OS-9 error code.

subroutine datime(id,it)

integer id, > current Gregorian date
it > current Gregorian time

Function: Returns the current date and time in decimal, such that they can be printed in integer format. E.g., for 15 January 1988, 12 hours 5 minutes 20 seconds, id=880115 and it=120520.

Appendix 2
Other RTF library calls

What follows is a description of entry points which are provided in addition to the standard Fortran entry points, which are:
  1. implemented as library functions:
    index, len, amod, isign, sign, int, nint, max, max0, amax0, max1, amax1, min, min0, amin0, min1, amin1, erf;

  2. implemented as fast, in-line functions:
    mod, not, inot, abs, iabs, and, iand, or, ior, eor, ieor, xor, ixor, ibset, ibclr, ibchg, btest, owner, ishft, ishift, lshft, rshft, ifix, int, float, ichar, char, lle, lge, lne, llt, lgt, leq, swap_b, swap_w, f_to_vax, vax_to_f, bitfld, ibits, jbyt, jbit;

  3. implemented by hardware coprocessors or their emulation:
    sqrt, sin, cos, tan, exp, exp10, alog, alog10, asin, acos, atan, sinh, cosh, tanh, atanh, and the Fastbus calls - only for Aleph Event Builder.


character*(*) function upshft(strng)

character*(*) strng < character string

Function: returns the argument string, shifted to uppercase letters.

character*(*) function loshft(strng)

character*(*) strng < character string

Function: returns the argument string, shifted to lowercase letters.

real function rndm(irnd)

integer irnd(2) <> 64-bit workspace

Function: Rndm returns a real number in the interval (0,1). Rndm is a high quality uniform random generator with period length 2**64. It is essential to provide 64 bits as argument, e.g. an integer array of length 2. The complete status of the generator is contained in there and can be initialized, saved, and restored by setting/saving the argument. Calls to rndm with different workspaces which are initialized to different values provide independent generators.

real function rnorm(irnd)

integer irnd(2) <> 64-bit workspace

Function: Rnorm returns a real number of quasi-normal distribution, i.e. mean 0, variance 1, but values only within (-6,6). It is generated from 12 successive calls to rndm. Argument as for rndm.

integer function icamad(b,c,n,a,f,t)

integer b, < CAMAC branch number (0...9)
c, < crate number (1...7)
n, < station number (0...31)
a, < subaddress (0...15)
f, < function code (0...31)
t < word size (0=24 bit, 1=16 bit)

Function: returns the VME address under which a CAMAC module can be accessed in the given way (f,t) through a CES or DataSud VME to CAMAC branch controller. When being used together with RTF pointer variables, this provides a high-speed but non-standard way to do CAMAC cycles. See examples in the RTF manual. Note that as seen from inside of some CPU modules, e.g. the CES FIC 8230, a given VME address appears with an additional address offset which has thus to be added to the result of icamad (F0000000 for the FIC).

subroutine tback

Function: prints traceback information from the current subroutine level up to the main program to the standard error output. Returns to caller afterwards. Also called internally bt the RTF runtime system.

subroutine tbk(lu,ipc,iframe)

integer lu, < logical unit for output
ipc < program address from where to start traceback
iframe < stackframe pointer from where to start traceback

Function: prints traceback information from the subroutine level defined by the program counter ipc and the frame pointer iframe up to the main program to the output unit lu. Returns to caller afterwards. Also called internally bt the RTF runtime system. - Same effect as tback if lu=2 and ipc and iframe are the current values of the registers PC and A5.

subroutine activate(vect,ent,oldent)

integer vect, < interrupt vector number (2...255)
ent, < entry point address of new interrupt handler
oldent > entry point address of previous handler

Function: 'Steals' interrupt handling from OS-9 and routes an interrupt directly to an RTF of assembler routine. Beware! Be sure to know what you are doing, and that your handler is fully tested and does not disappear from memory once it is installed. No use of OS-9 services should be made inside of the handler. Your handler must be declared EXTERNAL in the routine calling activate. Re-install the old handler by calling activate again with call-by-value for oldent, because the value of oldent is the pointer to the old handler.

subroutine uzero(arr,start,end)

integer arr(*), < array
start, < start index (relative to 1st element of arr)
end < end index (relative to 1st element of arr)

Function: clears (part of) an array to zero. If arr is declared e.g. integer arr(100), and start=1, end=100, the entire array is cleared. In general, if arr is declared arr(i1:i2), then the end-start+1 elements arr(i1+start-1) to arr(i1+end) are cleared.

subroutine mvbits(source,os,length,dest,od)

integer source, < source bit string
os, < start bit number in source (LSB has number 0)
length, < number of bits transferred
dest, > destination bit string
od < start bit number in destination

Function: transfers a bit string of length consecutive bits from source to destination. Bit numbering starts with 0 for the least significant bit. Bits os to os+length-1 of the source are put into bits od to od+length-1 of the destination. Other bits of the destination remain unchanged.

Appendix 3
Error handling utilities for OS-9

The package described here is part of the standard RTF library for OS-9. OS-9 provides two ways to deal with abnormal conditions:
  1. Signals sent to a process (by the system, the operator, other processes...) may be intercepted, by installing an intercept routine with the F$Icpt system call. There is at most one intercept routine per process. The condition is specified by the signal code, and handled under user control.
  2. Failure interrupts caused by the process itself (e.g. bus errors) may be trapped, by installing one or more trap routines with the F$STrap system call. The trap routine(s) handle the condition(s) under user control.
These two cases must not be confused. Example: Although the exit code of a process after having caused a bus error is 102, this is unlike receiving a signal with code 102 which would also result in exit code 102. The presence of an intercept routine would allow control over the latter case, but not over the bus error case. Bus errors and other failure interrupts cannot be handled directly with an intercept routine. Also, more information comes along with a bus error than with a signal, e.g. program counter where the error happened and address used. This information can be used for refined error handling.

The utilities package described here is about failure interrupts. It provides trap routines for these interrupts together with an RTF interface. The interface lets the user select what should happen after an interrupt: traceback printout or not; stop, generate signal, call a user subroutine, or jump to a program label. This is accomplished by setting up information in a global area. This area contains also error details from the interrupt (e.g. bus error address), available to the user.

The following interrupts are made available to user control: bus error, zero divide, and the FPU (floating point coprocessor MC68881/2) errors: zero divide, operand error (e.g. SQRT of a negative argument), overflow, not a number and unordered condition (e.g. from uninitialized variables).

Other interrupts (address error, illegal instruction, F line trap) are used by RTF for various emulations for the 68000. Wrong emulations and the remaining user-trapable interrupts (CHK, TRAPV, privilege violation, A line trap, floating point underflow and inexact result) in RTF typically result from "static" programming errors which are not worth a recovery. The package provides improved handling for all these errors by generating a traceback.

Before describing these entry points, recall how to write an intercept routine in RTF.

Example:

      Subroutine Example_Icpt(idummy,#isignal)
      Eventflag *                      ! mark as intercept routine
      Integer*2 isignal                ! 2nd argument carries the
                                       ! signal code, by value, 16-bit
      ...
      if(isignal.eq.2) then            ! react to CTRL-E
        call cleanup
        stop
      endif
      ...
      end
Intercept routines may be entered indirectly after a failure interrupt by using the "generate signal" mode of the package described now.

A 3.1 Bus error handling

Bus error interrupts are generated when an address is accessed by the processor which does not respond or is unaccessible (e.g. protected by a memory management unit). This could happen due to hardware malfunction or due to programming errors. It could even happen on purpose when a program tries to find out if a piece of hardware exists.

A 3.1.1 Jump to program label

subroutine berr_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the bus error handling such that a bus error interrupt acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time berr_jump was called.

This is the call the best suited for automatic recovery from bus errors when simple repetition of the access is not good enough. Extra information can be obtained with a call to err_detail (see paragraph 7 below) which returns the failure address (the address the access to which failed) and, less important, the approximate program counter of the instruction which made the bus error.

Example:

      program doberr
      parameter (lower='ffa00000'x,upper='ffa01fff'x)
      call berr_jump(*99,0)
      call preparations
    1 call setup_hardware
      do forever
        call use_hardware
     enddo
  99 call err_detail(ipc,iaddr)
     if(iaddr.ge.lower .and. iaddr.le.upper) goto 1
     type '(a,z9)','Fatal bus error - address:',iaddr
     end
Here, bus errors in a given address range result in reinitialisation of the associated hardware. This works on 68000 systems as well as on 68020/30 systems.

A 3.1.2 Call user subroutine

subroutine berr_call(entry,lu)

integer entry, < address of Fortran subroutine (declared EXTERNAL in calling program)
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the bus error handling such that a bus error interrupt results in a call to entry, with no arguments. After execution of the subroutine, control returns to this program counter, which may be the address of the instruction trying the access, or of the instruction after that one. It may even point to the middle of an instruction on 68000 systems. Return from the user subroutine after error is of limited use on 68000 systems. On 68020/30 systems, the faulted bus cycle will be rerun automatically by the processor after return from the user routine.

This is the call the best suited for automatic repetition of the failing access. Extra information can be obtained with a call to err_detail (see paragraph 7 below) which returns the failure address (the address the access to which failed) and, less important, the approximate program counter of the instruction which made the bus error.

Example:

      program doberr1
      common /berr_c/ icount
      external brout
      call berr_call(brout,0)
      call preparations
      call setup_hardware
      do forever
        icount=0
        call use_hardware
      enddo
      end


      subroutine brout
      common /berr_c/ icount
      parameter (lower='ffa00000'x,upper='ffa01fff'x)
      call err_detail(ipc,iaddr)
      if(iaddr.ge.lower .and. iaddr.le.upper) then
        icount=icount+1
        if(icount.gt.3)
          type *,'More than three failures in a row - abort'
          call f_exit(102)
        endif
      endif
      type '(a,z9)','Fatal bus error - address:',iaddr
      call f_exit(102)
      end
Here bus errors in a certain address range result in automatic retry as long as they don't happen all the time. This does not work on 68000 systems.

A 3.1.3 Generate signal

subroutine berr_signal(isignal,lu)

integer isignal, < code of signal to be generated after bus error
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the bus error handling such that a bus error interrupt results in generating a signal. This either aborts the process or enters the optional intercept routine. After execution of the intercept routine, control returns to the program counter where the error happened, which may be the address of the instruction trying the access, or of the instruction after that one. It may even point to the middle of an instruction on 68000 systems. Return from the intercept routine after a bus error is of limited use on 68000 systems. On 68020/30 systems, the faulted bus cycle will be rerun automatically by the processor after return from the intercept routine.

A 3.1.4 Stop

subroutine berr_stop(lu)

integer lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the bus error handling such that a bus error stops the process after the optional traceback output. This mode with output to the standard error output path is the default used by the RTF library, by calling berr_stop(2) internally.

A 3.2 Integer zero divide handling

This interrupt can only occur if the divisor of an integer division is zero.

A 3.2.1 Jump to program label

subroutine zdiv_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time zdiv_jump was called.

A 3.2.2 Call user subroutine

subroutine zdiv_call(entry,lu)

integer entry, < address of Fortran subroutine (declared EXTERNAL in calling program)
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt results in a call to entry, with no arguments. After execution of the subroutine, control returns to the instruction after the failing one, with unpredictable numerical result unless the subroutine takes corrective action (hard to do).

A 3.2.3 Generate signal

subroutine zdiv_signal(isignal,lu)

integer isignal, < code of signal to be generated after bus error
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt results in generating a signal. This either aborts the process or enters the optional intercept routine. After execution of the intercept routine, control returns to the instruction after the failing one, with unpredictable numerical result unless the intercept routine takes corrective action (hard to do).

A 3.2.4 Stop

subroutine zdiv_stop(lu)

integer lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt stops the process after the optional traceback output. This mode with output to the standard error output path is the default used by the RTF library, by calling zdiv_stop(2) internally.

A 3.3 Floating point zero divide handling

Note that the case zero/zero results in an operand error rater than a zero divide interrupt. On the other hand, logarithms of zero as well as arc tangent of ±1 result in zero divide interrupts.

A 3.3.1 Jump to program label

subroutine fzdiv_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time fzdiv_jump was called.

A 3.3.2 Call user subroutine

subroutine fzdiv_call(entry,lu)

integer entry, < address of Fortran subroutine (declared EXTERNAL in calling program)
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt results in a call to entry, with no arguments. After execution of the subroutine, control returns to the instruction after the failing one, with unpredictable numerical result unless the subroutine takes corrective action (hard to do).

A 3.3.3 Generate signal

subroutine fzdiv_signal(isignal,lu)

integer isignal, < code of signal to be generated after bus error
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt results in generating a signal. This either aborts the process or enters the optional intercept routine. After execution of the intercept routine, control returns to the instruction after the failing one, with unpredictable numerical result unless the intercept routine takes corrective action (hard to do).

A 3.3.4 Stop

subroutine fzdiv_stop(lu)

integer lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the zero divide handling such that a zero divide interrupt stops the process after the optional traceback output. This mode with output to the standard error output path is the default used by the RTF library, by calling fzdiv_stop(2) internally.

A 3.4 Floating point operand error handling

Operand error interrupts are generated in the following cases:
asin (x) or acos(x) with abs(x) >1
divide 0/0
int(x) with abs(x) > 231
alog(x) or alog10(x) with x < 0
sqrt(x) with x < 0
and some other, odd cases. Details are found in the MC68881/2 User's Manual.

A 3.4.1 Jump to program label

subroutine operr_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the operand error handling such that an operand error interrupt acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time operr_jump was called.

A 3.4.2 Call user subroutine

subroutine operr_call(entry,lu)

integer entry, < address of Fortran subroutine (declared EXTERNAL in calling program)
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the operand error handling such that an operand error interrupt results in a call to entry, with no arguments. After execution of the subroutine, control returns to the instruction after the failing one, with unpredictable numerical result unless the subroutine takes corrective action (hard to do).

A 3.4.3 Generate signal

subroutine operr_signal(isignal,lu)

integer isignal, < code of signal to be generated after bus error
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the operand error handling such that an operand error interrupt results in generating a signal. This either aborts the process or enters the optional intercept routine. After execution of the intercept routine, control returns to the instruction after the failing one, with unpredictable numerical result unless the intercept routine takes corrective action (hard to do).

A 3.4.4 Stop

subroutine operr_stop(lu)

integer lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the operand error handling such that an operand error interrupt stops the process after the optional traceback output. This mode with output to the standard error output path is the default used by the RTF library, by calling operr_stop(2) internally.

A 3.5 Floating point overflow

This error occurs if an arithmetic operation yields a result which does not fit into the single precision floating point range (~ ±2*1038).

Note that overflow from integer operations and floating point underflows cause no interrupts. I.e., integer overflows remain undetected and floating point underflows give the result 0.

A 3.5.1 Jump to program label

subroutine fovfl_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the overflow handling such that an overflow interrupt acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time fovfl_jump was called.

A 3.5.2 Call user subroutine

subroutine fovfl_call(entry,lu)

integer entry, < address of Fortran subroutine (declared EXTERNAL in calling program)
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the overflow handling such that an overflow interrupt results in a call to entry, with no arguments. After execution of the subroutine, control returns to the instruction after the failing one, with unpredictable numerical result unless the subroutine takes corrective action (hard to do).

A 3.5.3 Generate signal

subroutine fovfl_signal(isignal,lu)

integer isignal, < code of signal to be generated after bus error
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the overflow handling such that an overflow interrupt results in generating a signal. This either aborts the process or enters the optional intercept routine. After execution of the intercept routine, control returns to the instruction after the failing one, with unpredictable numerical result unless the intercept routine takes corrective action (hard to do).

A 3.5.4 Stop

subroutine fovfl_stop(lu)

integer lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the overflow handling such that an overflow interrupt stops the process after the optional traceback output. This mode with output to the standard error output path is the default used by the RTF library, by calling fovfl_stop(2) internally.

A 3.6 Illegal floating point number handling

The IEEE floating point specification defines several data formats which are illegal floating point numbers. They could result from confusing data types (integer instead of real) or from presetting otherwise uninitialized data with illegal values on purpose.

Not all illegal numbers cause an interrupt during all floating point operations. Numbers with an all-1's exponent and a non-zero mantissa are illegal numbers - that is, typically numbers with bit patterns like small negative integers. The two cases "Signalling Not-A-Number" and "Branch/Set on Unordered Condition" distinguished by the hardware are given to the same handler in this package. Details are found in the MC68881/2 User's Manual.

A 3.6.1 Jump to program label

subroutine fnan_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes illegal number handling such that NAN/BSUN interrupts acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time fnan_jump was called.

A 3.6.2 Call user subroutine

subroutine fnan_call(entry,lu)

integer entry, < address of Fortran subroutine (declared EXTERNAL in calling program)
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the illegal number handling such that NAN/BSUN interrupts results in a call to entry, with no arguments. After execution of the subroutine, control returns to the instruction after the failing one, with unpredictable numerical result unless the subroutine takes corrective action (hard to do).

A 3.6.3 Generate signal

subroutine fnan_signal(isignal,lu)

integer isignal, < code of signal to be generated after bus error
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the illegal number handling such that NAN/BSUN interrupts results in generating a signal. This either aborts the process or enters the optional intercept routine. After execution of the intercept routine, control returns to the instruction after the failing one, with unpredictable numerical result unless the intercept routine takes corrective action (hard to do).

A 3.6.4 Stop

subroutine fnan_stop(lu)

integer lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes the illegal number handling such that NAN/BSUN interrupts stop the process after the optional traceback output. This mode with output to the standard error output path is the default used by the RTF library, by calling fnan_stop(2) internally.

A 3.7 Software-controlled jump to label in other program unit

It may be useful to react to certain, not interrupt-related conditions by a jump e.g. back into the main program out of nested subroutines. The following entry points implement that.

A 3.7.1 Prepare jump to program label

subroutine set_jump(label,lu)

integer label, < address of Fortran label inside routine calling this one (e.g.*999); calling routine is usually the main program
lu < Fortran logical unit number of optional traceback output (usually 2 for standard error output); 0 means no output

Function: initializes do_jump handling such that do_jump acts much like a GOTO label, possibly directly out of the depth of nested subroutine calls. Stack and frame pointers etc. are reset to the conditions at the time set_jump was called.

A 3.7.2 Execute jump to program label

subroutine do_jump(code)

integer label, < user-defined error code for later use

Function: transfers control to the label specified in set_jump, with the proper stack and frame pointer setting for the target program unit. The program counter of the instruction calling do_jump and the code are stored for optional traceback or retrieval with err_detail.

Example: jump out of an intercept routine into the main program.

      program jump
      external icpt
      call f_icpt(icpt)
      call set_jump(*99,2)
      ...
      ...
   99 call err_detail(ipc,icode)
      type '(a,z9)','Stopped for reason:',icode
      end


      subroutine icpt(dummy,#sig)
      integer*2 sig
      i=sig
      call do_jump(i)
      end

A 3.8 Obtaining error details

subroutine err_detail(ierr_pc,ierr_addr)

integer ierr_pc, > Program counter of failing instruction or 'closeby'
ierr_addr > Address failing to reply (applies only to bus errors)

Function: copies the program counter and the failure address (only for bus errors) of the last interrupt to user's variables. 'Closeby' means that the program counter points to the failing instruction, the next instruction , or even into the middle of the next instruction on 68000 systems. err_pc is precise enough to locate the problem in the program. For bus errors, err_pc is not good enough to allow smooth continuation from the point of error on 68000 systems, while 68020/30 processors have enough extra internal information available for this. err_addr allows to determine safely which hardware failed in the case of a bus error. - For other than bus errors, corrective action by the user is required to make continuation useful.

Appendix 4
Using RTF under OS-9

A 4.1 Editing

We have two full-screen editors for OS-9: Micro-EMACS (or uMACS) from Microware is a UNIX-style editor, and EDT is a VAX/VMS style editor. Here is a brief description of how to use EDT:

EDT <filename> for VT100 compatible terminals
EDT <filename> -vt52 for VT52 compatible terminals.

The layout of the numeric keypad is like on the VAX. Use PF2 to get a help display. When leaving EDT with EXIT or EX, it renames the old file to .BAK and stores the updated information under the old name. When leaving with QUIT, the updated information is kept in a .TMP file. When leaving with ABRT, updates will be lost.

A 4.2 Compiling

RTF is a one-pass compiler which produces assembly code out of the Fortran source. Further processing is left to the assembler of OS-9, r68020. The command procedure FOR invokes the compiler and the assembler, such that it generates relocatable code out of the source. The intermediate assembly code is deleted. FOR can compile one or more source files, with names separated by comma or blank, and accepts RTF options in the command line:
      FOR <filename> [[,]<filename>...] [-<option> [,<option>...]]
<filename> may contain wildcard characters. If no filename extension is given, it is assumed to be .f (if it is given, it is stripped off and replaced by .f - such that Fortran sources should always have extension .f). The <options> are as given in the RTF manual. Examples:
      FOR myfile -list=on
      for b.f
      for a b c
      for *.f -list=on,check=off
The current working directory may be different from the directory containing the source. The intermediate assembly code is always written to the current directory, while the generated relocatable code is written back to the directory of the source. Any include files used by the source inherit the source's path unless they are given as a full pathname, i.e. start with a /. Thus one can speed up compilation by using a fast device (e.g. ram disk) as current directory.

If you are interested in the generated assembler code, run the compiler alone, using the RTF command. RTF wants only one source file at a time, and wants the options without a hyphen (sorry). Example:

      rtf myfile.f mix=on
Then myfile.a contains the generated code together with the Fortran source as comments, due to the mix option.

A 4.2.1 Compiling with additional preprocessor step

Instead of the FOR command procedure, one can also use FP which invokes the C preprocessor of OS-9, CPP, prior to RTF. This is particularly useful for inline expansion of macros defined with #define commands, and can result in faster code than statement functions or external subroutines or functions. Of course, conditional compiling etc. are also available; see any C textbook. Example:
#define MVBITS(m,i,len,n,j) BITFINS(BITFLD(m,i,len),n,j,len)
      Program T
      ii=5
      jj=0
      Call MVBITS(ii,2,5,jj,7)
c     Call BITFINS(BITFLD(ii,2,5),jj,7,5)      ! just the same
      End
For compiling, use
      FP myfile 
The call to MVBITS is expanded to the call to BITFINS as indicated. Note that the macro preprocessor is case sensitive, as usual in the C language. As a consequence, the macro definition and macro reference must be written in exactly the same case.

A 4.3 Linking

RTF programs always require some initialization code, contained in file rtfstart.r . The command procedure LNK does that automatically. It invokes the OS-9 linker, l68. LNK accepts one or more relocatable files as input. The file and module names of the generated executable image defaults to the name of the first relocatable file given. The relocatables are assumed to have the extension .r, which must not be given. l68 options are accepted in the command line (useful e.g. -l=..., -g, -a, -z... see the OS-9 ALD manual). Examples:
      LNK test
      lnk test,testsub
      lnk bigone -ga
      lnk -z=t.lnk -l=/m2/hb.l -o=thb
The -g option generates a symbol table in a separate file; is useful when you want to execute under the debugger (see ALD manual). Apart from that, it has no influence on the speed of execution. - In the last example, t.lnk contains a list of relocatable files (with extension .r - sorry again), one per line.

For mixtures of RTF and C, where the main program is in RTF, similar command LNC is provided. Here, the file rtfcstart.r. This contains initialisations for C as well as for RTF. The C library, clib.l, is used. Example for the entire program preparation:

      for mainf
      for subf
      cc -r subc
      lnc mainf subc subf 
This generates an executable module named mainf. Note the option -r (generate relocatable) of cc.

A 4.4 Executing

Code can be executed by just typing the name of the executable module. If the module is in memory, it starts execution immediately. If not, the OS-9 tries to locate a file with the given name in the hierearchy of execution directories stated in the PATH environment variable of the shell. If found, the image is brought into memory, ans execution starts. Normally, the image disappears from memory once it is no longer needed, i.e. when execution is finished. If you want to run the image frequently, or you require a shore delay to execution, the module should be installed in memory using the LOAD command of OS-9. The module will then stay in memory until explicitely released with the UNLINK command.

To execute under the debugger, type DEBUG <module> rather than just <module>. Presently the debugger is a assembly level debugger, not Fortran source level. Global symbols like subroutines names etc. are nevertheless accessible by name. After some experience, you will find the debugger more useful than putting TYPE statements into your source all the time.

Appendix 5
RTF programming techniques under OS-9

This paragraph covers methods of interprocess communication, including shared data and synchronisation.

A 5.1 Sharing data among processes

One fundamental example of sharing data between processes is histogram filling and displaying, where filling and displaying are done with a separate process each. Fortran data structures, such as used by the HBOOK4 and ZEBRA packages, are implemented with COMMON blocks. The standard way to have shared data in OS-9 is with data modules which are named objects. Programs connect to them by a system call using the module name as input and getting the module address back. This address is then used as the pointer to the COMMON - i.e. this scheme only works with pointer based COMMON blocks.

Remember the various ways to have pointer-based COMMON blocks:

    1. INDIRECT NNN 
      COMMON /NNN/ X,Y...
    2. COMMON /@NNN/ X,Y...
    1. RECORD /NNN/ X,Y...
    2. INTEGER @NNN
      COMMON /@NNN/ X,Y...
    1. TEMPLATE NNN
      COMMON /NNN/ X,Y
    2. COMMON /PNNN/ @NNN
      RECORD /NNN/ X,Y...
    3. COMMON /PNNN/ @NNN
      COMMON /@NNN/ X,Y...
In cases 1a and 1b, the pointers are not expilicitely declared. Space is allocated automatically for the COMMON blocks when the program is initialized. The pointer is made a global variable which is set during the initialization to point to the allocated space. The pointer is globally available to all routines, even when compiled separately. The pointer is known via the name of the COMMON block.

In cases 2a and 2b, the pointers are explicitely declared. No space allocation is done; rather the program has to allocate space explicitely (e.g. by linking to a data module with F_LINK). Setting the pointer has to be done explicitely as well. Depending on how global the declaration of the pointer is, it may be valid in the current routine only, in the file currently compiled, or even in separately compiled files.

In cases 3a, 3b and 3c, the same is true as for 2a and 2b, but the pointer is guaranteed to be to be valid even in separately compiled files.

In consequence, method 3a is recommended for sharing data which reside in a data module. 3b or 3c could also be used but 3a seems more obvious.

Example:

      Program Main
      Integer F_Link,F_DatMod
      Template Evnt
      Common /Evnt/ IEvent(100000)
      ...
      If(F_Link('EventMod',@Evnt,Junk).ne.0) Then
        If(F_DatMod('EventMod',100000*4,@Evnt,Junk).ne.0) Stop 'error'
      EndIf
      ...
      (access to IEvent)
      ...
      End


      Subroutine Subr
      Template Evnt
      Common /Evnt/ IEvent(100000)
      ...
      (access to IEvent)
      ...
      End
The main program allocates space by linking to / creating a data module, getting the pointer @Evnt set up properly by the call to F_Link or F_DatMod. The subroutine, whether compiled separately or not, uses the same pointer, because @Evnt is itself in a COMMON block and thus global.

A 5.2 Using big COMMON blocks

Directly addressable global space is limited to 64k in total. That is, the sum of all normal COMMON blocks and all other global variables has to fit into this size! - The solution is not to use normal COMMON blocks throughout, but to use pointer-based ones for the big COMMON blocks. Instead of space for the COMMON itself, just 4 bytes for its pointer are needed in this case. Still, space should be allocated automatically.

Therefore, method 1a is recommended for big COMMON blocks. 1b could also be used but 1a seems more obvious.

Example:

      Program Main
      Indirect Evt
      Common /Evt/ IEvnet(100000)
      ...
      End


      Subroutine Subr
      Indirect Evt
      Common /Evt/ IEvnet(100000)
      ...
      End
Space is allocated automatically when the program starts execution, using the F$SRqMem system call. As with normal COMMONs, the subroutine accesses the same data as the main program, whether compiled separately or not.

A 5.3 Using 'floating' data structures

In cases where a structure defined in a COMMON or RECORD statement has to be mapped dynamically onto a block of memory, without any global effect, methods 2a or 2b are adequate. 2a appears simpler. The following examples are also found in chapter 2.4.12:
  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 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=...
    

A 5.4 Synchronisation

Apart from exchanging data, interprocess communication requires a mechanism for signalling that data are actually ready, picked up, etc. OS-9 offers three facilities for that: signals, events, and the I/O system's pipes. Please refer to the OS-9/68000 Operating System Technical Manual for details.

The basic features of signals and events have been given in chapter A1.1.1 together with the description of the library calls to use them from RTF. In short, signals as well as events are fast. Signals provide easy timeout implementation. The wakeup signal can be lost, such that safe use of signals usually requires intercept routines. Events are safe and have the advantage that they are known to the system by name; they are thus more recommended as signals.

Very different is the use of the I/O system for communication. It is somewhat less efficient (for the silicon) but is much more portable (to UNIX) and straightforward to use. The SHELL's feature of I/O rerouting and the unified I/O system allow to write processes which are totally unaware of the way they are communicating. Moreover, such processes can be tested easily and independently of each other. By means of networks like OS9Net, the I/O system can be extended transparently across CPU boundaries, such that the use of pipes for interprocess communication makes even implementations on a multiprocessor system a relatively simple task. In addition, small amounts of data (a few hundered bytes) can be transported together with the data-available signal. Such data could consist for example of a pointer to a memory area where the bulk of data (e.g. an event buffer) may be found.

The recommended method here is to write processes which read from and write to their standard input and output paths, i.e. read(*...) and write(*...) in Fortran. Usually they would then communicate interactively via the terminal used to start them. After testing the processes this way from the terminal, the I/O can be rerouted to other I/O paths, e.g pipes between them, by starting them with the appropriate SHELL command. The processes' code need not be touched any more. - In short, use the method of UNIX filters.

A 5.5 Using intercept routines

OS-9 supports a means of asynchronous processing within one process by using an intercept routine. There can at maximum be one intercept routine per process. If present, the intercept routine is entered when the process gets a signal that would otherwise abort the process (apart from the 'kill' signal (code 0) which cannot be intercepted). The intercept routine can interpret the signal code passed to it in d1.w if desired.

A simple application would be to clean up things in a controlled way before terminating with STOP or a call to f_exit. Note the use of the keyword 'eventflag *' as the first declaration statement in the intercept routine.

Example:

      Program Main
      Integer F_Icpt
      External Cleanup
      ...
      I=F_Icpt(Cleanup)                ! let OS-9 install the intercept routine
      ...
      call resource_allocate(...)      ! this could e.g. lock a CAMAC module
      ...
      End

      Subroutine Cleanup               ! entered e.g. on CTRL-C, CTRL-E
      Eventflag * 
      ...
      call resource_release(...)       ! clean up things OS-9 doesn't clean up
      ...
      Stop 'forced termination'        ! stop the process orderly (exit code 0)
      End 
Another example deals with the interpretation of the signal code and possible return to normal processing. Note the use of the keyword 'eventflag *' as the first declaration statement in the intercept routine which is mandatory for resumption of normal processing.
      Program GetInput
      Integer F_Icpt,F_SigMask,I_SSig
      External GetSignal
      Common/Signal/ICode
      ...
      Open(10,'/pipe/SecondaryInput',access='read')
      I=F_Icpt(GetSignal)              ! let OS-9 install the intercept routine
      I=F_SigMask(1)                   ! mask signals to ensure no loss of
                                       ! signals from I_SSig
      While(InChr(1).ne.0) EndDo       ! eat up old input
      While(InChr(10).ne.0) EndDo
      I=I_SSig(1,101)                  ! request signal on standard input ready
      I=I_SSig(10,110)                 ! request signal on other input ready
      ...
      Do While(.true.)
        ICode=0
        I=F_Sleep(1000,J)              ! wait for something with timeout
                                       ! this also unmasks signals
        I=F_SigMask(1)                 ! mask signals again
        If(ICode.eq.0) Then
          Type *,'No input for 10 seconds'
        ElseIf(ICode.eq.101) Then
          Read(1,*) ...                ! react to standard input
          ...
          I=I_SSig(1,101)
        ElseIf(ICode.eq.110) Then 
          Read(10,*) ...               ! react to other input
          ...
          I=I_SSig(10,110)
        ElseIf(ICode.eq.2 .or. ICode.eq.3) Then
          ...                          ! react to termination requests
          Stop 'operator cancelled process'
        EndIf
      EndDo
      End


      Subroutine GetSignal(Dummy,#SigCode)
      EventFlag *                      ! don't forget this one
      Integer*2 SigCode                ! signal code is passed by value in d1
      Common/Signal/ICode
      ICode=SigCode                    ! pass signal code to main process
      End                              ! this returns to main via F$RTE

A 5.6 Proper use of I_SSig (signal on data ready)

The system call I_SSig is useful for two purposes: (1) listening to more than one input at a time, (2) listening to input while not blocking output to the same device. Care must be taken, however, that signals are not lost because data arrive before the I_SSig request is issued and the process starts to wait. It is mandatory to use I_SSig together with F_SigMask, as already shown in the example just above. Here is a similar one for 'transparent mode' between two terminal ports, which makes use of both of the features (1) and (2).
      Program TranspMode
      Integer F_Icpt,F_SigMask,I_SSig
      External GetSig
      Common/Signal/LuIn,LuOut,ISig
      Parameter (META=4)               ! use CTRL-^ as meta character
      ...
      Open(10,'/t1')
      Open(11,'/t2')
      I=F_Icpt(GetSig)
      I=F_SigMask(1)                  ! mask signals
      While(InChr(10).ne.0) EndDo     ! eat up old input
      While(InChr(11).ne.0) EndDo
      I=I_SSig(10,1010)               ! request signals from the devices
      I=I_SSig(11,1011)
      ...
      Do While(.true.)
        LuIn=0
        I=F_Sleep(0,J)                ! wait, unmask signals
        I=F_SigMask(1)                ! mask signals again
        If(LuIn.ne.0) Then            ! react
          I=InChr(LuIn)
          If(I.eq.META) stop          ! stop on meta character
          Call OutChr(LuIn,I)         ! else transmit
          I=I_SSig(LuIn,ISig)
        EndIf
      EndDo
      End
 
      Subroutine GetSig(Dummy,#SigCode)
      EventFlag *
      Integer*2 SigCode
      Common/Signal/ICode
      If(SigCode.eq.1010) Then
        LuIn=10
        LuOut=11
      ElseIf(SigCode.eq.1011) Then
        LuIn=11
        LuOut=10
      Endif
      ISig=SigCode
      End

Appendix 6
RTF exit codes for OS-9

In addition to the exit codes documented in the OS-9 manuals, there are some RTF specific exit codes produced during compilation as well as during program execution.

A 6.1 Compilation exit codes

Compilation exit codes have a 46 (decimal; i.e. 2E hex) or 47 (i.e. 2F hex) in the topmost byte. The complete codes are as follows:
046:nnnRTF finished with a total number of nnn errors (only if nnn non-zero).
047:001The RTF command is wrong or incomplete.
047:002RTF does not get sufficient workspace from the system.
047:003RTF cannot open the source file.
047:004RTF has been aborted due to internal errors.
047:005RTF cannot open an include file.
Also, RTF can exit with one of the exit codes from the runtime library, because it uses the library during compilation. - For normal completion, it will exit with code zero.

A 6.2 Runtime library exit codes

RTF-specific runtime exit codes have a 48 (decimal; i.e. 30 hex) in the topmost byte, or they are one of the usual OS-9 exit codes. - The complete RTF-specific codes are as follows:
048:010Invalid CAMAC address given to function ICAMAD.
048:011Invalid interrupt vector number given to subroutine ACTIVATE.
048:012Wrong number of actual arguments given to called routine.
048:013Attempt to store into a character constant.
048:015Stack limit exceeded. Usually no traceback available after this error.
048:023Output record is too long (i.e. more than 512 bytes).
048:024Format out of order or not supported.
048:025Too many (i.e. more than 10) concatenations in actual CHARACTER argument.
048:030This is 68020/30 code - cannot execute on a 68000/10.
048:040Conversion error in formatted input/output. Note that input/output request errors (from OPEN,CLOSE,READ,WRITE) result in the corresponding OS-9 exit codes.
048:048Wildcards are not accepted by subroutine DELETE.
The following codes are only used by the floating-point software emulation, i.e. when a MC68881/2 chip does not exist. Otherwise these faults are trapped through the appropriate interrupts.
048:000Non-implemented floating point instruction.
048:001Floating point overflow.
048:002Floating point divide by zero.
048:003Floating to integer conversion overflow.
048:004EXP function argument too big.
048:005ALOG function argument zero or negative.
048:006SQRT argument negative.
048:007Attempt to use illegal floating point number (not-a-number).

Appendix 7
Release notes: RTF/68K versions 3.n and 5.n for OS-9

A 7.1 General

RTF/68K V3.n includes major changes as compared to versions V2.n . There are three reasons for that:
  1. Release 2.1 of OS-9 has new versions of the assembler and linker which allow more efficient handling of COMMON blocks. Also, the USE directive becomes unnecessary.

  2. Introduction of the CES FIC8230 CPU with 68020+68881 as OPAL's new workhorse. The two consequences are:
    1. RTF's default options are set to generate code optimized for the 68020+68881.
    2. VMEbus access by the FIC has always the topmost address bit set, which requires a change of CHARACTER descriptor formats.

  3. Simplification of maintainance by keeping one code version for all CPU types. Method: downward instead of upward compatibility with 68000 CPUs like Microsys CPU07, by emulation of floating point, 32 bit multiply/divide, and 32-bit branch to subroutine instructions.
As one consequence of these changes, existing programs will run faster on CPU07s if they use COMMON blocks, but a little bit slower with the default options if they use much floating point. Programs exploit the FIC's full speed. The second consequence is, all RTF programs need re-compiling and re-linking going from version 2.n to 3.n . In order to guarantee consistency, old code will not link due to missing entry points, and will not execute due to a missing module.

A 7.2 New format for character string descriptors

RTF allows null-terminated strings (usually character constants) as well as standard Fortran character expressions to be passed to routines which expect a type character argument. To distinguish between the two cases, one of the address bits is used. In the previous versions of RTF, bit 31 was used which is fine for all 68000 and most 68020/30 CPU boards, but not for all (e.g. the FIC). This lack of generality has now been removed from RTF for OS-9, and bit 0 is used instead as descriptor flag. Character constants in RTF are addressed relative to the program counter and are guaranteed to have an even address (bit 0 clear). However when passing non-character arrays or variables (which contain null-terminated strings) to routines which expect a type character argument, trouble will occur if they start at a odd byte address.

RTF programs need recompilation mainly because of this new convention. In order to avoid undetected compatibility problems, some crucial entry point names have been changed on purpose. Also, the name of the runtime module, formerly os9lib, has been changed to rtflib. Old code can neither be run nor re-linked in the new system.

A 7.3 New handling of COMMON blocks

In previous versions, all COMMON variables had to be accessed indirectly through a pointer to the COMMON block. This was transparent to the user. Some runtime overhead was introduced by this procedure. The advantage was that all COMMON blocks could be re-mapped freely to new addresses without explicitely basing them onto pointers, which is possible in RTF.

From OS-9 version 2.2 on, COMMON variables can be accessed directly. Thus the default has been changed such that direct addressing is used normally (space is allocated in the VSECT).

Note that in OS-9 version 2.1, this scheme does not work yet because of a bug in the linker, l68. Version 2.2 or at least the l68 of 2.2 (which runs also under 2.1) is required to run RTF version 3.1 or higher.

The advantages are improved speed and considerably more compact code for programs with much use of COMMON variables. The disadvantage is that if one wants to re-map a COMMON block, it has to be based explicitely on a pointer. Also, big COMMON blocks (more than 65000 bytes long) have to be accessed indirectly even in the new version, such that they also have to be based on pointers explicitely. The compiler will give a warning else. Moreover, all COMMONs together have to fit into the 64k limit. The linker will give messages otherwise.

This is a new potential source of inconvenience. To overcome this, the extra declaration INDIRECT cname[,cname,...] has been introduced. The cname are COMMON block names. This allows to state in a preamble (e.g. by a global INCLUDE statement at the beginning of each source file) which COMMON blocks should be addressed indirectly while all others are handled the fast way, without using the pointer mark '@' explicitely in the COMMON block declarations.

A (bad) alternative is to use the compiler option COMMON=INDIRECT which switches to the previous default of all COMMONs being pointer-based automatically. This is, however, not recommended for efficiency reasons. Also, mixing of code containing the same COMMON, but compiled partly with COMMON=INDIRECT and partly with COMMON=ABSOLUTE will not work.

The pointers to pointer-based COMMONs are now themselves put into a COMMON area automatically. The USE statement (which affected the use of libraries) is now no longer necessary.

A 7.4 Emulation of 68020/30 instructions on 68000/10

The new default for floating point arithmetic is FLOAT=HARD, i.e. it is optimized for a CPU which has the floating point coprocessor. Also, for 32-bit multiply, divide, and modulus arithmetic, the 68020/30 codes muls.l, divs.l, and divsl.l are always generated.

In order that the code optimized for the 68020+68881 can run on the 68000/10 unchanged, the Fortran runtime library now emulates floating point hardware and 32-bit multiply/divide instructions. The old entry points for floating point software are eliminated, such that the option FLOAT=SOFT cannot be used with code containing any floating point operations. Caution: double precision calculations are not currently emulated but require a 68881/68881 coprocessor or a 68040 processor.

In addition, long-distance branch to subroutines (32-bit offset) are emulated. The kernel itself emulates the instruction move to ccr which does not exist on the 68000 but is generated by RTF.

If the compiler option CPU=68020 or 68030 is used, marginally more efficient code is generated for the 68020/30. This, however, can then not run on a 68000 or 68010 because of addressing modes which cannot be emulated efficiently. The code is actively aborted when trying to run it on a 68000/10, with an appropriate error message.

A 7.5 Other additions to the runtime library rtflib

  1. Many OS-9 system calls have been added. All calls are now documented in "Calling OS-9 System Services from RTF, and other RTF library calls".

  2. OPEN accepts now the parameters access='append' and access='dir'.

  3. Support for random-access files is now provided by library routines. Random access is not restricted to fixed length records. Instead, the file position can be set to any byte in the file. The RTF language itself still does not provide random-access files. Example:

    Standard in RTF
    OPEN(LU,...,RECL=LNG...) OPEN(LU,...)
    WRITE(LU,...,REC=N...) CALL I_SEEK(LU,(N-1)*LNG)
    WRITE(LU,...)

A 7.6 New features of the compiler command

The FOR command procedure now accepts wildcards and the filename extension .f . Thus
      for *.f or for a*.f , z?.f
are now allowed. For example, for compiling a large amount of code in the background with the compiler messages routed to a file, one can use a command like e.g.
      for *.f >>>rtf.list & 
FOR always invokes the assembler for the 68020/30, r68020, even when producing code without the compiler option CPU=68020 or 68030. A dummy r68 has been made which calls r68020 (this dummy is used also by the C compiler).

A 7.7 New features/Changes of RTF version 5.3

  1. The INTERRUPT and EVENTFLAG keywords must now be the first declaration instead the last declaration, i.e. they must follow the SUBROUTINE statement directly.
  2. NORD style $INCLUDE statements and CERN style debug information on the stack are no longer supported.
  3. DO loops with variable negative step size are now handled correctly.
  4. Free-format input expects now as much input as is needed to satisfy all I/O list elements, even across record boundaries. I/O list elements are no longer filled with zero after the first input record ends. Short input with zero-filling can still be achieved by ending the input with any special character instead of end-of-record.
  5. DOUBLE PRECISION is now supported.
  6. BREAK and NEXT loop control has been added.
  7. Global predifined variable IOSTATUS introduced. It holds the OS-9 error code after an error exit from READ, WRITE, OPEN, CLOSE.
  8. FP command processor invokes the CPP macro preprocessor, enabling inline macro expansion in RTF programs.
  9. New option NOP=ON for synchronization of the 68040 integer unit and bus interface before MOVEM.
  10. The NS floating point processor is no longer supported.
  11. Formatted I/O is now in full precision for DOUBLE PRECISION data.