     FCINT(I-UBC)                   04/16/80                  Page  1



     NAME
             FCINT - FC internals

     INTRODUCTION
             this document describes the UBC Fortran 77 compiler (FC)
             internals. In particular it first describes  the  format
             of  the  threaded  code  then  some  details  on how the
             compiler generates it.

     THREADED CODE
             the compiler generates what is known as "threaded code",
             where the code consists of addresses of  subroutines  to
             execute  and  operand addresses. A machine register (R4)
             is used point to the next threaded code  instruction  to
             execute. The advantages of threaded code are:

        1    it  allows  one  word  subroutine  calls, thus producing
             compact code.

        2    the compiler generates the same code regardless  of  the
             model  of  CPU  on  which  it  is  to  execute. Only the
             threaded code library  need  be  changed  for  different
             models.

        3    threaded  code  easily implements polish notation, where
             operands are first pushed onto a stack (in this case the
             hardware (R6) stack), and then operated on there.

        4    the transfer to the next threaded code routine  is  very
             fast,  in fact only a single instruction (jmp *(r4)+) is
             required.

        5    most of the other (DEC) Fortran compilers for the PDP 11
             generate threaded code.


             A sample might be in order:

             (1) i = j + 1

             (2) movi_ms,j
             (3) movi_ms,const1
             (4) addi_ss
             (5) movi_sm,i





        1    is the Fortran source that is compiled into the threaded
             code in lines (2) to (5).  "i"  and  "j"  are  integer*2
             variables.

     FCINT(I-UBC)                   04/16/80                  Page  2



        2    pushes the value of "j" onto the stack.

        3    pushes the value of 1 (a constant) onto the stack.

        4    adds  the  two values on the top of the stack and pushes
             the result onto the stack replacing the original values.

        5    pops the stack into the variable "i".

     THREADED CODE CONVENSIONS
             in general the  following  format  is  used  for  naming
             threaded code routines:

             operation [type1] [type2] [_] [loc1] [loc2]



             Where:

       operation
             is  the  type of operation that is to be performed, some
             common ones are: mov, add, sub, mul, div, not, and,  or,
             neg.

       type1 is  optional, and if present gives the type (or mode) of
             the operand(s). If two types are specified then  "type1"
             is  the  type  of the first. The possible types are: "i"
             (integer*2),  "q"   (integer*4),   "f"   (real*4),   "d"
             (real*8),   "l"   (logical*2),   "b"   (logical*1),  "c"
             (complex*8), "h" (complex*16), and "s" (character).

       type2 is optional, and is only  present  when  there  are  two
             different types or modes required. This happens only for
             the "exp" and "cvt" operators.

             is  a  delimeter  used  to  (1)  identify  threaded code
             routines (all threaded code routines have a "" in  their
                                                         _
             name,  but  not as the first character; and (2) to serve
             as a delimeter.

       loc1  is optional, and is the location of the  first  operand,
             possible  locations are: "s" (on stack), "m" (in memory,
             address follows in next word), "p"  (parameter,  address
             of  address follows), "i" (immediate, operand follows in
             one or more words), "a" (address on stack), "r" (operand
             is in appropriate general register).

       loc2  is optional, and is present  only  when  more  than  one
             operand is required.

     OPERATORS USED


       add   adds  the  two values on the top of the stack and places
             the result onto the stack.
     FCINT(I-UBC)                   04/16/80                  Page  3



       ago   implements an assigned goto.

       and   logically AND's the two values on the top of  the  stack
             and replaces them.

       ass   implements the ASSIGN statement.

       bck   starts a BACKSPACE statement.

       cgoto implements a computed GOTO.

       chk   checks subscripts for the --C compiler option.

       cvt   converts from one mode to another.

       do    implements the DO loop termination test.

       end   specifies an END= address.

       enf   starts an ENDFILE statement.

       entry implements the ENTRY statement.

       eq    implements the .eq. logical comparison.

       eqv   implements the logical equivalence (.eqv.) operator.

       err   specifies an ERR= address.

       exp   implements the exponetiation (**) operator.

       fmtrd starts a formatted read operation.

       fmtwt starts a formatted write operation.

       ge    implements the .ge. logical comparison.

       goto  causes a treaded code GOTO.

       gt    implements the .gt. logical comparison.

       le    implements the .lt. logical comparison.

       lt    implements the .lt. logical comparison.

       main  starts off the main program.

       mov   moves operand from "loc1" to "loc2". This is used mostly
             to  push  operands  onto  the  stack  for arithmetic and
             logical operations, and to pop operands  off  the  stack
             into memory after the operations are complete.

       mul   multiplies  the  two  values on the top of the stack and
             places the result onto the stack.
     FCINT(I-UBC)                   04/16/80                  Page  4



       ne    implements the .ne. logical comparison.

       neg   negates the top of the stack.

       not   logically complements the top of the stack.

       objrd starts an object time formatted read.

       objwt starts on object time formatted write.

       or    logically OR's the two values on the top  of  the  stack
             and replaces them.

       pause implements the "pause" statement.

       ranrd starts a random access read.

       ranwt starts a random access write.

       rc    checks  the return code for a call statement with return
             labels.

       rec   specifies a record index for random access I/O.

       rew   starts a REWIND statement.

       serr  indicates a source error was present in the statement.

       sf    implements the statement function statement.

       stop  implements the "stop" statement.

       sub   pops a value off the stack and subtracts it from the new
             top of stack.

       subr  starts off a subroutine.

       teq   implements the .eq. zero logical comparison.

       tge   implements the .ge. zero logical comparison.

       tgt   implements the .gt. zero logical comparison.

       tle   implements the .lt. zero logical comparison.

       tlt   implements the .lt. zero logical comparison.

       tne   implements the .ne. zero logical comparison.

       tr    does an input or output  transfer  on  the  value  whose
             address is on the top of the stack.

       trv   does  on  output transfer on the value on the top of the
             stack.
     FCINT(I-UBC)                   04/16/80                  Page  5



       xor   logically XOR's the two values on the top of  the  stack
             and replaces them.

     RULES FOR THREADED CODE ROUTINES
             in  general  the  threaded  code  follows  the following
             rules:

        1    the threaded code register (R4) must be  preserved  from
             one  threaded  routine  to  another as it specifies what
             operation is to be performed.

        2    any other general register (r0  thru  r3)  may  be  used
             freely.

        3    the floating point registers may be used freely.

        4    the  floating point processor mode may be changed freely
             from single to double as required.

        5    since the compiler expects to know the number of  values
             on  the  stack at any given time care should be taken to
             insure that the stack pointer is manipulated properly.

        6    the SBRK routine maintains the  break  address  for  the
             Fortran  runtime,  so  it  should  be  used  to  get any
             additional space required.

        7    the mode of the floating point processor is  changed  to
             DOUBLE  before  calling  any  subroutine in order that C
             subroutines have the right processor mode. It  might  be
             reasonable to change the runtime code to use DOUBLE mode
             always   and   switch   modes  only  when  doing  single
             precision.

     NAMING CONVENTIONS
             a function or subroutine or entry point  is  given  it's
             Fortran  name,  prefixed by an underscore ("_"). This is
             compatible with the C compiler convention. Common blocks
             names are also prefixed by "_", and may thus be used  to
             interface   with  C  variables,  arrays,  or  structures
             globally declared.

     CALLING SEQUENCE AND PARAMETER MECHANISM
             Fortran routines generate a  calling  sequence  that  is
             mostly  compatible with the DEC PC calling sequence, and
             the calling sequence used by the  C  compiler.  To  code
             generated for:

             call sub(a,b,c)


             Is:

             push c-address
             push b-address
             push a-address
     FCINT(I-UBC)                   04/16/80                  Page  6



             push 3      (the number of arguments)
             mov sp,r5   (point to parameter list with r5)
             jsr pc,sub  (call the subroutine)
             add $8,sp   (pop off 3 parameters and arg count)



             The   Fortran   calling   sequence   has  the  following
             characteristics:

        1    the addresses (NOT the values)  of  the  parameters  are
             passed  to  the called routine. If an expession is given
             the result is stored into a temporary location  and  the
             address of the temporary is passed.

        2    the  number  of  parameters  is  passed  as  part of the
             parameter list.

        3    General register five (R5) is set to point to the  start
             of  the  parameter  list.  This  is  only  so that older
             subroutine written  in  assembler  language  will  still
             work.  New  assembler  routines  should conform to the C
             calling sequence.

        4    the actual transfer is  via  a  "JSR  PC,SUB"  (i.e. the
             return  address  is  on  the  top  of  the  stack)  this
             satisfies the DEC PC, calling sequence convention.

        5    since the parameter list itself is stored on the top  of
             the stack, the C calling sequence is also satisfied.

        6    after  the  called routine returns (it has saved R2, R3,
             R4, and preserved the value of the stack pointer SP) the
             parameter list is popped off the stack.


             The runtime now assumes that the parameter list has been
             placed on the stack (as for a  C  call),  and  does  not
             assume that R5 points to it (although for a Fortran call
             it  will). This makes calling C from Fortran and Fortran
             from C more reliable, since it does not assume  anything
             about what R5 points to.


             Note  however,  that  any C routine that calls a Fortran
             subroutine must provide  the  count  of  the  number  of
             arguments  in  order  to  satisfy  the  Fortran  calling
             sequence, and all arguments must be passed  by  address,
             not value.

     ACCESSING OF PARAMETERS
             as part of the procedure entry code the addresses of the
             parameters   are   copied   into  local  variables.  All
             references to parameters is done by  using  the  address
             stored in the local pointers.
     FCINT(I-UBC)                   04/16/80                  Page  7



     SHARED AND SEPARATED CODE
             the  runtime  is  set up to be sharable and the compiler
             generates code that may be made sharable (that is useful
             when  tracking  down  code  that   overwrites   itself).
             However,  since the generated code must be in DATA space
             if I/D separation is made little advantage is gained  by
             puting just the runtime code into the INSTRUCTION space.
             It  could  be  done,  but  the  ability to make the code
             itself sharable is more generally usefull.


             In order to gain full advantage of  I/D  separation  the
             compiler  would  have  to  generate actual machine code.
             This is not trivial and would  be  advantagous  for  the
             speed advantage if nothing else.

     COMPILER OPTIMIZATIONS
             the  compiler  does  not  attempt many optimizations. It
             optimizes the following types of expessions (mostly  for
             efficient subscript evaluation).

             c1 [+ - * /] c2 ==> c3
             e [+ -] c1 ==> [+ -] c1 + e
             e [+ -] 0  ==> e
             e [* /] 1  ==> e
             c1 * (c2 [+ -] e) ==> (c1*c2) [+ -] (c1 * e)



             In  addition,  it factors out any constants in subscript
             calculations that can  be  incorporated  into  the  base
             address  of the array. For example: a(2*i+3) is compiled
             into value((a+8)+8*i) where (a+8) is known  at  linkedit
             time.

     IF STATEMENT OPTIMIZATIONS
             the  compiler  will optimize IF statements that evaluate
             to constants in such a way that code that can  never  be
             executed is not actually generated. For example:

             1      if (.false.) print,'hi'
             2      if (.true.) print,'there'



             Is actually compiled as if it were written:

             1      continue
             2      print,'there'


     FCINT(I-UBC)                   04/16/80                  Page  8




             This  also  applies to the BLOCK-IF statements. Whenever
             the compiler is  able  to  determine  the  result  of  a
             logical  expression  at  compile time it will do so. For
             example:

                   parameter (i=1)
                   if (i.eq.1) then
                        stmt-1
                   else
                        stmt-2
                   endif

             In this case, since the expression is known to  be  true
             at compile time it is compiled as if it were written:

                  stmt-1


     TOUR OF THE COMPILER
             there are two main loops in the compiler:

        1    "compile"   is   responsible  for  doing  the  syntactic
             analysis  and  generating  the  code  trees   for   each
             statment.

        2    "code"  is  responsible  for  the  code  generation,  by
             processing the code  trees  and  generating  the  object
             module.

     COMPILE


        1    calls  "initpgm"  to  initialize  for  compiling  a  new
             program section.

        2    sets the error exit (via "setexit") to gain  control  in
             case of an error being detected at a lower level.

        3    reads each statment via "readstmt"

        4    picks up a label via "scanlabel".

        5    calls  "compress"  to  take out the blanks, (taking care
             not to mangle hollerith and string constants),

        6    calls "classify" to find out what sort  of  statment  it
             is,

        7    calls "dolabel" if there was a label present

     FCINT(I-UBC)                   04/16/80                  Page  9



        8    calls "process" to actually process the statment.

        9    calls  "stmtend"  to  dump the generated expression tree
             onto the file "tmp".

        10   "code" is called when "compile" has processed  an  "end"
             statment.

     CODE


        1    allocates common (via "alloccm")

        2    "codepass" does one pass thru the code trees to generate
             the addresses for labels and constants.

        3    allocates  the  constants via "allocconsts" and the data
             initialized variables via "allocdata"

        4    the normal variables are allocated via "allocbss".

        5    the object file is created via "objcreate".

        6    the second pass thru the code  tree  is  made,  actually
             writing  the  genrated  code  onto  the  object file via
             "codepass".

        7    the constants are now actually written into  the  proper
             place   (at   the   end   of   the   TEXT  section)  via
             "allocconsts".

        8    "endobj" is called to clean  up  and  write  the  symbol
             table.
