On 2012-06-08, at 5:50 AM, Mikael wrote:
I remember an ML post where Marc said that, presuming you don't use eval, Gambit's Scheme codebase can be tree-shaked too.
Well just removing unneeded parts of Gambit's core ought to be straightforward enough too; afaik actually *all* of the runtime can be removed and you still have a running system, though I suppose such a system would be of little use.
Tree shaking is done at compile time, and on a single module basis. If you want to tree-shake the whole program including the Scheme libraries, then concatenate all the Scheme library files and your program files into a single source file which you can compile using the tree-shaking flag. With the current Gambit runtime this will give very disapointing results because of the many interdependencies of the runtime system. For example, the default runtime system calls "load" (to load ~~lib/gambcext if it exists), and "load" calls "eval", and "eval" needs all the global variables. So the tree shaker will not be able to eliminate much code.
The tree shaker will be more useful for custom runtime libraries with few interdependencies. A custom runtime library is fairly straightforward to implement (but the coding effort will depend on how much functionality you want). The only required parts of the Gambit runtime library is the file _kernel.scm (which contains low-level functionality expressed in Scheme), and all of the .c files not prefixed by "_" in the lib directory.
As an example, here is how to write a minimal runtime library which implements procedures for doing stdio-like output with putchar and puts. The file minilib.scm contains the definitions of these procedures:
-------------------------------------------------------------------------------------- ;; File: "minilib.scm"
(include "header.scm")
;; Implement a minimal stdio emulation
;; The procedures ##os-device-stream-open-predefined and ;; ##os-device-stream-write, which are defined in _kernel.scm, ;; actually call the C functions ___os_device_stream_open_predefined ;; and ___os_device_stream_write which are implemented in lib/os_io.c .
;; The other "##" procedures are inlined by the Scheme compiler, so ;; there is no need for a Scheme "define" of these procedures and you ;; won't find them in _kernel.scm .
(define stdout (##os-device-stream-open-predefined 1 ;; stdout is 1 (##fx+ (##fx* (macro-direction-shift) (macro-direction-out)) (##fx* (macro-append-shift) (macro-default-append)) (##fx* (macro-truncate-shift) (macro-default-truncate)))))
(define (putchar c) (let ((buf (##u8vector (##fx<-char c)))) (##os-device-stream-write stdout buf 0 1)))
(define (puts str) (let loop ((i 0)) (if (##fx< i (##string-length str)) (begin (putchar (##string-ref str i)) (loop (##fx+ i 1)))))) --------------------------------------------------------------------------------------
and the file miniapp.scm contains the application which uses that runtime system:
-------------------------------------------------------------------------------------- ;; File: "miniapp.scm"
(puts "hello\n") (puts "world\n") --------------------------------------------------------------------------------------
To build the minimal runtime library and application, copy this shell script and the above files to the lib subdirectory and run the shell script:
-------------------------------------------------------------------------------------- #! /bin/sh
CC="gcc"
CLIB="main.o setup.o mem.o c_intf.o os.o os_base.o os_time.o os_shell.o os_files.o os_dyn.o os_tty.o os_io.o"
# create miniature library ../gsc-boot -:=.. -c minilib.scm ../gsc-boot -:=.. -link -flat -o minilib_.c _kernel.c minilib.c $CC -c -I../include -D___SINGLE_HOST -D___LIBRARY -o minilib.o minilib.c $CC -c -I../include -D___SINGLE_HOST -D___LIBRARY -o minilib_.o minilib_.c
# create app linked with the miniature library ../gsc-boot -:=.. -c miniapp.scm ../gsc-boot -:=.. -l minilib_.c -link -o miniapp_.c miniapp.c $CC -c -I../include -D___SINGLE_HOST -o miniapp.o miniapp.c $CC -c -I../include -D___SINGLE_HOST -o miniapp_.o miniapp_.c
$CC -o miniapp $CLIB _kernel.o minilib.o minilib_.o miniapp.o miniapp_.o
strip miniapp ls -l miniapp
./miniapp
echo $? --------------------------------------------------------------------------------------
On Mac OS X Lion, the miniapp executable is 350K when linked with the minimal runtime library, which is quite a bit less than the 3.5M obtained when linking with Gambit's standard runtime library.
Note however that this approach is *bare bones*... None of the standard Scheme procedures are available. You have to implement them if you want them. For examples, if you want the procedure "cons", then you need to add to minilib.scm the following definition which calls ##cons (which is inlined by the compiler):
(define (cons x y) (##cons x y))
If you want the procedure "car" (and you want it to be type safe) then you need something like:
(define (car x) (if (##pair? x) (##car x) (##exit 1)))
If you want better error handling you might want to add an "error" procedure:
(define (error msg) (puts "*** ERROR -- ") (puts msg) (puts "\n") (##exit 1))
(define (car x) (if (##pair? x) (##car x) (error "car expects a PAIR")))
You get the idea...
This gives you a lot of control, and a lot of responsibility.
This is a great way to implement a Scheme system without having to spend time designing a garbage-collector, object representation, operating-system interface, etc. etc. I'm pretty sure a simple interpreter for Scheme (or another language) could be written in Scheme to obtain an executable below one megabyte.
Marc