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
 |
|
This material is taken from:
Multics System Designers' Notebook(AN82-00)
Copyright Honeywell, Inc., 1980
|
|