SECTION 8


                              PL/I LANGUAGE CONVENTIONS




     This  section  highlights  the  coding rules for system  programs that are to be
written in the PL/I language.    Recommendations for  generating  efficient  code are
included.    The rules are to be taken   as guidelines   there will be  rare programs
that follow every rule.   Refer to "Efficient PL/I Constructs" below.


     PL/I  is  the  Multics Operating  System  programming language.  However,  there
are several features in the language which  should be  avoided  either  because  they
are  inefficient,  they are not  implemented  well  by our compiler,  or they lead to
complex  coding constructs.   The following language features  should be  avoided  by
subsystem programs:

     1.     use of PL/I input/output statements

     2.     aggregate expressions (except for assignment)

     3.     condition prefixes

     4.     use of "returns (char (*))"

     5.     use of the built-in function decat



CONSTRAINTS


     The following list describes some general restrictions and requirements

     1.     all variable names have to be declared in a declare statement

     2.     each  reference to a  member  of  a structure  must be qualified   by the
            name of the level-one containing structure

     3.     no compilation warning messages or error messages are allowed

     4.     implicit conversions should not be used

     5.     multiple block closures by an end statement should not be used

     6.     the default statement should not be used

     7.     executable  statements should be used to  initialize  automatic variables
            to make the action more explicit,  rather than the initial attribute.

     8.     no variable names should be the same as keywords in the PL/I language.



EFFICIENT PL/I CONSTRUCTS


     This  subsection  is an  informal  guide to  efficient use of  the  Multics PL/I
compiler.   It provides   advice  on how to  take advantage of  the  good features of
the compiler  while avoiding its   weaknesses.   Emphasis is   placed  on  constructs
which   produce more   efficient code   than others.    The reader   is assumed to be
familiar with PL/I.


     For  a  semiformal  definition  of the  language  supported by the  Muitics PL/I
compiler, see the Multics PL/I Language Specification,  Order No. AG94.



The Alignment Attributes


     The  use of the  aligned  attribute and  the  unaligned  attribute  can  have  a
great effect on the  speed of a  program and the  size of its  data base.   Unaligned
items   can start on   a bit boundary   (character boundary  for   character strings,
pictures,  and   decimal  variables),   aligned   items   must   start  on at least a
fullword  boundary and  occupy an   integral   number   of fullwords.    If a   value
requires 72 bits or  less of  storage to  represent it,  access of the  value will be
faster if its generation of  storage is  aligned because  it can be  directly  loaded
into the aq registers.



ATTRIBUTES WITH ARITHMETIC AND POINTER VARIABLES


     Access  of  aligned  binary and  pointer  variables is  usually faster than that
of   unaligned variables.    The only   exception to   the above   is that  unaligned
pointers  that   the   compiler   recognizes  as  aligned   are  accessed   at speeds
comparable   to that   of aligned   pointers, but   the former   cannot be indirected
through.   You  should  use aligned  binary and pointer  variables   for local scalar
variables,   and  only use   unaligned binary and   pointer variables in   large data
structures where size is important, but speed of access is not.

     The alignment   attribute   has  no   effect   on   the   access time of decimal
variables or varying strings.



USE OF THE ALIGNMENT ATTRIBUTES WITH SHORT STRINGS


     A  short  string is  defined to be a  nonvarying  string with  constant  extents
whose  length   is less than   or equal to   72 bits (eight  characters).   Access of
aligned  short strings   is usually  much   faster   than that   of unaligned   short
strings.   Thus,  it   is recommended that one use  aligned   short strings for local
scalar variables,  and restrict the use  of  unaligned  short  strings to  large data
structures where space is important.



USE OF THE ALIGNMENT ATTRIBUTE WITH LONG STRINGS

     All  nonvarying strings   that are   not   short   are considered   to be  long.
Because  these strings are too  long to   fit into the aq  registers or  their length
is not known at compile time,  the use of the  aligned  attribute  does not  speed up
their   access.   It   is   recommended   that   one   use   the   default  alignment
attribute--unaligned.



USE OF UNALIGNED SHORT VARIABLES IN ARRAYS AND STRUCTURES


     For  the  purposes  of this  discussion,  short  variables are  those  variables
which  occupy no more than 72 bits  (eight characters) of  storage  and are  declared
with constant extents.


     When  accessing an  element of  an  array of  short  unaligned  variables,   the
access  code  is  quicker if  a  constant  subscript  is used,  because  the compiler
uses an   EIS (Extended Instruction Set)   instruction,  when the  subscript   is not
constant,   in   accessing   the  variable.    If  an  unaligned  short  variable  is
contained in an    array  of  structures, and  the variable    is  accessed   with  a
nonconstant  subscript,  access   code is faster if the  array  is declared  aligned,
because the use of an EIS instruction is avoided.



Use of the Precision Attribute in Offset and Length Expressions


     Because  the Multics  CPU's  index   registers   can   only   hold   18  bits of
information, while up to  24 bits may be  needed to  express  the  offset  or  length
of a  string  for  use in  an EIS instruction,  the  compiler  must  make use  of the
precision   attribute   in  deciding   which  register  to  use.    If  a   subscript
expression, the second or third argument  of the  substr  builtin,   or the  declared
length of  a string  has a  precision  of 18 or  less,  it can  be  kept in  an index
register, whereas if the precision is more  than   18, it  must be  kept in  the a or
q  register.   This  means,  for  example,  that  if a  user  knows  that  he wants a
substring that  may be  more than  262,143  items long,  then  the  precision  of the
third  argument  of substr  should  reflect  that fact (otherwise the high-order bits
of the length may be lost).   Conversely,  if the user knows  that a  string  is less
than  262,144  items long,  he should  reflect  that knowledge in the  precision used
for subscripts  and  arguments to  substr.    (Besides  looking at the   precision of
the  length and  offset  expressions,  the compiler also  makes  use  of the declared
string size in cases of  constant  extents to  determine  where  the offset or length
may be kept.)


     The  general  guideline  is  to  always   declare  variables  with  the  correct
precision.   The  following  precisions  are  guidelines  when  a user is  not sure a
smaller precision will suffice.   A word offset  into a  segment  should be  declared
fixed bin(18).   A number of words  on a  segment  should be  declared fixed bin(19).
Character   string  indexes  and lengths  should  be  declared  fixed  bin(21).   Bit
string indexes and lengths should be declared fixed bin(24)



The Use of Internal Static to Simulate Named Constants


     If a  variable is  declared to be  internal  static  with an  initial  attribute
and  is  never  set  within a  program,  the  compiler will treat it as  if it were a
constant.   (A  variable is considered set if  it appears  on  the  left  side  of an
assignment statement,  is the first argument  of a  pseudovariable,   or a  reference
of a  defined attribute.)   Converting  an   internal static  variable to  a constant
means  that  more  efficient  code  will  often  be  generated  to  use the variable,
sometimes avoiding storage references, and that the variable  will  not  have  to  be
copied  into the  combined  linkage  section  upon initiation of the segment.   Since
passing a variable as an  argument is  equivalent to  setting it,  one  must  enclose
the  variable  in parentheses  if it is to  appear  in an  argument list.   This will
make the variable be  passed by value and  force a  copy to  be  made at  call  time.
The  options(constant)  attribute  may  be  used   to  tell  the  compiler  that  the
variable is  not set even if  passed as an  argument.    Making  sure  that  such  an
internal   static  variable,  which  the  user  intends  to  use  as a  constant,  is
considered by the compiler to be a  constant is  worthwhile  if the  variable is  not
a  long  string  which  is  only  used in a few calls.  This  feature of the compiler
is a good substitute for named  constants  which the  PL/I  language  generally  does
not provide.


     When  "options(constant)"   is  added to  the  declaration of an internal static
initialed  variable,  the variable is  allocated in the  text section  whether or not
it  is set or   passed as an argument.   The user is   responsible  for ensuring that
the variable is   not actually set,  however,  as  this would   cause faults or other
errors.



Use of the Initial Attribute


     The  compiler's  implementation   of   the   initial   attribute  for automatic,
based, and controlled arrays  is  inefficient   compared  with the code  the user can
get  from  explicit  assignment  statements.   Therefore, using the initial attribute
in the above cases is discouraged.   Since   the use of the  initial  attribute  does
not generate   code for  static  variables,   the above statement does   not apply in
that case.    Users are warned,   however,  that use   of the initial   attribute can
make a  program more  difficult to read   in some cases,  and that  initialization of
large   external static   arrays this   way can   cause creation   of a larger object
segment than intended.



The Assignment Operation



THE MULTIPLE ASSIGNMENT STATEMENT


     In  deciding  whether or  not to use a  multiple  assignment   statement  rather
than    separate   assignment   statements,   is   useful   to   know   under   which
circumstances  multiple   assignment   statements  produce   inefflcient   code.    A
multiple assignment statement of the form;

     Ti, T2, ---, Tn = E;

where    E   is  not a   constant,    is  semantically   equivalent  to the  separate
statements:

     V  = E;

     T1 = V;

     T2 = V;

        .

        .

        .

     Tn = V;

If the  temporary   represented by V  can be kept in   a machine  register throughout
the assignment,   then the multiple assignment  statement   is  efficient.   Clearly,
this  implies that    if E  is longer   than two    words,  the  multiple  assignment
statement  will not  be efficient,   since   E  cannot fit   in a   register.   Thus,
multiple  assignment  statements   are not  efficient when the right   hand side is a
long string, a varying string, an entry   value,  a  label value,  a   file value,  a
format value, an area, a decimal value, a complex value, or an aggregate.



CONVERSIONS


     All of  the PL/I  conversions  are  efficient,   many of them  producing  inline
code,  while the  others  produce  calls  to  any_to_any_.   Inline  code is produced
for  all  cases   where  neither  the  source  nor   target  are  complex,   decimal,
character string,  or  picture  (see  the  discussion  of  pictures  below).   Of the
other cases, the following produces inline code:

     complex_float binary     (precision<=27) = real binary;

     real binary                    =     complex_float binary (precision<=27);

     real decimal                   =     real decimal;

     complex decimal                =     complex decimal;

     real binary integer            =     real decimal;

     real decimal                   =     real binary integer;

     character                      =     real fixed decimal;

     character                      =     real binary integer;

All other cases produce calls to any_to_any_.


     The  convert  builtin  function  can  be  used  to   effect  conversion  between
character  and  binary  and to  avoid  intermediate conversions  that other  builtins
might cause.



PICTURES


     The  use of  pictures  provides a  convenient way to  get  efficient  controlled
conversion  between  arithmetlc  and character.   When using pictures,  the  user can
avoid PL/I's inconvenient conversion rules by specifying the desired format.


     While  picture  unpacking  (going  from  character to arithmetic  form)  is done
by   pl1_operators,   the  most  common  cases  of   picture  editing   (going   from
arithmetic to  character form)  are done  inline.   Inline  code is generated for the
majority  of  cases  of  editing  into real fixed  pictures.   The cases  of  editing
into  real  fixed  pictures  that   is   done  by  pl1_operators_   are  any  of  the
following:

     °     the absolute value of the number's scale is greater than 31

     °     a "y" picture character  appears  in  a  drifting  field  picture   (e.g.,
           $$$y99)

     °     a  zero  suppression  character  or  drifting  character  appears  to  the
           right of the "v" picture character

     °     the  inline  sequence  requires  more  than  63  micro-ops  for  the  MVNE
           instruction



ARITHMETIC OPERATIONS


     Most  arithmetic  operations are  implemented  with fast inline  code.   The one
general   exception   is  the  power   operator  (e.g.   **)   which   is   sometimes
implemented  by pl1_operators_   or subroutine   calls,   users are cautioned against
using the "/"  operator with fixed point operands  as the  PL/I  precision  rules may
cause unexpected results.   Use the divide builtin function instead.



BINARY OPERATIONS


     Most   binary  arithmetic  operations  produce inline  code.   Multiplication of
fixed   binary    (precision>=36)  numbers  utilizes  pl1_operators_  references, all
fixed  binary   division   invoked  by   the   "/"    operator   causes    references
pl1_operators_ routines.


     The  "**"  operation  generates  pl1_operators_   calls for   real  operands and
full subroutine calls for  complex operands.    If the  operands  are both real,  and
the  second  operand is a  positive  integer  constant that could be represented as a
fixed bin(35)  value,  inline code will be   generated to do the  power  operation as
repeated multiplications.



DECIMAL OPERATIONS


     Most   decimal  arithmetic  operations   cause   efficient   inline   code to be
generated.    The major  exception is   the case   of one   or both   of the operands
having a   scale  greater than 32   or less than -31.    This case will often   cause
additional   assignments   or  multiplications  to   be  generated   since   the 6180
hardware only handles scales within the range -31  to 32.


     If  the power  operator  has  decimal operands,  a conversion to and from binary
and/or a subroutine call will be generated.



String Operations


     All  string  operations  (as opposed   to builtins)   cause inline   code  to be
generated.   In addition,   some  special  cases cause better than  usual code to  be
generated.



SPECIAL CASE OF CONCATENATION

     Concatenation  is  often  used in   constructing varying   strings.   A   normal
concatenation of the form:

     a = b || c;

results in  three (3) moves -- b and c are moved  into a temporary,   and the  result
is moved into a.     However, a concatenation of the form:

     vs = vs || c;


where  vs is a  varying string,  results in just   one move --  a is moved to the end
of  vs.   The   latter special   case can   be used   to great  advantage in building
varying strings.   Consider the following example:

     vs =   a ||  b ||  c;

results   in  four moves   and perhaps some   instructions to allocate   temporaries,
while:

     vs = a;

     vs = vs || b;

     vs = vs || c;

results in three moves with no temporaries allocated.



OPERATIONS ON LONG STRINGS


Most statements of the form:

          a = b <bool_op> c;

          a = translate (b,...);

          a = bool (b,c,<bolr>);

where  a,  b,  and   c are long nonvarying strings, cause code   to be generated that
performs   the  operation   in  a  temporary   and   then  moves   the result into a.
However,   if a is the same length as   the temporary  would be,  and if the compiler
believes that a  could not  possibly overlap   with b or c,   then the operation will
be performed directly in a and no temporary will be allocated.

     in a statement of the form:

     if a <op> b ...

or

     if bool (a, b, <bolr>) ...

where a   and b are long strings,   the  compiler will  attempt to   do the operation
without allocating  a  temporary,   by using an  SZTL instruction if the value is not
needed elsewhere.



AGGREGATE OPERATIONS


     Most  aggregate  operations,   other than  simple  assignment and the use of the
string and   unspec builtins and   pseudovarlables,  are  relatively   inefficient in
the   present Multics   PL/I implementation    and should   be avoided.    By simple,
assignment, we mean assignment statements of the form:

     p -> aggregate = q -> aggregate;



Use of the Builtin Functions


     Most  of the  standard   PL/I   builtin   functions   and   pseudovariables  are
implemented  efficiently  in the  Multics  compiler.   However,  there are exceptions
and special cases.




ARITHMETIC BUILTINS


     With the  exception  of the  divide builtin,   all the arithmetic builtins cause
effIcient code to be generated.   The  divide builtin is  inefficient  only for  some
cases in which a   fixed binary  result is  produced.   If a   fixed binary result is
produced,  a reference to a very   slow pl1_operators_  divide  routine is  generated
uniess the result   and both operands are unscaled with a  precision     less than or
equal to 35.



STRING BUILTINS


     Efficient  inline or  out-of-line  code is  generated  for all  but  one  string
builtin, decat.    Execution of the decat   builtin is  about 50   times  slower than
might be expected.


     There are  special  cases of  some of the   other  string  builtins   that cause
more  efficient code   to be generated  than is normally   generated for the  general
case.   These are:

     index (<char_str>, <char1>)

     index (<char_str>, <char2>)

     index (reverse(<char_str>), <char1>)

     index (reverse(<char_str>), <char2>)

     index (reverse(<char str>), reverse(<char2>))

     search (<char1>, <char_str>)

     verify (<char2>, <char_str>)

     search (<char_str>, <constant>)

     verify (<char_str>, <constant>)

     search (reverse(<char_str>), <constant>)

     verify (reverse(<char_str>), <constant>)

     translate (<char_str>, <constant> [,<constant>])

     before (<char_str>, <char1>)

     before (<char_str>, <char2>)

     after (<char_str>, <char1>)

     after (<char_str>, <char2>)

     ltrim (<char_str>, <constant>)

     rtrim (<char_str>, <constant>)

     copy (<char1_constant>, expression)



MATHEMATICAL BUILTINS


      References to  the mathematical  builtin  functions are  compiled  either  into
fast references  to  pl1_operators_   or   into   slower   subroutine   calls.    The
following  math  builtlns  are  implemented  in  pl1_operators_  if  they  have  real
arguments:

     atan     exp      sin    tand

     atand    log      sind

     cos      log10    sqrt

     cosd     log2     tan

All other cases produce subroutine calls.



The Call Statement and Function References


     When a  call statement or  function  reference  is  executed,   in  the  general
case,  an  argument list  must be  constructed  which takes 3 + 2*number_of_arguments
words.    When the  new  procedure  block   is  entered,   a   new  stack   frame  is
established by a pl1_operators_  routine that  takes  around 30  instructions.   This
is  a high  overhead  to  have  when  using  an  important  feature  of  PL/I that is
necessary  for good  programming practice.   The Multics  system  PL/I  compiler  has
two  optimizations  which can  greatly reduce this overhead.   First,  it can  decide
that an  internal  procedure  or begin  block may  share the  stack frame of  another
block rather than  obtaining its own.   A block  that does not  obtain its own  stack
frame is  called  a  "quick"  block or  procedure.   Second,  the compiler  can build
argument  lists  to  quick  procedures  at  compile  time,   if  the  arguments  have
constant  addresses   known  at  compile  time.    These  two  optimizations  greatly
reduce the cost of call statements and function references.



DETERMINING THE 'QUICKNESS' OF A BLOCK


     The  Multics PL/I  compiler goes  through  a  two  stage  process  to  determine
which  (procecure  or begin)  blocks  can be  quick,  that is,  which  ones need  not
obtain  stack frames.   The first stage  excludes  blocks  from  being quick  because
of their properties.   The following properties can make a block non-quick.

     °     it is the external procedure block

     °     it is an ON-unit

     °     it has I/O statements

     °     it has format staternents

     °     it has ON, or revert statements

     °     it has automatic variables with expression extents

     °     it  has an  entry  that is  assigned to an entry  variable or passed as an
           argument

     °     it has an entry with a star-extent return value

     °     it has an  entry with a  star-extent  parameter  that is  called  with the
           corresponding   argument    being   an    expression   whose   length   is
           non-constant

     °     it  has an  entry  that is  referenced in the argument list of such a call
           after the aforementioned argument


     In the second stage,  the compiler  uses a graph of the  calls  between  blocks,
to  determine  which of the remaining eligible blocks can be quick.    The  algorithm
used  in  this  stage is an  iterative  one  based  on  the  constraint  that a quick
block may use  the  stack frame of one  and only one non-quick  block and    thus may
effectively  be invoked  from only  one  non-quick  block.   In fact,  the  algorithm
states  that a  quick  block  may  be  invoked  from  only  one  stack frame,  and an
invocation from a quick block is considered an invocation from its      owner's stack
frame.


     A  user can determine which  blocks  have  been  made  quick  by  examining  the
symbols  listing  produced  by  the  compiler.    In  the  section  marked,  "STORAGE
REQUIREMENTS FOR THIS PROGRAM"  is a  list of all the      blocks in the program.  If
the  line  for a  particular  block  contains  the  words,  "shares stack frame  of",
that block is quick.




USING CONSTANT ARGUMENT LISTS



     In  generating  a quick  procedure  call,  the Multics  PL/I compiler  can often
generate a constant argument list if the  addresses  of the  arguments  are  known at
compile  time.    This  saves  the cost  of  executing  instructions  to  set up  the
argument  list  at  runtime.    At  this time   the  following  constraints  must  be
satisfied for the compiler to generate a constant argument list:


     °     the quick procedure must contain no non-quick blocks

     °     the stack frame of the caller must be smaller than 16,384 words

     °     the  arguments  must be  constants,  expressions  with operators,  builtin
           references,  function references, or automatic variables

     °     all  automatic  arguments must be allocated  in  the  stack  frame of  the
           caller

     °     all automatic arguments must have constant extents

     °     all subscripted arguments must have constant subscripts




Using If Statements


     In handling if statements containing logical operators, such as:

     if x = 0 | p ^= null | x + 3<z
          then call a;

     if z>3 & q = null & loaded
          then call b;

the  Multics  system  PL/I compiler  (as of  MR4.0)  attempts  to generate  code that
uses  the minimum number  of operations  to  decide the  result.   This is  a  change
from   previous  releases  of  the  compiler  that  always  evaluated  the   complete
expression in  the if statement.   In order  for  this optimization  to  take  place,
the user must  specify the -optimize control  argument for   the  compilation,  there
must be no irreducible  function references  in the  expression,  and the  expression
must  evaluate  to a bit(1)  value.   Thus a user  should  feel  free to use  logical
operators in if statements without worrying about their efficiency.


     NOTE:     no  program may depend on the  order of evaluation  of operands in the
     expression of an If statement.   Thus, the statement:

        if p ^= null
        & p -> q  = 0 then ...
        is illegal

Any  program  that  depends  on  complete  or    incomplete  evaluation  of  such  an
expression  is  in  error,   unless  the  expression  contains  irreducible  function
references,  in which case complete evaluation takes place.



Optimlzation of Comparisons


     The  Multics system  PL/I compiler (as  of  MR4.0)  remembers  in  its  abstract
machine state model the  most recent  comparison or  indicator  setting  operation at
any  particular  point  in time  in generating  object  code.    This  enables  it to
remove redundant comparisons  in constructs such as the following:

     if a<b
          then call foo;
          else if a = b
          then ...



Other Constructs That Are Costly or Dangerous


     °     default statements

     °     multidimensional arrays with star bounds

     °     arrays of elements of star extents

     °     programs requiring a stack frame of more than 16,384 words

Back  
This material is taken from:
Multics System Designers' Notebook(AN82-00)
Copyright Honeywell, Inc., 1980