diff --git a/docs/internals/internal-design.txt b/docs/internals/internal-design.txt new file mode 100644 index 0000000000000000000000000000000000000000..a27693138700a969fd0b220312454fb5507fcfa1 --- /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.