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.