CHAPTER 9


				ASSOCIATIVE MEMORY



	  This chapter explores memory and its use.  Pointers and data sharing
	  are introduced.	 We discover a very efficient representation for
	  holding enormously many phrases.	Chapter 12 presents a parser based
	  on this representation.  Chapter 15 applies this efficient
	  representation to hold many ~meanings.

	  The next chapter considers what ~modification of memory can mean.



9.1
Memory

	  Human memory is associative.  Given a stimulus, like a certain smell,
	  many associations arise, like the details of a certain visit to a
	  relative's home.  The past manifests itself in the present by turning
	partial information into more complete information.  The more complete
	information came along with the partial information in the past, when
	the association was established.


9.2
Random Access Memory

	Random access memory (RAM) associates addresses and data.  The
	~address of a bin is associated with the ~contents of that bin.  RAM
	plays the role of turning addresses into data.  This association
	between addresses and contents is implemented by a RAM in only one of
	two directions:  Address can be turned into contents, but contents
	can't be turned into addresses.

	  There are memories that turn contents back into addresses.  This kind
	  of memory is called ~content ~addressable ~memory (CAM).	The
	  implementation of a CAM requires more of each bin than merely the
	  storage of data.  Each bin must also have a comparator, so that it can
	  compare its contents against the desired contents given to the CAM.
	  Thus, CAM is substantially more expensive than RAM.

	  Another difference between CAM and RAM is that a CAM may deliver
	  several addresses corresponding to the desired contents, because
	  several bins might contain the same contents.	 RAM, in contrast,
	  always delivers one contents when given one address.


9.3
Association By Address Proximity

	A RAM certainly associates each bin's address with its contents.
	However, there is another kind of association built on top of the RAM's
	address/contents associations.  Two adjacent bins, bins whose addresses
	differ by one, are associated by proximity.


        pic

	For example, we can associate a person's name, age, and sex in three
	consecutive bins in memory, as shown in figure 9.1.  These data are
	kept associated together by their adjacent addresses.

	Even if given only the address of the first bin, you can still get at
	the three pieces of information.  The addresses of the second and third
	bins are computed by adding 1 and 2 respectively to ghe given address
	of the first bin.  This three-bin piece of memory is called a ~block
	of memory.


9.4
Association By Pointer

	Two blocks of memory can be related even if they reside far from one
	another.  One block can contain in one of its bins the ~address of
	the other block.

        pic

	Figure 9.2 shows the block at address 93176, whose second bin points to
	the bin at address 12345.  Figure 9.2(b) shows that all we need to do
	to represent that pointer is to put the address of where the arrow
	ends into the bin from where the pointer starts.  Figure 9.2(c)
	illustrates what a pointer means in general.

	One of the remarkable things about a pointer is that it consumes only
	one bin of memory.  The bin from where the pointer emanates
	can be arbitrarily far from where the pointer culminates.  No matter
	how far apart the start is from the end, RAM guarantees we can get from
	the start to the end as fast as we can get from anywhere to anywhere
	else.  Long pointers cost no more than short pointers.

	(In microchip designs, such a connection between two points on the
	microchip costs much.  Between the start and finish, silicon area is
	quickly consumed as a wire must exist all the way from the start to the
	finish.  Long wires consume more area than short wires.  In microchips,
	wires can't cross freely lest they short together.

        pic

        Pointers can cross freely (as in figure 9.3) because no substance is
        required between the start and end of any pointer.


	  (The low cost of pointers in computer
	  memory support much more complex software systems than can exist on a
	  microchip.  Of course, the microchip can execute faster than software
	  can because all wires can be traversed ~simultaneously, whereas in
	  software, only one pointer can be followed at a time).

	  Figure 9.3 shows another memory block.	This block, labelled "family",
	  holds four pointers.	As with any block, those four pointers are
	  associated together by proximity of the bins' addresses.  From the
	point of view of that family block, it is easy to get to any family
	member (to each person block).

	The person and family blocks would be good representations for the
	datatypes PERSON and FAMILY as declared in ICL:

		TYPE	PERSON =  [ NAME: TEXT
				    AGE:  INT
				    SEX:  BOOL ] ;

		TYPE	FAMILY =  [ FATHER: PERSON
				    MOTHER: PERSON
				    CHILD1: PERSON
				    CHILD2: PERSON ] ;


	   BOX:	How might you define FAMILY so as to have any number of
	   BOX:	children?


9.5
Walking A Pointer In Machine Language - Indexing

	Our presentation of machine language in chapter 6 omitted one
	particularly useful feature.  All instructions' operands were specified
	  as addresses, addresses that were bolted into the machine instructions.
	  If the address of an intruction's operand needed to be variable, e.g.,
	computed, we were out of luck.

	In general, the second operand, the memory operand, can be specified
	with the old notation:

		333

	or with the augmented notation:

		333(1)

	Parentheses enclosing a register address can now be appended to the
	otherwise constant 333.  The address expression

		333(1)

	denotes the address computed by taking the contents of register 1 and
	adding 333 to it.  For example, if register 1 now contains the number
	222, then

		333(1)

	denotes the address 555.  This address expression can occur in any
	instruction, such as

		LOAD  2 , 333(1)

	This loads into register 2 the contents of bin 555.  If we were to
	execute this instruction at another time, register 1 might have a
	different value, and hence this one instruction could work on different
	addresses at different times.

	The register used in an address expression is called the ~index
	~register.

	For another example, let's assume that register 1 contains the address
	  of the block shown in figure 9.1.	 The following address expressions
	  denote the three bins shown:

		    0(1)	is the bin containing	John
		    1(1)	is the bin containing	35
		    2(1)	is the bin containing	Male.

	  Remember that the register appears inside the parentheses, and the
	  displacement (what adds to the register's contents) appears outside
	the parentheses.

	Suppose we have in register 1 the address of the "family" block shown
	in figure 9.3.  How do we get to the father of the family?  That is,
	how do we get to the person block containing John,35,Male?  The
	following program puts into register 2 the address of that person
	block:

		LOAD  2 , 0(1)

	The first word of the family block is loaded into register 2.  Register
	2 thus has the address of the person block.

	Suppose again that register 1 has the address of the family block.
	How do we get the mother's age?  The following puts the mother's age
	into register 2:

		LOAD  2 , 1(1)	   "Make reg 2 point to mother's person block"
		    LOAD  2 , 1(2)     "Get the age from that person block"

	  We've traversed two pointers.  Starting in register 1 is a pointer to
	  the family block.  The first instruction traverses that pointer and
	  leaves in register 2 the contents of the family block's second word.
	  That second word itself is a pointer.  The second instruction
	  traverses that pointer, the pointer to a person block, and ultimately
	  loads into register 2 the second word in that block, the mother's age.

	  Suppose we want to discover the difference in age between a family's
	  father and mother.  Again, if register 1 points to the family block,
	  the following program puts the age difference in register 3:

		    LOAD  2 , 0(1)     "2 gets a pointer to the father"
		    LOAD  3 , 1(2)     "3 gets the father's age"

		LOAD  2 , 1(1)	   "2 gets a pointer to the mother"
		SUB   3 , 1(2)	   "father's age minus mother's age"

	Traversing a pointer is very fast.  It takes only one instruction to
	do it.

	We need to include indexing into the machine language shown in Chapter
	6.  We used to say that

		LOAD  1 , 333

	is represented by the number

		102 1 333

	Let's now say that it is represented by

		    102 1 0 333

	  We've introduced a new field into the machine language instruction,
	a field that holds the index register's address.  That field resides
	  to the left of the constant address part (the displacement).
	  We've put a zero in there to indicate that no indexing is desired.

	The instruction

		LOAD  1 , 333(2)

	is represented by

		102 1 2 333

	We put a 2 into the index register field.

	Section 9.8 augments our embedded assembly language to include
	indexing.

	  pic

		BOX:	How many LOAD instructions would it take to traverse
		BOX:	a list of length 3, as shown in figure 9.14?
		BOX:
		BOX:	Write an assembly language ~loop that sets register
		BOX:	2 to the second bin of each block.
		BOX:
		BOX:	Do you recall the ICL notation for visiting each
		BOX:	element in a list?  (See Section 22.3.1).


9.6
Pointers Consume Memory

	If you look at figure 9.3, you will see that we've consumed 16 bins, 3
	bins in each of four person blocks, and 4 more bins for the family
	block.

        pic

        Figure 9.4 shows an equivalent representation that consumes
	only 12 bins total.  We've removed all pointers, and thus saved the
	four bins that held pointers.

	By replacing a pointer with what it points to, we save the space
	taken by the pointer.  Are pointers ever cost effective?


9.7
Sharing

        pic

	Consider figure 9.5.  We've added a new block besides the family block,
	the social club block.  A club block, like a family block, references
	its members by pointing to them.	Notice that the person blocks for
	Mary and Jill are referenced twice.

        pic

	Figure 9.6 shows the situation with two clubs.
	Many person blocks are referenced from more than one point of view.


	  When a block of memory is referenced more than once, we say that it
	  is ~shared.  When some blocks are shared, the use of pointers is more
	  efficient than an equivalent rendition without pointers.

        pic

	  Figure 9.7 shows the memory consumed for a non-pointer rendition of
	  figure 9.6.  The non-pointer rendition occupies 36 bins, and the
	  pointer rendition consumes only 24 bins.  We've duplicated the data in
	the person blocks.  For example, Mary's name, age, and sex appears
	  three times, once within each of the family and club blocks.  These
	  multiple appearances of the same data wind up consuming more memory
	  than the rendition with pointers.

	  With pointers, each reference to a block can be represented using only
	  one bin, no matter how big the referenced block is.	 Also, each
	  additional reference to a block consumes only one additional bin.
	  With pointers, you can use only one bin to represent an arbitrarily
	  large amount of information.

	  The value of sharing can be seen within the scientific literature.
	  One measure of a scientific article's value is the number of other
	articles that reference it.  The value rises as the sharing increases.

	When a block has N pointers to it, the memory consumed by that block
	is shared N ways.  Each of the N points of view effectively pays
	for approximately 1/N of the memory, instead of 1 (for one copy of the
	block).  If every block is shared N ways, then the implementation using
	pointers costs approximately 1/N the memory used by an implementation
	without pointers.

	However, much more dramatic savings can occur.


9.7.1
Nested Sharing - Exponential Representation In Only Polynomial Memory

        pic

	Figure 9.8 shows a structure that has nested sharing.  The leftmost
	block, block #3, points to a 0 and a 1 block on its right.  Those two
	blocks share block #2 on their right.  Further to the right block #1
	is shared.

	Starting from the left, you can travel along pointers and travel thru
	two shared blocks, blocks #2 and #1.  You encounter sharing within
	sharing.

	Under each of blocks #1, #2, and #3, we have noted how many trips are
	possible starting from that block.  From block #3, there are eight
	possible trips.  For each trip, if we write down the "0"s and "1"s
	we pass, we will find eight different strings composed of 0's and 1's.
	Each trip, from block #3, represents a binary number of length 3.

	If we were to extend figure 9.8 from 3 to N stages, there would be
	2-to-the-Nth possible trips.  Each possible trip corresponds to a
	binary number of length N.  When sharing occurs within sharing, as it
	does here, a linear amount of memory, 6*N bins worth, represents
	exponentially many, 2-to-the-Nth, phrases.

	Let's begin to replace pointers by copies of what they point to, so
	as to remove all sharing from within figure 9.8 (a copy of which
	appears as figure 9.9(a)).

        pic

	Figure 9.9(b) shows what happens when we replace pointers to
	block #1 by pointers to unshared copies of block #1. The far
	right 0 and 1 blocks have now become shared. To get rid of
	that sharing as well, figure 9.9(c) shows the result. 


	Figure 9.9(d) shows the result after unsharing block #2, with no
	sharing remaining.  Notice how we've copied the block #2 and everything
	to its right.  That copying was necessary so as to have no data
	shared.  The original 3-stage (N) shared representation has expanded
	to something with 8 (2^N) blocks at the far right.

	Chapter 12 will present a parser that actually explores all possible
	rewrites (introduced first in Chapter 1).  In that process, we will be
	producing and examining an exponential number of distinct phrases, like
	these, and yet we will do that consuming only polynomial amounts of
	memory and processing time.

	As an introduction to parsing, let's consider a possible representation
	  of phrases, as declared in ICL via:

		    TYPE	PHRASE =  [ CHAR:	 CHAR
						RIGHT: PHRASE ] ;

	  This says that a PHRASE is just a CHARacter followed by another PHRASE
	  to the right.  The phrase "2" is denoted as follows:

		    [CHAR: '2']

	  There is nothing to the RIGHT in this short phrase.	 The phrase 12 is
	  represented as a PHRASE as follows:

		    [CHAR: '1'  RIGHT: [CHAR: '2']	]

	  12 is the character '1' followed by the phrase '2'.	 Here is a
	  rendition for 123:

		    [CHAR: '1'  RIGHT: [CHAR: '2'  RIGHT: [CHAR: '3'] ] ]

        pic

	  Figure 9.10 shows the memory representation for it.

	  Recall the first rule in the "Numbers" grammar (Section 1.1.1):

		    0		->	  <DIGIT>

	  This rule gives us the option to rewrite the character '0' into a
	  <DIGIT>.

        pic

	  We can represent both possibilities at once, both the before
	  and the after, as shown in figure 9.11(a).


	  Figure 9.11(b) shows a '0' appended to the left.
	  This represents the two phrases:

		    0		0
		    0		<DIGIT>

	  Figure 9.11(c) shows the result of applying our grammar rule this time
	  to the new '0'.	 Figure 9.11(d) shows the ~choice of the phrases
	  starting with '0' or <DIGIT>.

	  To accomodate this multiplicity of sub-phrases, let's change the RIGHT
	field to be a ~set of phrases instead of just a single phrase:

		TYPE	PHRASE =  [ CHAR: CHAR  RIGHT: { PHRASE } ] ;

	An equivalent declaration that may be easier to think about follows:

		TYPE	PHRASE =  [ CHAR: CHAR  RIGHT: CHOICE_OF_PHRASES ] ;

		TYPE	CHOICE_OF_PHRASES =  { PHRASE }  ;

	In figures 9.11 and 9.8, the rectangular blocks each represents a
	CHOICE_OF_PHRASES, and each rounded block represents a PHRASE.

	This parsing example shows how nested sharing may be encountered.
	Each step that we take to the RIGHT poses a multiplicity of phrases.
	Many of those phrases share common sub-phrases even further to the
	right.

	The sharing arises when we append a character (e.g., '0') to the left
	of an existing CHOICE_OF_PHRASES.  The grammar tries rewriting the '0'
	into <DIGIT>.  That new digit PHRASE comes to share with the '0'
	phrase the same CHOICE_OF_PHRASES to the right.

	When those two phrases beginning with 0 and <DIGIT> are packaged
	together as a CHOICE_OF_PHRASES, the possibilities explode, as that
	CHOICE_OF_PHRASES may itself become shared from the left as we
	append another 0 character to the left, and so on.


		BOX:	How many distinct phrases can you represent with 36
		BOX:	blocks, like figure 9.8?



9.7.2
Each Appearance Of A Variable Shares Its Point Of View

	How do we specify the data structure shown in figure 9.11(d)?  Let's
	  start by specifying figure 9.11(a).  First, we build the rightmost
	  blocks, the '0' and <DIGIT> blocks, via:

		    PHRASE_0 :=  [ CHAR: '0' ] ;
		    PHRASE_D :=  [ CHAR: <DIGIT> ] ;

	  We then form the CHOICE_OF_PHRASES on the left via:

		    CHOICES_1 :=	{ PHRASE_0 ;
					  PHRASE_D	 }  ;

	  We could specify this all at once more simply via:

		    CHOICES_1 :=	{ [CHAR: '0'     ] ;
					  [CHAR: <DIGIT> ]  } ;

	  CHOICES_1 represents figure 9.11(a).

	  We go on to build figure 9.11(d) by repeating this process once more.
	  We first make figure 9.11(c) via:

		    PHRASE_0 :=  [ CHAR: '0'	   RIGHT: CHOICES_1 ] ;
		    PHRASE_D :=  [ CHAR: <DIGIT> RIGHT: CHOICES_1 ] ;

	  Notice that there are ~two appearances of the variable CHOICES_1.
	  PHRASE_0 and PHRASE_D reference structures that share a common RIGHT
	  field.  We achieve figure 9.11(d) via:

		    CHOICES_2 :=	{ PHRASE_0 ;
					  PHRASE_D	 } ;

	  Alternatively, we could assign into CHOICES_2 directly from CHOICES_1
	  via:

		    CHOICES_2 :=	{ [CHAR: '0'     RIGHT: CHOICES_1 ] ;
					  [CHAR: <DIGIT> RIGHT: CHOICES_1 ]	  } ;

	  This latter specification exposes most clearly that CHOICES_2 shares
	  CHOICES_1 twice.  The variable CHOICES_1 appears twice.

	  Sharing is introduced into data structures by writing expressions that
	  reference the same variable more than once.  The data structure
	  referenced by that variable will be shared within the new, bigger
	  data structure being built now.  For example, the new data structure
	  that will be referenced by CHOICES_2 shares within itself the data
	  structure referenced by CHOICES_1.

	  Figure 9.9(a) is specified via:

		    BLOCK_1 :=  {	 [CHAR: '0'] ;
					 [CHAR: '1']   } ;

		    BLOCK_2 :=  {	 [CHAR: '0'	 RIGHT: BLOCK_1 ] ;
					 [CHAR: '1'	 RIGHT: BLOCK_1 ]	  } ;

		    BLOCK_3 :=  {	 [CHAR: '0'	 RIGHT: BLOCK_2 ] ;
					 [CHAR: '1'	 RIGHT: BLOCK_2 ]	  } ;

	  BLOCK_2 is built with two appearances of BLOCK_1, and similarly BLOCK_3
	  is built with two appearances of BLOCK_2.  To get N stages of sharing,
	  we can write:

		    BLOCK:= NIL ;

		    REPEAT N;
			 DO	BLOCK:=  {	[CHAR: '0'	RIGHT: BLOCK ]  ;
						[CHAR: '1'	RIGHT: BLOCK ]	 } ;
			 END

	  Each iteration sets BLOCK so as to reference a data structure that
	  references twice what BLOCK used to point to.

	  If sharing were disallowed, then we would deduce that:

		    BLOCK_2	  is greater than twice the size of	 BLOCK_1

	  because BLOCK_2 contains two (unshared) copies of BLOCK_1.  Similarly:

		    BLOCK_3	  is greater than twice the size of	 BLOCK_2

	  because BLOCK_3 contains two copies of BLOCK_2.  Also, in the loop
	  just shown, each iteration would double the size of BLOCK, since
	  without sharing, the resulting BLOCK holds twice the old BLOCK.


		    BOX:	If a block points to itself, how many possible
		    BOX:	trips are there?


9.7.3
Cycles

	Let's reconsider the PERSON and FAMILY data structures.  Suppose we
	have each person block contain a pointer back to the family in which
	the person is a child.

        pic

	Figure 9.12(a) shows this.


	Here are the updated datatype declarations for PERSON and FAMILY:

		TYPE	PERSON =  [ NAME:      TEXT
				    AGE:       INT
				    SEX:       BOOL
				    CHILD_IN:  FAMILY ] ;

		TYPE	FAMILY =  [ FATHER, MOTHER, CHILD1, CHILD2 : PERSON ] ;

	PERSON now references a FAMILY (in the CHILD_IN field).  Notice that
	you can start at a PERSON, travel to the FAMILY in which that person
	is a child, and then travel back from that family to the same PERSON.
	It is possible to take that round trip arbitrarily many times.

	We say that a set of blocks contains a cycle if there is at least
	one possible round trip, taken by following pointers.  The blocks
	that you pass on the round trip are said to be members of that cycle.
	Data structures with cycles represent infinitely many possible trips.

	Figure 9.12(b) shows our database when pointers to
	families are replaced by copies of the families.  We now have combined
	person/family blocks, each consuming 7 bins of memory.  There are still
	pointers however.  The family sub-blocks point to other person/family
	blocks.

	To get rid of these remaining pointers, we would replace each pointer
	to a person/family block by a copy of that person/family block.

	Unfortunately, our present 7-bin person/family blocks presume that each
	family sub-block consumes only 4 bins.  But now, we are proposing that
	each of those four bins be replaced by a 7-bin person/family block.
	That would raise the size of a family sub-block to 28 bins.

	The datatype declarations just shown show that a PERSON references a
	FAMILY, and that a FAMILY references PERSON blocks.  Let's try to
	  determine the size of a PERSON block and of a FAMILY block when we
	  forbid the use of pointers.	 We know that a PERSON contains a FAMILY,
	  and so the size of PERSON is strictly greater than the size of a
	  FAMILY.  On the other hand, a FAMILY contains at least two PERSONs,
	  and so a FAMILY has to be bigger than a PERSON.

	  How can a FAMILY be larger than a PERSON and vice versa?	This
	  contradiction shows that we can't ever remove all pointers from this
	structure.  By making at least one of PERSON and FAMILY be a pointer,
	we reduce its size to one bin.  Once one of PERSON and FAMILY is
	reduced to a pointer, the other will have finite size.

	Figure 9.12(b) shows the situation if we make PERSON be a pointer (and
	not FAMILY).  Figure 9.12(a) shows the even more efficient solution
	that arises when we make both pointers.

	Cycles allow for the representation of infinitely many trips
	taken by following pointers.  We will encounter cycles in our
	representation of semantics in chapter 28, to represent the infinitely
	many ways to apply the following rules:

		int	->	real

		real	->	int

	These rules say that an INTeger can be turned into a REAL with one
	application of the first rule, or with that plus arbitrarily many
	applications of the pair of rules.

	While sharing provides for representing exponentially many possible
	trips in only polynomial memory, cycles provide a finite representation
	of infinitely many possible trips.


9.7.3.1
At Least One Block In A Cycle Is A Shared Block

        pic

	Figure 9.13(a) shows a cycle of blocks.  From the outside where we
	stand, there must be a pointer to one of those blocks.  If we can't
	  get to any block to start with, then those blocks are visible
	  to no one, and hence they could just as well not exist!  The cycle
	  of blocks would drop out of our universe altogether, like a black
	  hole, via a background process known as garbage collection (Part 8).

	  Figure 9.13(b) shows that if those blocks are visible at all, then
	  there is another entry into the cycle.	Thus at least one block in an
	  accessible cycle has two pointers pointing to it, one pointer from the
	  cycle and the other pointer from outside the cycle.	 A cycle
	  contains at least one shared block.

	  The phenomenon of cycles can be seen as a special case of sharing.
	  You can devise algorithms which travel along pointers.  You can even
	  devise algorithms which visit each block exactly once.  You avoid
	  visiting a block more than once by marking each block you arrive at
	  when you arrive at it.  If you encounter a block that is marked, it
	  means you've already been there before.  By retreating at marked
	blocks, you will succeed in travel thru each block exactly once.

	Just by guarding against travelling thru shared blocks more than once,
	you avoid traveling thru a cycle more than once.  You always enter a
	cycle at a shared block (figure 9.13).  After you travel the cycle,
	you reencounter that shared block.  But since you avoid traveling any
	shared block more than once, a second trip around the cycle is avoided.


		BOX:	What can you say about a cycle that has no shared
		BOX:	blocks?

9.7.3.2
Exercises

   1)	How can the use of pointers drastically reduce memory consumption?

   2)	Figure 9.8 shows eight phrases represented by 9 blocks, as three
	stages.  How many blocks are required to represent 1024 phrases?

   3)	How many blocks are required to represent 3-to-the-tenth phrases,
	given the characters 0, 1, and 2?

   4)	Given ~only the rules:

		<DIGIT>	   ->    <NUMBER>

		<NUMBER>   ->    <ATOM>

	and given the phrase:

		<DIGIT> <DIGIT> <DIGIT> <DIGIT> <DIGIT>

	How many possible phrases, starting from the far left and ending at the
	far right, can be produced by applying all possible rewrites at all
	possible places upon this phrase?  How few blocks are required to
	represent all those phrases, like in figure 9.8 or 9.11?

   5)	Draw a picture like figure 9.8 or 9.11 to represent concisely the two
	phrases that exist when the rule:

		<ATOM>  +  <ATOM>	->	<ATOM>

	is applied to the phrase

		HELLO  <ATOM>  +  <ATOM>  GOODBYE

	Notice that different PHRASEs in the same CHOICE_OF_PHRASES can have
	distinct RIGHT fields.

   6)	Write an assembly language program that maps figure 9.14(a) to (b).
	Feel free to use register 3.  This is all done with LOADs and STOREs.
	This inserts a new element into a list.

   7)	Write an assembly language program to delete the second element in the
	list referenced by register 1, so as to produce figure 9.14(c).


9.8
Updating The Embedded Assembler To Include Indexing

	The following assembly language specification:

		LOAD  2 , 333(1)

	is written in embedded assembly language as:

		LOAD( 2 , 333 \OFF_OF 1 );

	That is, the second operand of any instruction (the memory operand)
	can use the notation

		displacement  \OFF_OF  register


	OFF_OF is a new function.  We have called it using ICL's
	  ~infix notation.  Any function having exactly two parameters
	  can be called either the standard way:

		    OFF_OF( displacement , register )

	  or the infix way:

		    displacement	\OFF_OF  register

	  By putting a backslash ("\") in front of a function name, it becomes
	  an infix operator, like "+" (Section 22.1.3).


9.8.1
The New Type MACHINE_ADDRESS_EXPRESSION

	  The result of OFF_OF, and in fact the second operand in all
	  instructions, all need to be of the new type declared as follows:

		    TYPE   MACHINE_ADDRESS_EXPRESSION =

						    [ DISPLACEMENT:  ADDRESS_EXPRESSION
							INDEX_REGISTER:  INT		    ] ;

	  For brevity, we will use the short name MAE to denote this new type
	  MACHINE_ADDRESS_EXPRESSION, and we will use the short AE to denote
	  our original ADDRESS_EXPRESSION.

	  A MAE is basically an AE, which appears as the DISPLACEMENT field.
	  Now an index register can appear as well.  An INDEX_REGISTER of zero
	  will mean "no indexing".  The funtion OFF_OF produces a MAE, given
	  an AE and a register.

	  To update our embedded assembler, we must make each instruction take in
	  a MAE instead of an AE.  The burden of this change will be placed
	  on the function that all instructions call, PUT_INSTRUCTION.
	  PUT_INSTRUCTION will now take in a MAE, and assemble our new 4-field
	  instructions (instead of the old 3-field instruction encoding).

	  Once we've upgraded our functions, all of our program specifications
	will cease to be legal.  Where we wrote:

		LOAD(1,333);	or	LOAD(1,ONE);

	we must now supply a MAE instead of the AEs 333 and ONE.
	All instructions now require a MAE.

	We believe our older specifications still make sense.  We know what
	we mean when we supply merely an AE.  We want that to be interpretted
	as a MAE whose INDEX_REGISTER field is zero.


9.8.2
Another Coercion

	We provide this implicit transformaion in ICL by declaring:

		LET  AE  BECOME  MAE  BY

			[ DISPLACEMENT: AE   INDEX_REGISTER: 0 ]

		ENDDEFN

	This says that an AE can be seen also as a MAE, whose register
	field is zero.  This coercion will allow us to keep our old program
	specifications.

	Thus:
		LOAD( 1 , 333 );

	will continue to be meaningful, because the 333 (an AE) will be
	converted into a MAE.  What we've just written will
	  automatically be equivalent to:

		    LOAD( 1 , 333 \OFF_OF 0 );

	  We actually define the \OFF_OF operation via:

		    DEFINE	OFF_OF( X: AE  REG: INT )  =	MAE:

				   [ DISPLACEMENT: X  INDEX_REGISTER: REG ]

		    ENDDEFN