The implementation of PC Scheme

PC Scheme is a compiler-based implementation of Scheme for PC-class machines. The compiler generates code for an idealized virtual machine which is emulated with threaded code techniques. The design has traded off the requirements of space sad speed effectively, resulting in one of the fastest PC-class LISP systems known to the authors. 1 I n t r o d u c t i o n Scheme [RRRS 85] is a lexically ecoped dialect of LISP which was originally developed at MIT [Steele 78a] and has subsequently been used extensively for research, education, and application programming. Like COMMON LISP, Scheme features lexical scoping and first-class functions. Unlike COMMON LISP, Scheme supports continuations as first-class objects and is properly tail-recursive. Many Scheme dialects, including ours, have also incorporated environments and engines as first-class objects. Texas Instruments has been interested in Scheme for some time as a vehicle for experiments in compilation techniques and the design of architectures for symbolic computing. PC Scheme, a commercial by-product of this research, was created to support the development of such AI packages and products as TI's~Personal Consu~ant'" and Arborist. '" Since TI and IBIvi ~ PCs with Inte~ ~-) 8088 and 80286 processors and typical memory capacities of 512K bytes were to be used as both the development and target machines for these efforts, PC Scheme was required to be both fast and small. Permission to cOW/without fee all or part of this material is granted provided that the copies are not made or dis~ribuUxl for direct commercial advantage, the ACM copyright notice and the title of the publication and its date appear, and notice is given that copying is by permission of the Association for Computing Machinery. To copy otherwise, or to republish, requires • fee and/or specfic permission. Following Powell [Powel184], the design philosophy of our system is "best simple. " Whenever possible, our design decisions favored the simplest and most cost-effective alternatives available. Uncommon features were made to "pay their own way," while common ones were streamlined as much as possible. The result is a compilation strategy and virtual machine architecture that are highly tuned to our purpmes. 2 T h e V i r t u a l M a c h i n e ( V M ) The key to a successful LISP implementation on a small machine is careful design of the environment in which programs execute. The selection of data representations is particularly critical because of the pervasive use of type dispatching in program code and the garbage collector. Although our requirement for speed dictated a compilerbased approach, the expected bulkiness of true native code on 8-bit processors was excessive for the small memories available. We chose instead to emulate a virtual machine representing an idealized Scheme architecture. Our VM simplifies the compiler considerably yet is surprisingly fast. 2.1 V M I m p l e m e n t a t i o n Threaded code [Bell 73] is a well-known implementation technique for the emulation of language-specific architectures. It combines compact encoding of programs with rapid execution. Our byto.level encoding is similar to most Paacal P-code implementations: the byte pointed to by the emulated "program counter" is used as an inde.x into a table of machine language subroutine addresses. Each of these machine language routines performs an operation, optionally conditioned by the contents of subsequent "operand" bytes in the emulated VM instruction stream, and then executes the NEXT sequence shown below to fetch and execute the next VM instruction. xor AX.AX ; c l e a r AX lods byte p t r ES:[SI] ; get next mov BX,AX : opcod, shl BX,I ; make word index imp op_tabla*[BX] : and Jump © 1986 ACM 0-89791-2004186t0600-0086 75¢ 86 The emulation overhead for this NEXT sequence represents about 16% of the total execution time for an average mix of VM instructions. As shown in Figure 1, the ~ emulator and that part of the runtime support which is written in assembly language and C consume 137,500 bytes of memory. The rest of the system is written in Scheme. The first 76,700 bytes of Scheme code are loaded into the heap during system initialization. The rest of the system code is autoloaded as needed. 2.2 V M A r c h i t e c t u r e Unlike P-code, which is a stack-based architecture oriented towards strongly typed languages, our virtual machine architecture is register-based and emphasizes the requirements of Lisp-like languages. The choice of a registerbased model was motivated by the desire to investigate compilation techniques for such register-based machines as the Motorola 68000, the National /TI 32000, and Berkeley's SOAR [Ungar 84], a tagged Reduced Instruction Set Computer (RISC). The ~ architecture largely follows the tenets of the RISC philosophy but integrates most lowlevel run time features and many commonly used primitive Scheme functions as VIVI instructions. This is a valuable aid to increased performance for an emulated VM, though less appropriate for an actual implementation in hardware. The heart of the PC Scheme VM is a set of 64 general purpose registers, R0-R63. Arguments to functions are passed in registers where they remain available for use until no longer needed or until another function call occurs. Except as described in section 2.5, all user variables and temporaries reside in the registers so they may be conveniently accessed by VM instructions. There are also special purpose registers for the global and fluid environment pointers, the current code block pointer, and the current program counter. The VM is a conventional load/store architecture with most instructions operating directly on the registers. For fast decoding, each VM instruction has its own specific operand format and its handler is responsible for fetching and operating upon the operand bytes. Register operands are encoded as the register number scaled by 4, the width in bytes of an entry in the underlying table of VM "regi s ters / Other operand types include immediate values, constant table indices, stack frame offsets, and branch displacements. A typical VM instruction is illustrated below. I n s t r u c t i o n : ADD RS,R7 ; it5 := R5 ÷ R7 Byte 1: Byte 2: Byte 3: 0pcode • ADD. encoded as 80 SD operand • It6. encoded as 5*4 = 20 S operand = R7. encoded as 7*4 = 28 Other virtual machine architectures for LISP include variations on the MIT LISP Machine [Greenblatt 84] and SPICE LISP /Wholey 84 I. RABBIT ISteele 78b], MACSCHEME, and an unnamed "small Scheme implementation" at MIT [Schooler 84} propose VMs specific to the needs of Scheme. Of these, only RABBIT is register-based. D~o~. ptio. "native code: program static da ta runtime stack C's heap (subtotal) Scheme: s tandard system Autoloaded " Edwin editor Size (b¢es) 72,000 23,700 12,300 29,500 137,500 76,700 109,000 Scheme: SCOOPS 18,700 structure editor 8,200 miscellaneous 30,900 total 381,000 Figure 1: Memory space allocation The others employ simple stack models, with instructions typically operating on implicitly specified operands at the top of the stack. Like PC Scheme, MACSCHEME and the MIT "small Scheme" are bytecoded implementations (although the MIT design was never completed). The RABBIT VM is actually a subset of LiSP with global variables serving as VM registers. SPICE and the LISP Machines have microcoded implementations. These LisP-oriented architectures have several features in common. Generally, instructions operate only on tagged data and validate the types of their operands. Frequently used operations like MF, HBER, SYMBOLP, and CONS are implemented as instructions. Except for RABBIT, they have been designed with reduced compiler complexity as a specific goal and are oriented primarily to supplying the needs of software developers rather than to achieving the highest possible performance. Conversely, PC Scheme is an experiment in the design of fast delivery vehicles for AI programs. This has lead us to consider optimizing compilers and RISC-like, registerbased VMs for the same reasons that they are of interest to designers of conventional processors. Explicit operand addressing significantly reduces the number of pushes, pops, and other data movement instructions generated, resulting in shorter object programs compared to traditional stack VMs. Performance is further enhanced by the high average level of work performed by each V'M instruction compared to the overhead of the NEXT operation. Much of this work is dedicated to software tag checking, however--an operation that can be performed much more effectively with suitable hardware. We believe that our VM is a suitable architecture for a RISC implementation. 2.3 M e m o r y Mode l and D a t a Representat ions PC Scheme memory consists of a linear address space subdivided into logical pages to support a BIBOP (big bag of pages) tagging mechanism. An object is represented by a three byte pointer. The page number and page displacement are stored in the top byte and bot tom two bytes, respectively.