Trailing-Edge
-
PDP-10 Archives
-
decuslib20-01
-
decus/20-0004/nobox.tty
There are no other files named nobox.tty in the archive.
NOBOX
This file contains facilities for compile-time suppression of
unnecessary allocation and collection of cons cells, large integer
boxes, and floating boxes. It also provides access fields that can
be used to speed up boxing and unboxing in arithmetic operations.
The trick for managing storage allocation is basically to allocate
the memory for temporary results (e.g. a list that will be thrown
away or a floating number that will not exist outside a local
computational context) at compile-time or load-time. This "static"
storage will be re-used whenever the given line of code is
re-executed. Compiled functions need no run-time support for these
facilities, so that NOBOX need not be loaded to execute compiled
code.
CONS CELLS.
The function CBOX may be used to suppress allocation of cons cells.
It operates like the function CONS, except that the cons-cell
returned is constructed at compile/load time. New values for CAR and
CDR are smashed into the cell at each execution. When run
interpreted, CBOX is exactly equivalent to CONS.
The function LBOX is to LIST and CBOX is to CONS. Thus, (LBOX A B C)
will cause a 3-element static list to be included with a compiled
function's literals. Each time the line of code is executed, those
three cells will be returned containing the current values of the
variables A, B, and C. When run interpreted, LBOX is exactly
equivalent to LIST.
LBOX allocates cells according to the number of its arguments. There
is also a mechanism for suppressing new conses when the length of a
list is not known at compile-time. This is accomplished by the
iterative statement operator SCRATCHCOLLECT. This can be used in
iterative statements exactly as COLLECT. Each time it is executed,
however, it re-uses the cells that it returned on previous
executions, which it has remembered as an internal scratch list.
This scratch list will always be the length of the maximum value that
was ever returned; new cells will be allocated whenever the scratch
list runs out, and they will be permanently remembered.
CBOX, LBOX, and SCRATCHCOLLECT, like the arithmetic facilities
described below, must be used carefully. They should only be used
for processing temporary values, since the static cells will be
smashed if the line of code is re-executed. See Note 2 below.
NUMBER BOXES.
There are 3 functions and 2 record declarations for improving the
efficiency of arithmetic computations. They permit information to be
given to the PDP-10 compiler (not the byte-compiler) that will
improve access time to variable-values that are known to be large
integers or floating point numbers, and that will inhibit the
allocation (and subsequent collection) of number boxes needed for
holding temporary results of numeric computations. In the latter
respect, these duplicate some of what SETN does, except that they are
more convenient to use and are executed more efficiently.
The records declared in this file (IBOX and FBOX) describe the
structure of large-number and floating-point boxes. IBOX has a
single field, called I, by which the user can cause the bits of a
large-integer box to be extracted. The use can create a large
integer box containing a given value by saying
(SETQ X (create IBOX I _ (FOO)))
Even if (FOO) evaluates to a small integer, the result will be stored
in a new large-number box. Why is this seeming ineffeciency
important? Because if some values of (FOO) might be large, making
all values large means that the compiler can be told exactly how to
treat future references to X without generating run-time tests to
discover how to do the unboxing. Thus, wherever the value of X is to
be referenced, the user can simply write X:I (or (fetch I of X). The
compiler will generate a single MOVE instruction without any
type-testing whatsoever. Furthermore, given that X is guaranteed to
be bound to a large integer, the user can re-use that number box by
saying X:I_(FUM), which is equivalent but much more efficent than
(SETN X (FUM)). In other words, once it is known that X is bound to
a large integer, the suffix :I can be used in all number-contexts to
inform the compiler of that fact.
The record FBOX and the field F act in the same way for
floating-point boxes. Note, however, that the (create FBOX --)
construction isn't that useful for floating point numbers, since
there is no distinction between small and large to be erased. The :F
suffix can still be used to improve accesses to the values, however.
The facilities described so far do nothing to suppress the creation
of unnecessary boxes; indeed, the (create IBOX --) will produces
boxes for small numbers that would not be allocated otherwise. The
functions (not records) IBOX, FBOX, and NBOX are used to suppress
unnecessary boxing of temporaries. Effectively, they cause
"constant" or "static" boxes of the appropriate type to be allocated
and stored in a functions literals when a function is compiled or
loaded. Those boxes can be used (and reused) to hold temporary
results.
IBOX and FBOX can appear with 0 or 1 arguments. If no arguments are
specified (which is different than having a single argument evaluate
to NIL), then the value of the function is a large-integer or
floating number box, respectively, which is allocated statically.
These might be used to construct an initial binding for a variable
into which temporary values will be stored using the :I or :F
assignments. For example
(PROG ((X (IBOX)))
(x:I_(FOO)) ...).
If an argument is specified for IBOX or FBOX, then again a static box
of the appropriate type will be allocated at compile- or load-time,
but the value of the argument will be stored in that box whenever the
IBOX statement is executed. For example, suppose you wanted to set a
file pointer to 1 past a given byte position. If you said
(SETFILEPTR FILE (ADD1 POS))
and POS happened to be large, you would get a new number box
allocated on each execution. That box would be passed into
SETFILEPTR and then returned as its value. Since the value is not
saved, the box would be thrown away, to be collected later. If you
said
(SETFILEPTR FILE (IBOX (ADD1 POS)))
the desired position would be stored in a constant box--no
allocations would take place. For another example, suppose that you
have a complicated integer expression whose value must be saved in a
variable to be used a little further down in your code:
(X_(IPLUS 2000 (ITIMES FOO (IQUOTIENT FUM 5))))
...
(Z_(IPLUS X (GETFILEPTR FILE)))
The PDP-10 compiler is smart enough to suppress the boxing inside the
X expression tree, but it will generate a box when it comes to do the
SETQ (_). This can be suppressed by writing
(X_(IBOX (IPLUS 2000 (ITIMES FOO (IQUOTIENT FUM 5)))))
Furthermore, it is now known that X is bound to a large-integer, so
the
Z assignment can be speeded up by writing
(Z_(IPLUS X:I (GETFILEPTR FILE)))
In effect, the function IBOX suppresses boxing at the root of an
arithmetic expression tree; the field I can be used to speed access
at the leaves of such a tree. The compiler knows how to generate
efficient code between the root and the leaves.
The function FBOX behaves the same as IBOX, except that it traffics
in constant floating boxes. Note that if the argument of IBOX is
FLOATP, then it will be FIXed; if the argument of FBOX is FIXP, it
will be FLOATed.
The function NBOX is a generic function for copying unknown values
into constant number boxes. It allocates 2 constant boxes, one
integer and one floating, and stores the value of its argument in the
one compatible with the value's type. This is of limited utility,
since it does not suppress the explicit boxing that might result from
evaluation of its argument. It is useful only if the argument value
is a constant number box (of unknown type) that needs to be copied
(see caution 2 below).
CAUTIONS
There are some dangers in using these facilities, just as there are
with SETN, FRPLACA, and any other function that smashes structures.
The user of this package should be particularly aware of the
following:
1. The F and I fields aim at efficiency more than validity. This
means that they DO NOT CHECK THE TYPE of the pointer that they smash
into. If you say, for example, X:I_2, and X is bound to NIL, then
you will have clobbered CAR and CDR of NIL! You must be very careful
that the arguments that you give for replacing do indeed point to
cells that unboxed numbers can be smashed into. (These need not be
explicit number boxes--it is legal to store into the unboxed region
of an array, for example, by presenting a pointer to the array cell
in the replace statement.) Note: the DECLTRAN package can be used
to generate the replaces, IBOXes, FBOXes automatically in a safe and
efficient way.
2. CBOX, LBOX, SCRATHCOLLECT, IBOX, and FBOX allocate constant
boxes, and those boxes will be reused (i.e. smashed with new values)
every time the line containing the function call is executed. If you
save that box in a variable or data-structure (e.g. by a SETQ) as a
way of preserving the value it contains, and then re-execute that
line of code, the saved value will be smashed. Thus you must beware
of using the constant boxes to save information in loops or
recursions that can get you back to the same statement. In these
situations, you must copy the value into other cells, perhaps a
constant associated with some other line of code, or perhaps you must
simply allow lisp to allocate the box to copy into in the ordinary
way. The first approach can be realized by SCRATCHCOLLECT, IBOX,
FBOX or NBOX:
(for V in X SCRATCHCOLLECT V), if X is the value of an LBOX, or
(SETQ Y (IBOX X:I], if X was the result of a previous IBOX,
will copy the value into new constants. The second approach can be
obtained by APPEND for LBOX, or by simply referencing X:I for IBOX
(:F for FBOX): (SETQ Y X:I]. You must also be careful about
returning a constant box as the value of a function, since the caller
might unknowingly save the value and re-invoke the box-returner.
----------------
These facilities were implemented by Ron Kaplan, with help from
Martin Kay and Beau Sheil.