Store Mapping

KEY A variable declared in an IMP80 program is allocated storage on the stack (Section 3). It is possible in IMP80 to give an alternative name to such a variable. It is also possible to refer to an arbitrary store location, not necessarily within the stack area, and operate on it as though it held a variable of a specified type. "Store mapping" is the name given to this technique. It is useful for the following reasons: * to enable entities not declared within a program to be accessed and operated on by the program, without recourse to machine code or other machine specific features * to save space in main store (see first example below) * to access a variable both as declared, and as a set of sub-variables; for example, a variable of type %integer can be accessed as several separate variables of type %byte %integer
* to access an array element as a simple variable, with consequent saving of machine time * to improve the clarity of a program The store mapping facilities in IMP80 are provided by means of several components: * reference variables * user-written mapping functions * the ADDR function * the standard mapping functions

Reference variables & mapping fns

KEY Two of the fundamental entities from which programs are constructed are constants and variables (Section 2). A variable comprises an identifier (its name), and a type which specifies what type of value it may have. Its value is a constant, or a reference to a variable. Thus, the value of a variable of type %integer is an integer constant, while the value of a variable of type %integer %name is a reference to an integer variable. A function (Section 3) is similar to a variable in that it has an identifier, and a type which specifies what sort of value it returns. A mapping function (or map) is analogous to a reference variable in that its type (the type of the value that it returns) is a reference to a variable; for this reason maps are sometimes called "name functions". The difference between a variable and a function, of course, is that a variable has its value stored while a function computes its result.

Examples

KEY 1) Use of a reference variable with a map. In this program, a two-dimensional array is accessed by means of a map, because the array is symmetrical: i.e. X(i,j) = X(j,i) for all valid i and j. Thus to save storage space only the values of X(i,j) with i >= j are stored. By keeping only these values in a one-dimensional array A - which is global to the map - we can make economical use of store without losing the symmetrical appearance of the array X. The array is first assigned values and then various references to it involving %integer ALPHA and %integer %name BRAVO are made.
%begin %integer %array A(1:210) %integer ALPHA, I, J %integer %name BRAVO %integer %map X(%integer I,J) %signal %event 6 %unless 1<=I<=20 %and 1<=J<=20; ! Array bound check. %result == A(I*(I-1)/2 + J) %if I>J %result == A(J*(J-1)/2 + I) %end %for I = 1,1,20 %cycle X(I,J) = I\\2 + 2*I*J + J\\2 %for J = 1,1,I %repeat
ALPHA = X(17,10) {ALPHA assigned the value of the variable returned {by the call X(17,10) of map X. BRAVO == ALPHA {BRAVO made to refer to ALPHA (thus BRAVO is now {synonymous with ALPHA). BRAVO = 4 {BRAVO (i.e. ALPHA) assigned the value 4. BRAVO == X(16,9) {BRAVO made to refer to the variable returned by {the call X(16,9) of map X. BRAVO = 4 {BRAVO (i.e. the variable it currently refers to) {assigned the value 4. ALPHA = BRAVO {ALPHA assigned the value of the variable to which {BRAVO currently refers. This value is 4. X(6,15) = 17 {The variable to which reference is returned by {the call X(6,15) of map X is assigned the {value 17. ALPHA = X(15,6) {ALPHA set to the value of the variable returned {by the call X(15,6) of the map X. From the {previous statement, and bearing in mind the {symmetry of X, this value is 17. : %end %of %program
2) Reference variable used as a procedure parameter When a procedure is called, its formal parameters - in the example below SIZE and STATUS - are assigned values according to their types and to the actual parameters used in the call. SIZE is of type %integer and so has the current value of TOTAL assigned to it; STATUS is of type %integer %name and so is "pointed at" the integer variable RETURN. This example is included because procedure %name-type parameters provide a method for renaming variables, and as such represent a common application of store mapping techniques. %routine %spec TEST(%integer SIZE, %integer %name STATUS) %integer TOTAL, RETURN : TOTAL = 3 TEST(TOTAL, RETURN) ! TEST called: formal parameters assigned values. :
3) Mapping a direct access file The following map enables the programmer to treat a direct access file held on backing store as though it were a real array with declaration %real %array FILE(1:NBLOCK, 0:255), where NBLOCK is the number of blocks in the file. In use, the only difference from a normal array is that a closing call <real var> = FILE(0,n) must be made. In this implementation of IMP80, real variables are 32-bit entities, and each block of a direct access file consists of 1024 8-bit bytes. It is assumed that the direct access file has been associated with channel number 1 prior to the execution of this program. Note that when a virtual memory operating system is being used, a simpler and better method for mapping arrays onto files may be available - see later example.
%real %map FILE(%integer BLOCK, ELEMENT) ! The external routines specified below relate to the use of direct- ! access (DA) files. They are the standard routines provided in most ! implementations of IMP80, but do not form part of the language ! definition. Further details for some implementations are given in ! Appendix B. %externalroutinespec OPEN DA(%integer CHANNEL) %externalroutinespec CLOSE DA(%integer CHANNEL) %externalroutinespec READ DA(%integer CHANNEL, %integername BLOCK, %name START, FINISH) %externalroutinespec WRITE DA(%integer CHANNEL, %integername BLOCK, %name START, FINISH) %constant %integer NO=0, YES=1 %own %integer CURRENT BLOCK = 0, BLOCK CHANGED = NO %own %integer LAST ELEMENT %own %real LAST VALUE %own %real %array BUF(0:255) = 0(256)
%unless BLOCK>0 %and 0<=ELEMENT<=255 %start ! Could be an error, or the closing call. ! In either case, tidy up and close the file. WRITE DA(1, CURRENT BLOCK, BUF(0), BUF(255)) %if BLOCK CHANGED=YES CURRENT BLOCK = 0 %if BLOCK=0 %start; ! Closing call. CLOSE DA(1) %result == BUF(0); ! Irrelevant in this case. %finish ! Error - parameters out of range. %signal %event 6 %finish %if CURRENT BLOCK=0 %start; ! First call. OPEN DA(1) CURRENT BLOCK = BLOCK READ DA(1, CURRENT BLOCK, BUF(0), BUF(255)) %finish %else %start; ! Not the first call.
! Has the value of the last element returned by the map been ! changed? If so, note the fact so that the block is written back ! when no longer required by the map. BLOCK CHANGED = YES %if BUF(LAST ELEMENT) # LAST VALUE %if CURRENT BLOCK # BLOCK %start ! Block required differs from that currently held in BUF. ! Write currently held block back if it has been changed. WRITE DA(1,CURRENT BLOCK,BUF(0),BUF(255)) %if BLOCK CHANGED=YES ! Now get block required. CURRENT BLOCK = BLOCK READ DA(1, CURRENT BLOCK, BUF(0), BUF(255)) BLOCK CHANGED = NO; ! Reset change flag. %finish %finish LAST ELEMENT = ELEMENT LAST VALUE = BUF(ELEMENT) %result == BUF(ELEMENT) %end; ! Of %real %map FILE.

ADDR, standard mapping fns

KEY A variable has an identifier, a type and a value. A fourth attribute of an accessible variable is its address. This is a positive integer which uniquely specifies its location in the computer store. The address of a variable can be obtained by use of the standard %integer %function ADDR. Example: %string(27) S %integer ADDRESS OF S : ADDRESS OF S = ADDR(S) The parameter of ADDR can be a variable or array element of any type; in the case of a reference variable parameter, ADDR returns the address of the variable to which it refers, not the address of the reference variable itself. Given a variable, it is thus possible to find its address. Conversely, given an address, it is possible to construct a variable of any type, subject to certain restrictions detailed below. This is achieved by the use of the standard mapping functions. For each arithmetic and string type there is an associated standard map whose name is the same as that of the type; thus BYTE INTEGER, LONG REAL, STRING, etc. A standard map has a single parameter of type %integer, which is an address; it returns a reference to a variable of the appropriate type, located at that address. There are also two special standard maps, ARRAY and RECORD. They are described later in this Section. Example: %integer I, J %byte %integer %array B(0:3) : I = M'ABCD' : B(J) = BYTE INTEGER(ADDR(I)+J) %for J = 0,1,3 :
In this example the integer I has been unpacked into its four component byte integers (implementation using 32-bit integers assumed). Example: %byte %integer %array IN(0:80) %string(*)%name LINE : LINE == STRING(ADDR(IN(0))) : From this point onward the array can be referenced either as an array of bytes or as the string LINE. Obviously the length byte of the string, normally the first byte of the string location, will have to be set to an appropriate value.
There are a number of points to note about the use of the standard mapping functions: * They are efficient. * An address error will occur if the address passed to a standard mapping function is not aligned correctly with respect to the type of reference implied. On byte addressed machines, for example: SHORT INTEGER requires the address to be divisible by 2 INTEGER (if 32-bit integers used) requires the address to be divisible by 4 REAL requires the address to be divisible by 4 The maps for the longer arithmetic types may have corresponding requirements. * In Section 3 it was explained that variables declared in a program are allocated storage on the stack. However, if the computer system being used enables an IMP80 program to obtain store addresses of items not on the stack (e.g. items within the operating system, or a connected file in a virtual memory system), then the standard mapping functions can be used to refer to them as variables. This is an extremely powerful facility. * It is possible to cause a location in which, for example, a real constant is stored to be treated as an integer or a string, etc. While this facility may be useful on occasions it should be used with care, because: 1) misuse can lead to errors which are hard to diagnose; 2) it requires a knowledge of the precise format of data types and is necessarily implementation dependent; 3) the purpose of providing types in IMP80 is to enable checks to be made that dissimilar items of data are not being erroneously combined. Use of the standard mapping functions merely to avoid such checks is bad programming practice.

ARRAY

KEY {EMAS IMP80: There is only one standard mapping function, ARRAY, for all the array types. It takes two parameters: an address, and an array format to specify the type of the array being referenced. %array %format statements are analogous to %record %format statements in that they enable an identifier to be associated with a type and a structure. They can then be referenced by name in subsequent statements. ARRAY can only be used to assign to a reference variable of the appropriate array type. Example: %integer %array AONE(1:10 000) %integer %array %name ATWO %integer %array %format AFORM(1:100, 1:100) : ATWO == ARRAY(ADDR(AONE(1)), AFORM) : ATWO(27, 43) = 928 :
The %array %format statement is used to describe the characteristics of the array ATWO - i.e. the number of dimensions and bounds for each dimension. As an alternative to using an array format for the second parameter, the name of another array (of the appropriate type) can be used, if one with suitable characteristics has been declared and is in scope. In order to map record arrays, the second parameter of ARRAY must either be an existing record array variable, or a record array format. A record array format must specify both the dimensions of the array and the format of each (record) element of the array: %record (<record format>) %array %format <name> (<array dims>)
Example: This example is of a record array format being used in an IMP80 program running under the operating system EMAS. The integer function SMADDR returns the address of a file connected in the virtual memory of the user process. %routine PAYCHECK(%integer CHANNEL, RECNO) %integer I, J, K, START, LENGTH %string(11) NAME %external %integer %fn %spec SMADDR(%integer CHANNEL, %integername L) %record %format PAYF (%string(11) SURNAME, %integer AGE, SEX, YEAR, %integerarray SALARY (1:12)) ! Each record thus formatted contains 72 bytes. %record(PAYF)%array %format PAYAF(1:RECNO) %record(PAYF)%array %name PAY : ! Assume that a file was associated with channel CHANNEL, ! prior to the execution of the routine.
START = SMADDR(CHANNEL,LENGTH); ! File now connected. %if LENGTH < 72*RECNO %start PRINTSTRING("File too small:") WRITE(LENGTH,1); NEWLINE PRINTSTRING("Must be at least") WRITE(72*RECNO,1); NEWLINE %return %finish PAY == ARRAY(START,PAYAF) ! Now record array name PAY has been mapped onto the file. ! Note that START was set by the SMADDR call. : NAME=PAY(I)_SURNAME %if PAY(I)_SALARY(J)>350 .... : PAY(K)_YEAR=1978 : %end; ! Of %routine PAYCHECK. }

RECORD

KEY The main use of the standard map RECORD is to assign a reference to a record reference variable. The format of the record in question is that of the reference variable on the left-hand side. Example: %record %format F(%integer A, B, C, %real D, %string(11) E) %record (F) R %record (F) %array RA(1:50) %record (F) %name N1, N2, N3 %integer ADDRESS : ADDRESS = address of some store location : N1 == R;! Record name N1 now synonymous with record R. N2 == RA(20);! Record name N1 now synonymous with element 20 ! of record array RA. N3 == RECORD(ADDRESS);! Record name N3 now synonymous with a ! record of format F located at ! address ADDRESS. :
Example: %integer J %integer %array II(1:100) %record %format A(%byte %integer I, J, K, L) %record %format B(%half %integer P, Q) ! Implementation-dependent type %record(A)%name X %record(B)%name Y : X == RECORD(ADDR(II(J))) Y == RECORD(ADDR(II(J))) : Now, for example: X_I is a reference to the leftmost byte of II(J) Y_P is a reference to the leftmost half-word of II(J)
This example could have used an alternative record format: %integer J %integer %array II(1:100) %record %format A(%byte %integer I, J, K, L %or %c %half %integer P, Q) ! Implementation-dependent type %record(A)%name X : X == RECORD(ADDR(II(J))) : Now, for example: X_I is a reference to the leftmost byte of II(J) X_P is a reference to the leftmost half-word of II(J)
{EMAS IMP80: RECORD returns a reference to a record whose format is unspecified but "very large". This is acceptable when assigning a reference to a reference variable, as described above, since in EMAS IMP80 the format of the reference on the right-hand side of such an assignment statement does not have to match that of the reference variable on the left-hand side. However, a record variable cannot be assigned by a statement of the form RECVAR = RECORD(ADDRESS) since the sizes of the records on each side are not equal. On the other hand, the form RECVAR <- RECORD(ADDRESS) is permitted; as many bytes as RECVAR can hold (determined by its format) will be transferred.}
{IMP77: the standard map RECORD is of the form %record(*)%map RECORD(%integer AD) i.e. the reference returned by RECORD may be to a record of any format; the actual format is determined by the context. Thus RECORD can be used to assign to a complete record. Example: %record %format AF(%integer %array X(1:20), %string(10) TITLE) %record(AF) A %integer AD : : A = RECORD(AD) {The record format used is that of A, i.e. AF} :
Note that statements of the form RECORD(I) = RECORD(J) are not allowed, since no format is indicated from the context.} {EMAS IMP80: programmer-written record maps are not available.}