Support for compilation into real machine code in CSL ===================================================== First note that this does not really exist yet, and the facilities and ideas described here are part of an EXPERIMENT rather than stable and viable reality. And yet more - since I first wrote this I have re-thought the way I want to achieve the aim of faster code so the scheme described here is NOT liable to be the way that I go... Furthermore these days Unix systems seem to be moving towards greater protection so mixing data and executable memory is liable to be MUCH harder than it used to be and any completion of this work correspondingly more laden with system-specific stuff that is just there to defeat the protection mechanisms! An objective is that a single CSL image file should continue to work properly with any version of the system. This means that if run on a computer that uses a machine code where no hard-code compiler is available the system should run all byte-coded. It also suggests that there should be support for having multiple sets of machine code in the one image. The first part of realising this is to arrange that there is a numeric code value that is available at run-time and that identifies the current machine architecture. Part of this is set up in the CSL sources in the file "machine.h" by defining a symbol HARD_CODE_TAG, which is an integer in the range 0 to 31 (if I ever get close to having more different sorts of computer than that I will think some more!). The tag found in this way can be qualified (in csl.c) by setting some of the 0xe0 bits to distinguish between systems that "machine.h" does not. Eg this MIGHT be used to distinguish between 486, Pentium and Pentium-Pro optimisations in an Intel-targetted version, where different optimisations would be useful in the three cases. The value of hard_code_tag end up in the range 0 to 255 with 0 reserved to mean "no hard code facility available for this architecture". The value of this tag is available at run-time on the features list (lispsystem!* in Standard Lisp mode) as an entry of the form (native . nnn). Hard coded functions live in a set of pages (hard_code_pages). In memory there will be just one such set of pages, and for a FIRST version at least their contents will be fixed and not subject to garbage collection. I hope that eventually it will be possible to have a garbage collector for this space though, and so the code in CSL makes provision for a second space of hard code that the main one could be copied across into. In generating machine code the possibility that even while machine code is being executed a garbage collection might want to relocate it should be considered, and for some architectures this thought may be sufficiently dreadful that it must be avoided even if on other systems it is permitted. In an image file there can be many different sets of hard code pages. Because preserving a new image only wants to over-write one of these (the one for the current machine type) these will not be stored as part of the main initial image itself, but as items rather like "fasl" modules. Well actually rather more like the way that help data is stored in the image file. I will support the idea that for codes 1 to 255 the numeric code -1 to -255 can be passed in to the low-level file manipulation code in "preserve.c" (just as special values are used for IMAGE_CODE and HELP_CODE). At startup it can be checked whether a hard code module of the relevant sort exists, and if so it can be loaded. The format in the module will be a 2-byte integer showing how many pages are there followed by dumps of the data to go in each such page. On doing a "preserve" to write out a new checkpoint image the code will in effect preface the writing of the root portion of the image with a "(faslout 'HardCode<nn>); (write-out-the-hard-code)(faslend)" or whatever to put a copy of what is in memory back into the module. Well, I guess the correct strategy will be for it to do this if anything has been done that changes the set of things compiled into machine code. When hard code is loaded two more things have to be done. Firstly the code may need to be relocated, both to allow for the address in memory that it ends up at, and then to fix up places where it refers to entrypoints into the rest of CSL and to statically allocated data. This will be done by making the contents of a hard code page contain relevant relocation information so that it can be scanned when first loaded and the code patched up. The relocation and patch-up information needs to be designed so that a single relocation program can deal with all the possible relocation modes needed by various architectures, even though only a few will be implemented to start with. Secondly it will be necessary to point actual entrypoints into code towards the proper bits of compiled code. This last is achieved by having (in the main heap image) a list (hard_code) that gives such entrypoints. For every function that has been hard coded for at least one architecture this has an entry, so overall its structure is: ((f-name-1 nargs . details-1) (f-name-2 nargs . details-2) ...) The value nargs is an integer as used in symbol_set_definition (fns2.c) and where each "details" is a list of items details = ((type-and-page offset . env) ...) here type-and-page is an integer (a Lisp fixnum). The top 8 bits give the machine type that this entry refers to. If these bits are zero we are talking here about a byte-coded definition. Every function that is hard compiled for some architecture must have a bytecoded definition stored here (so it can be instated on systems where no hard code is available). Non-zero values are used when genuine hard code is available. The next 2 bits give four possibilities. The obvious one is when the code-pointer described here just goes into the natural function cell of the symbol and the other two function calls get filled with default values. The remaining three codes are used to make it possible to put a value into just the FN1, FN2 or FNN call of the function. The bottom 4 bits of the fixnum are TAG_FIXNUM to indicate that it is a small integer, so that leaves 18 over. These are used to specify a page-number in the hard code heap. Since pages are 64K or (more usually) 256K bytes large having 18 bits of page selection is comfortably generous for the moment. Lisp integer "offset" is a byte offset within the selected page. "env" is an arbitrary Lisp value (but very often a vector) that will be placed in the environment cell when the function is defined. Note that it may often (I hope) be that the same environment vector will be used for several different machine architectures, and in such cases the reference will be to a shared object, so space might not be too badly wasted. See relocate_hard_code() in restart.c and preserve_hard_code in preserve.c for some more details. The following Lisp functions may be used: (setq v (make-native n)) create handle on n bytes of native code space (putv-native v k w) put byte w at offset k (getv-native v k) retrieve byte (for checking) (putv-native v k w 1/2/4) as putv-native but the trailing integer arg (getv-native v k 1/2/4) .. says use 1, 2 or 4 byte value. (preserve) dumps all current native code for re-loading (native-address 'lispfn nargs) get address of entrypoint for function when called with n args (native-address n) integer n selects an address to hand back as an integer. See fns3.c for details The native code as created must include (put there by the person generating the code) relocation etc information. (symbol-set-native fname args bpsbase offset env) fname must be a symbol. (args & 0xff) is the number of args to the function. If other bits of args being set tell the system NOT to set the other 2 function cells to error calls, so USUALLY just use args=1,2 or 3. bpsbase is the value returnned earlier by (make-native nnn). Bytes in it must have been filled in by (native-putv ..) calls. offset is the offset within this vector that the entrypoint should be set to. The offset is needed because the first few bytes of the vector will need to hold relocation information for when the code is re-loaded. At present PLEASE start the contents of the vector at byte 8 leaving the first few bytes untouched. Sometimes (later on) a function taking variable numbers of args will also have several entrypoints into the same vector - another reason for having the offset. env is a thing to put in the environment cell of the function, and this will be passed as the first argument to any call, so it will probably usefully be a vector of literal Lisp objects that the function wants to use. Problem: I maybe want to support cross-compilation of native code, and for that the function symbol-set-native may need to be told what architecture the relevant code has been created for?