From 215baa49b3a7ca310364ac5227ac7bbbf1efb38c Mon Sep 17 00:00:00 2001
From: ram <ram>
Date: Fri, 19 Apr 1991 13:12:29 +0000
Subject: [PATCH] Initial revision

---
 docs/internals/internal-design.txt | 677 +++++++++++++++++++++++++++++
 1 file changed, 677 insertions(+)
 create mode 100644 docs/internals/internal-design.txt

diff --git a/docs/internals/internal-design.txt b/docs/internals/internal-design.txt
new file mode 100644
index 000000000..a27693138
--- /dev/null
+++ b/docs/internals/internal-design.txt
@@ -0,0 +1,677 @@
+
+
+;;;; Terminology.
+
+OBJECT
+   An object is one of the following:
+      a single word containing immediate data (characters, fixnums, etc)
+      a single word pointing to an object     (structures, conses, etc.)
+   These are tagged with three low-tag bits as described in the section
+   "Tagging".  This is synonymous with DESCRIPTOR.
+
+LISP OBJECT
+   A Lisp object is a high-level object discussed as a data type in Common
+   Lisp: The Language.
+
+DATA-BLOCK
+   A data-block is a dual-word aligned block of memory that either manifests a
+   Lisp object (vectors, code, symbols, etc.) or helps manage a Lisp object on
+   the heap (array header, function header, etc.).
+
+DESCRIPTOR
+   A descriptor is a tagged, single-word object.  It either contains immediate
+   data or a pointer to data.  This is synonymous with OBJECT.
+
+WORD
+   A word is a 32-bit quantity.
+
+
+
+;;;; Tagging.
+
+The following is a key of the three bit low-tagging scheme:
+   000 even fixnum
+   001 function pointer
+   010 other-immediate (header-words, characters, symbol-value trap value, etc.)
+   011 list pointer
+   100 odd fixnum
+   101 structure pointer
+   110 unused
+   111 other-pointer to data-blocks (other than conses, structures,
+   				     and functions)
+
+This taging scheme forces a dual-word alignment of data-blocks on the heap, but
+this can be pretty negligible:
+   RATIOS and COMPLEX must have a header-word anyway since they are not a
+      major type.  This wastes one word for these infrequent data-blocks since
+      they require two words for the data.
+   BIGNUMS must have a header-word and probably contain only one other word
+      anyway, so we probably don't waste any words here.  Most bignums just
+      barely overflow fixnums, that is by a bit or two.
+   Single and double FLOATS?
+      no waste
+      one word wasted
+   SYMBOLS are dual-word aligned with the header-word.
+   Everything else is vector-like including code, so these probably take up
+      so many words that one extra one doesn't matter.
+
+
+
+;;;; GC Comments.
+
+Data-Blocks comprise only descriptors, or they contain immediate data and raw
+bits interpreted by the system.  GC must skip the latter when scanning the
+heap, so it does not look at a word of raw bits and interpret it as a pointer
+descriptor.  These data-blocks require headers for GC as well as for operations
+that need to know how to interpret the raw bits.  When GC is scanning, and it
+sees a header-word, then it can determine how to skip that data-block if
+necessary.  Header-Words are tagged as other-immediates.  See the sections
+"Other-Immediates" and "Data-Blocks and Header-Words" for comments on
+distinguishing header-words from other-immediate data.  This distinction is
+necessary since we scan through data-blocks containing only descriptors just as
+we scan through the heap looking for header-words introducing data-blocks.
+
+Data-Blocks containing only descriptors do not require header-words for GC
+since the entire data-block can be scanned by GC a word at a time, taking
+whatever action is necessary or appropriate for the data in that slot.  For
+example, a cons is referenced by a descriptor with a specific tag, and the
+system always knows the size of this data-block.  When GC encounters a pointer
+to a cons, it can transport it into the new space, and when scanning, it can
+simply scan the two words manifesting the cons interpreting each word as a
+descriptor.  Actually there is no cons tag, but a list tag, so we make sure the
+cons is not nil when appropriate.  A header may still be desired if the pointer
+to the data-block does not contain enough information to adequately maintain
+the data-block.  An example of this is a simple-vector containing only
+descriptor slots, and we attach a header-word because the descriptor pointing
+to the vector lacks necessary information -- the type of the vector's elements,
+its length, etc.
+
+There is no need for a major tag for GC forwarding pointers.  Since the tag
+bits are in the low end of the word, a range check on the start and end of old
+space tells you if you need to move the thing.  This is all GC overhead.
+
+
+
+;;;; Structures.
+
+Structures comprise a word for each slot in the definition in addition to one
+word, a type slot which is a pointer descriptor.  This points to a structure
+describing the data-block as a structure, a defstruct-descriptor object.  When
+operating on a structure, doing a structure test can be done by simply checking
+the tag bits on the pointer descriptor referencing it.  As described in section
+"GC Comments", data-blocks such as those representing structures may avoid
+having a header-word since they are GC-scanable without any problem.  This
+saves two words for every structure instance.
+
+
+
+;;;; Fixnums.
+
+A fixnum has one of the following formats in 32 bits:
+    -------------------------------------------------------
+    |        30 bit 2's complement even integer   | 0 0 0 |
+    -------------------------------------------------------
+or
+    -------------------------------------------------------
+    |        30 bit 2's complement odd integer    | 1 0 0 |
+    -------------------------------------------------------
+
+Effectively, there is one tag for immediate integers, two zeros.  This buys one
+more bit for fixnums, and now when these numbers index into simple-vectors or
+offset into memory, they point to word boundaries on 32-bit, byte-addressable
+machines.  That is, no shifting need occur to use the number directly as an
+offset.
+
+This format has another advantage on byte-addressable machines when fixnums are
+offsets into vector-like data-blocks, including structures.  Even though we
+previously mentioned data-blocks are dual-word aligned, most indexing and slot
+accessing is word aligned, and so are fixnums with effectively two tag bits.
+
+Two tags also allow better usage of special instructions on some machines that
+can deal with two low-tag bits but not three.
+
+Since the two bits are zeros, we avoid having to mask them off before using the
+words for arithmetic, but division and multiplication require special shifting.
+
+
+
+;;;; Other-immediates.
+
+An other-immediate has the following format:
+   ----------------------------------------------------------------
+   |   Data (24 bits)        | Type (8 bits with low-tag) | 0 1 0 |
+   ----------------------------------------------------------------
+
+The system uses eight bits of type when checking types and defining system
+constants.  This allows allows for 32 distinct other-immediate objects given
+the three low-tag bits tied down.
+
+The system uses this format for characters, SYMBOL-VALUE unbound trap value,
+and header-words for data-blocks on the heap.  The type codes are laid out to
+facilitate range checks for common subtypes; for example, all numbers will have
+contiguous type codes which are distinct from the contiguous array type codes.
+See section "Data-Blocks and Other-immediates Typing" for details.
+
+
+
+;;;; Data-Blocks and Header-Word Format.
+
+Pointers to data-blocks have the following format:
+   ----------------------------------------------------------------
+   |      Dual-word address of data-block (29 bits)       | 1 1	1 |
+   ----------------------------------------------------------------
+
+The word pointed to by the above descriptor is a header-word, and it has the
+same format as an other-immediate:
+   ----------------------------------------------------------------
+   |   Data (24 bits)        | Type (8 bits with low-tag) | 0 1 0 |
+   ----------------------------------------------------------------
+
+This is convenient for scanning the heap when GC'ing, but it does mean that
+whenever GC encounters an other-immediate word, it has to do a range check on
+the low byte to see if it is a header-word or just a character (for example).
+This is easily acceptable performance hit for scanning.
+
+The system interprets the data portion of the header-word for non-vector
+data-blocks as the word length excluding the header-word.  For example, the
+data field of the header for ratio and complex numbers is two, one word each
+for the numerator and denominator or for the real and imaginary parts.
+
+For vectors and data-blocks representing Lisp objects stored like vectors, the
+system ignores the data portion of the header-word:
+   ----------------------------------------------------------------
+   | Unused Data (24 bits)   | Type (8 bits with low-tag) | 0 1 0 |
+   ----------------------------------------------------------------
+   |           Element Length of Vector (30 bits)           | 0 0 | 
+   ----------------------------------------------------------------
+
+Using a separate word allows for much larger vectors, and it allows LENGTH to
+simply access a single word without masking or shifting.  Similarly, the header
+for complex arrays and vectors has a second word, following the header-word,
+the system uses for the fill pointer, so computing the length of any array is
+the same code sequence.
+
+
+
+;;;; Data-Blocks and Other-immediates Typing.
+
+These are the other-immediate types.  We specify them including all low eight
+bits, including the other-immediate tag, so we can think of the type bits as
+one type -- not an other-immediate major type and a subtype.  Also, fetching a
+byte and comparing it against a constant is more efficient than wasting even a
+small amount of time shifting out the other-immediate tag to compare against a
+five bit constant.
+
+	   Number   (< 30)
+00000 010      bignum						10
+00000 010      ratio						14
+00000 010      single-float					18
+00000 010      double-float					22
+00000 010      complex						26
+
+	   Array   (>= 30 code 86)
+	      Simple-Array   (>= 20 code 70)
+00000 010	    simple-array				30
+	         Vector  (>= 34 code 82)
+00000 010	    simple-string				34
+00000 010	    simple-bit-vector				38
+00000 010	    simple-vector				42
+00000 010	    (simple-array (unsigned-byte 2) (*))	46
+00000 010	    (simple-array (unsigned-byte 4) (*))	50
+00000 010	    (simple-array (unsigned-byte 8) (*))	54
+00000 010	    (simple-array (unsigned-byte 16) (*))	58
+00000 010	    (simple-array (unsigned-byte 32) (*))	62
+00000 010	    (simple-array single-float (*))		66
+00000 010	    (simple-array double-float (*))		70
+00000 010	 complex-string					74
+00000 010	 complex-bit-vector				78
+00000 010	 (array * (*))   -- general complex vector.	82
+00000 010     complex-array					86
+
+00000 010  code-header-type					90
+00000 010  function-header-type					94
+00000 010  closure-header-type					98
+00000 010  funcallable-instance-header-type			102
+00000 010  unused-function-header-1-type			106
+00000 010  unused-function-header-2-type			110
+00000 010  unused-function-header-3-type			114
+00000 010  closure-function-header-type				118
+00000 010  return-pc-header-type				122
+00000 010  value-cell-header-type				126
+00000 010  symbol-header-type					130
+00000 010  base-character-type					134
+00000 010  system-area-pointer-type (header type)		138
+00000 010  unbound-marker					142
+00000 010  weak-pointer-type					146
+
+
+
+;;;; Strings.
+
+All strings in the system are C-null terminated.  This saves copying the bytes
+when calling out to C.  The only time this wastes memory is when the string
+contains a multiple of eight characters, and then the system allocates two more
+words (since Lisp objects are dual-word aligned) to hold the C-null byte.
+Since the system will make heavy use of C routines for systems calls and
+libraries that save reimplementation of higher level operating system
+functionality (such as pathname resolution or current directory computation),
+saving on copying strings for C should make C call out more efficient.
+
+The length word in a string header, see section "Data-Blocks and Header-Word
+Format", counts only the characters truly in the Common Lisp string.
+Allocation and GC will have to know to handle the extra C-null byte, and GC
+already has to deal with rounding up various objects to dual-word alignment.
+
+
+
+;;;; Symbols and NIL.
+
+Symbol data-block has the following format:
+    -------------------------------------------------------
+    |     5 (data-block words)     | Symbol Type (8 bits) |
+    -------------------------------------------------------
+    |   		Value Descriptor		  |
+    -------------------------------------------------------
+    |			Function Pointer 		  |
+    -------------------------------------------------------
+    |			 Property List			  |
+    -------------------------------------------------------
+    |			   Print Name			  |
+    -------------------------------------------------------
+    |			    Package			  |
+    -------------------------------------------------------
+
+The issues with nil are that we want it to act like a symbol, and we need list
+operations such as CAR and CDR to be fast on it.  CMU Common Lisp solves this
+by putting nil as the first object in static space, where other global values
+reside, so it has a known address in the system:
+    -------------------------------------------------------  <-- start static
+    |				0			  |      space
+    -------------------------------------------------------
+    |     5 (data-block words)     | Symbol Type (8 bits) |
+    -------------------------------------------------------  <-- nil
+    |			    Value/CAR			  |
+    -------------------------------------------------------
+    |			  Definition/CDR		  |
+    -------------------------------------------------------
+    |			  Property List			  |
+    -------------------------------------------------------
+    |			    Print Name			  |
+    -------------------------------------------------------
+    |			     Package			  |
+    -------------------------------------------------------
+    |			       ...			  |
+    -------------------------------------------------------
+In addition, we make the list typed pointer to nil actually point past the
+header word of the nil symbol data-block.  This has usefulness explained below.
+The value and definition of nil are nil.  Therefore, any reference to nil used
+as a list has quick list type checking, and CAR and CDR can go right through
+the first and second words as if nil were a cons object.
+
+When there is a reference to nil used as a symbol, the system adds offsets to
+the address the same as it does for any symbol.  This works due to a
+combination of nil pointing past the symbol header-word and the chosen list and
+other-pointer type tags.  The list type tag is four less than the other-pointer
+type tag, but nil points four additional bytes into its symbol data-block.
+
+
+
+;;;; Array Headers.
+
+The array-header data-block has the following format:
+   ----------------------------------------------------------------
+   | Header Len (24 bits) = Array Rank +5   | Array Type (8 bits) |
+   ----------------------------------------------------------------
+   |               Fill Pointer (30 bits)                   | 0 0 | 
+   ----------------------------------------------------------------
+   |               Available Elements (30 bits)             | 0 0 | 
+   ----------------------------------------------------------------
+   |               Data Vector (29 bits)                  | 1 1 1 | 
+   ----------------------------------------------------------------
+   |               Displacement (30 bits)                   | 0 0 | 
+   ----------------------------------------------------------------
+   |               Displacedp (29 bits) -- t or nil       | 1 1 1 | 
+   ----------------------------------------------------------------
+   |               Range of First Index (30 bits)           | 0 0 | 
+   ----------------------------------------------------------------
+				  .
+				  .
+				  .
+
+The array type in the header-word is one of the eight-bit patterns from section
+"Data-Blocks and Other-immediates Typing", indicating that this is a complex
+string, complex vector, complex bit-vector, or a multi-dimensional array.  The
+data portion of the other-immediate word is the length of the array header
+data-block.  Due to its format, its length is always five greater than the
+array's number of dimensions.  The following words have the following
+interpretations and types:
+   Fill Pointer
+      This is a fixnum indicating the number of elements in the data vector
+      actually in use.  This is the logical length of the array, and it is
+      typically the same value as the next slot.  This is the second word, so
+      LENGTH of any array, with or without an array header, is just four bytes
+      off the pointer to it.
+   Available Elements
+      This is a fixnum indicating the number of elements for which there is
+      space in the data vector.  This is greater than or equal to the logical
+      length of the array when it is a vector having a fill pointer.
+   Data Vector
+      This is a pointer descriptor referencing the actual data of the array.
+      This a data-block whose first word is a header-word with an array type as
+      described in sections "Data-Blocks and Header-Word Format" and
+      "Data-Blocks and Other-immediates Typing"
+   Displacement
+      This is a fixnum added to the computed row-major index for any array.
+      This is typically zero.
+   Displacedp
+      This is either t or nil.  This is separate from the displacement slot, so
+      most array accesses can simply add in the displacement slot.  The rare
+      need to know if an array is displaced costs one extra word in array
+      headers which probably aren't very frequent anyway.
+   Range of First Index
+      This is a fixnum indicating the number of elements in the first dimension
+      of the array.  Legal index values are zero to one less than this number
+      inclusively.  IF the array is zero-dimensional, this slot is
+      non-existent.
+   ... (remaining slots)
+      There is an additional slot in the header for each dimension of the
+      array.  These are the same as the Range of First Index slot.
+
+
+
+;;;; Bignums.
+
+Bignum data-blocks have the following format:
+    -------------------------------------------------------
+    |      Length (24 bits)        | Bignum Type (8 bits) |
+    -------------------------------------------------------
+    |   	      least significant bits		  |
+    -------------------------------------------------------
+				.
+				.
+				.
+
+The elements contain the two's complement representation of the integer with
+the least significant bits in the first element or closer to the header.  The
+sign information is in the high end of the last element.
+
+
+
+
+;;;; Code Data-Blocks.
+
+A code data-block is the run-time representation of a "component".  A component
+is a connected portion of a program's flow graph that is compiled as a single
+unit, and it contains code for many functions.  Some of these functions are
+callable from outside of the component, and these are termed "entry points".
+
+Each entry point has an associated user-visible function data-block (of type
+FUNCTION).  The full call convention provides for calling an entry point
+specified by a function object.
+
+Although all of the function data-blocks for a component's entry points appear
+to the user as distinct objects, the system keeps all of the code in a single
+code data-block.  The user-visible function object is actually a pointer into
+the middle of a code data-block.  This allows any control transfer within a
+component to be done using a relative branch.
+
+Besides a function object, there are other kinds of references into the middle
+of a code data-block.  Control transfer into a function also occurs at the
+return-PC for a call.  The system represents a return-PC somewhat similarly to
+a function, so GC can also recognize a return-PC as a reference to a code
+data-block.
+
+It is incorrect to think of a code data-block as a concatenation of "function
+data-blocks".  Code for a function is not emitted in any particular order with
+respect to that function's function-header (if any).  The code following a
+function-header may only be a branch to some other location where the
+function's "real" definition is.
+
+
+The following are the three kinds of pointers to code data-blocks:
+   Code pointer (labeled A below):
+      A code pointer is a descriptor, with other-pointer low-tag bits, pointing
+      to the beginning of the code data-block.  The code pointer for the
+      currently running function is always kept in a register (CODE).  In
+      addition to allowing loading of non-immediate constants, this also serves
+      to represent the currently running function to the debugger.
+   Return-PC (labeled B below):
+      The return-PC is a descriptor, with other-pointer low-tag bits, pointing
+      to a location for a function call.  Note that this location contains no
+      descriptors other than the one word of immediate data, so GC can treat
+      return-PC locations the same as instructions.
+   Function (labeled C below):
+      A function is a descriptor, with function low-tag bits, that is user
+      callable.  When a function header is referenced from a closure or from
+      the function header's self-pointer, the pointer has other-pointer low-tag
+      bits, instead of function low-tag bits.  This ensures that the internal
+      function data-block associated with a closure appears to be uncallable
+      (although users should never see such an object anyway).
+
+      Information about functions that is only useful for entry points is kept
+      in some descriptors following the function's self-pointer descriptor.
+      All of these together with the function's header-word are known as the
+      "function header".  GC must be able to locate the function header.  We
+      provide for this by chaining together the function headers in a NIL
+      terminated list kept in a known slot in the code data-block.
+
+
+A code data-block has the following format:
+   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  <-- A
+   |  Header-Word count (24 bits)	  |  %Code-Type (8 bits)  |
+   ----------------------------------------------------------------
+   |  Number of code words (fixnum tag)				  |
+   ----------------------------------------------------------------
+   |  Pointer to first function header (other-pointer tag)	  |
+   ----------------------------------------------------------------
+   |  Debug information (structure tag)				  |
+   ----------------------------------------------------------------
+   |  First constant (a descriptor)				  |
+   ----------------------------------------------------------------
+   |  ...                       				  |
+   ----------------------------------------------------------------
+   |  Last constant (and last word of code header)           	  |
+   ----------------------------------------------------------------
+   |  Some instructions (non-descriptor)			  |
+   ----------------------------------------------------------------
+   |     (pad to dual-word boundary if necessary)		  |
+   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  <-- B
+   |  Word offset from code header (24)	  |  %Return-PC-Type (8)  |
+   ----------------------------------------------------------------
+   |  First instruction after return				  |
+   ----------------------------------------------------------------
+   |  ... more code and return-PC header-words			  |
+   ----------------------------------------------------------------
+   |     (pad to dual-word boundary if necessary)	  	  |
+   ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  <-- C
+   |  Offset from code header (24)  |  %Function-Header-Type (8)  |
+   ----------------------------------------------------------------
+   |  Self-pointer back to previous word (with other-pointer tag) |
+   ----------------------------------------------------------------
+   |  Pointer to next function (other-pointer low-tag) or NIL	  |
+   ----------------------------------------------------------------
+   |  Function name (a string or a symbol)			  |
+   ----------------------------------------------------------------
+   |  Function debug arglist (a string)				  |
+   ----------------------------------------------------------------
+   |  Function type (a list-style function type specifier)	  |
+   ----------------------------------------------------------------
+   |  Start of instructions for function (non-descriptor)	  |
+   ----------------------------------------------------------------
+   |  More function headers and instructions and return PCs,	  |
+   |  until we reach the total size of header-words + code	  |
+   |  words.							  |
+   ----------------------------------------------------------------
+
+
+The following are detailed slot descriptions:
+   Code data-block header-word:
+      The immediate data in the code data-block's header-word is the number of
+      leading descriptors in the code data-block, the fixed overhead words plus
+      the number of constants.  The first non-descriptor word, some code,
+      appears at this word offset from the header.
+   Number of code words:
+      The total number of non-header-words in the code data-block.  The total
+      word size of the code data-block is the sum of this slot and the
+      immediate header-word data of the previous slot.  The system accesses
+      this slot with the system constant, %Code-Code-Size-Slot, offset from the
+      header-word.
+   Pointer to first function header:
+      A NIL-terminated list of the function headers for all entry points to
+      this component.  The system accesses this slot with the system constant,
+      %Code-Entry-Points-Slot, offset from the header-word.
+   Debug information:
+      The DEBUG-INFO structure describing this component.  All information that
+      the debugger wants to get from a running function is kept in this
+      structure.  Since there are many functions, the current PC is used to
+      locate the appropriate debug information.  The system keeps the debug
+      information separate from the function data-block, since the currently
+      running function may not be an entry point.  There is no way to recover
+      the function object for the currently running function, since this
+      data-block may not exist.  The system accesses this slot with the system
+      constant, %Code-Debug-Info-Slot, offset from the header-word.
+   First constant ... last constant:
+      These are the constants referenced by the component, if there are any.
+      The system accesses the first constant slot with the system constant,
+      %Code-Constants-Offset, offset from the header-word.
+
+   Return-PC header word:
+      The immediate header-word data is the word offset from the enclosing code
+      data-block's header-word to this word.  This allows GC and the debugger
+      to easily recover the code data-block from a return-PC.  The code at the
+      return point restores the current code pointer using a subtract immediate
+      of the offset, which is known at compile time.
+
+   Function entry point header-word:
+      The immediate header-word data is the word offset from the enclosing code
+      data-block's header-word to this word.  This is the same as for the
+      retrun-PC header-word.
+   Self-pointer back to header-word:
+      In a non-closure function, this self-pointer to the previous header-word
+      allows the call sequence to always indirect through the second word in a
+      user callable function.  See section "Closure Format".  With a closure,
+      indirecting through the second word gets you a function header-word.  The
+      system ignores this slot in the function header for a closure, since it
+      has already indirected once, and this slot could be some random thing
+      that causes an error if you jump to it.  This pointer has an
+      other-pointer tag instead of a function pointer tag, indicating it is not
+      a user callable Lisp object.  The system accesses this slot with the
+      system constant, %Function-Code-Slot, offset from the function
+      header-word.
+   Pointer to next function:
+      This is the next link in the thread of entry point functions found in
+      this component.  This value is NIL when the current header is the last
+      entry point in the component.  The system accesses this slot with the
+      system constant, %Function-Header-Next-Slot, offset from the function
+      header-word.
+   Function name:
+      This function's name (for printing).  If the user defined this function
+      with DEFUN, then this is the defined symbol, otherwise it is a
+      descriptive string.  The system accesses this slot with the system
+      constant, %Function-Header-Name-Slot, offset from the function
+      header-word.
+   Function debug arglist:
+      A printed string representing the function's argument list, for human
+      readability.  If it is a macroexpansion function, then this is the
+      original DEFMACRO arglist, not the actual expander function arglist.  The
+      system accesses this slot with the system constant,
+      %Function-Header-Debug-Arglist-Slot, offset from the function
+      header-word.
+   Function type:
+      A list-style function type specifier representing the argument signature
+      and return types for this function.  For example,
+         (FUNCTION (FIXNUM FIXNUM FIXNUM) FIXNUM)
+      or
+	 (FUNCTION (STRING &KEY (:START UNSIGNED-BYTE)) STRING)
+      This information is intended for machine readablilty, such as by the
+      compiler.  The system accesses this slot with the system constant,
+      %Function-Header-Type-Slot, offset from the function header-word.
+
+
+
+;;;; Closure Format.
+
+A closure data-block has the following format:
+   ----------------------------------------------------------------
+   |  Word size (24 bits)	         | %Closure-Type (8 bits) |
+   ----------------------------------------------------------------
+   |  Pointer to function header (other-pointer low-tag)	  |
+   ----------------------------------------------------------------
+   |				  .				  |
+   |	               Environment information                    |
+   |				  .				  |
+   ----------------------------------------------------------------
+
+
+A closure descriptor has function low-tag bits.  This means that a descriptor
+with function low-tag bits may point to either a function header or to a
+closure.  The idea is that any callable Lisp object has function low-tag bits.
+Insofar as call is concerned, we make the format of closures and non-closure
+functions compatible.  This is the reason for the self-pointer in a function
+header.  Whenever you have a callable object, you just jump through the second
+word, offset some bytes, and go.
+
+
+
+;;;; Function call.
+
+Due to alignment requirements and low-tag codes, it is not possible to use a
+hardware call instruction to compute the return-PC.  Instead the return-PC
+for a call is computed by doing an add-immediate to the start of the code
+data-block.
+
+An advantage of using a single data-block to represent both the descriptor and
+non-descriptor parts of a function is that both can be represented by a
+single pointer.  This reduces the number of memory accesses that have to be
+done in a full call.  For example, since the constant pool is implicit in a
+return-PC, a call need only save the return-PC, rather than saving both the
+return PC and the constant pool.
+
+
+
+;;;; Memory Layout.
+
+CMU Common Lisp has four spaces, read-only, static, dynamic-0, and dynamic-1.
+Read-only contains objects that the system never modifies, moves, or reclaims.
+Static space contains some global objects necessary for the system's runtime or
+performance (since they are located at a known offset at a know address), and
+the system never moves or reclaims these.  However, GC does need to scan static
+space for references to moved objects.  Dynamic-0 and dynamic-1 are the two
+heap areas for stop-and-copy GC algorithms.
+
+What global objects are at the head of static space???
+   NIL
+   eval::*top-of-stack*
+   lisp::*current-catch-block*
+   lisp::*current-unwind-protect*
+   FLAGS (RT only)
+   BSP (RT only)
+   HEAP (RT only)
+
+In addition to the above spaces, the system has a control stack, binding stack,
+and a number stack.  The binding stack contains pairs of descriptors, a symbol
+and its previous value.  The number stack is the same as the C stack, and the
+system uses it for non-Lisp objects such as raw system pointers, saving
+non-Lisp registers, parts of bignum computations, etc.
+
+
+
+;;;; System Pointers.
+
+The system pointers reference raw allocated memory, data returned by foreign
+function calls, etc.  The system uses these when you need a pointer to a
+non-Lisp block of memory, using an other-pointer.  This provides the greatest
+flexibility by relieving contraints placed by having more direct references
+that require descriptor type tags.
+
+A system area pointer data-block has the following format:
+    -------------------------------------------------------
+    |     1 (data-block words)        | SAP Type (8 bits) |
+    -------------------------------------------------------
+    |   	      system area pointer		  |
+    -------------------------------------------------------
+
+"SAP" means "system area pointer", and much of our code contains this naming
+scheme.  We don't currently restrict system pointers to one area of memory, but
+if they do point onto the heap, it is up to the user to prevent being screwed
+by GC or whatever.
-- 
GitLab