Compare commits

...

24 Commits

Author SHA1 Message Date
e2f4e3d746 Cleaner cps interface 2025-12-11 10:17:19 -06:00
3ad9159969 Arbitrary stack access. What to do with tail calls? 2025-12-09 16:09:05 -06:00
69b6ccbce0 Cps conversion for full programs? 2025-11-26 15:28:08 -06:00
7eb1ede3d9 Choose CPS over ANF due to body of literature on topic 2025-11-26 13:28:10 -06:00
b5d3438e79 Remove failing tests, remove implemented code generator 2025-11-25 17:45:18 -06:00
4f8459ae64 Minor scheme changes 2025-10-07 08:04:01 -05:00
095ced6f03 Desugaring and ANF tranforms 2025-10-03 16:47:28 -05:00
e31483a76e WIP scheme compiler 2025-08-07 16:30:47 -05:00
5915c42fe3 Atomic cps operations (ints lol) 2025-09-08 19:05:25 -05:00
883ee645c4 Remove common 2025-09-08 19:02:09 -05:00
2d868bb581 Move further code to common, rename to assembler (last restructure I promise) 2025-09-05 09:58:11 -05:00
4e8e3ef8c4 Move common elements of the assembler (basically everything) to common file 2025-09-04 19:48:26 -05:00
490840e577 Stateful assembler like Guile, hope to extend to interface for compiler 2025-09-03 19:37:13 -05:00
330aca002f Project restructuring to support the new direction 2025-08-28 13:02:37 -05:00
d109b6f374 Barking up the wrong tree here, remember compilation, not interpretation 2025-08-28 12:53:32 -05:00
31c529e83a Start working on reference for runtime environment 2025-08-28 09:06:22 -05:00
3166108e4e Don't reexport from root module 2025-08-25 21:25:58 -05:00
dd8376365d Add a little scratch file so I can keep track of my work. Actually terminate runtime program 2025-08-25 17:39:43 -05:00
54709e55f8 Added stepping to debugger 2025-08-25 17:26:11 -05:00
1c84a9c862 Some bugfixes to runtime code, probably need to implement stepping debugger 2025-08-25 12:34:46 -05:00
d0d0ca23ec More useful error messages, assume unsigned ints 2025-08-25 12:34:08 -05:00
33f1618915 Some more compile time errors, some bug fixes for runtime 2025-08-25 11:44:48 -05:00
274376a5de WIP? written but untested runtime code 2025-08-22 19:10:18 -05:00
03fa8d4370 fix bug, logand arg 2 2025-08-22 19:09:24 -05:00
15 changed files with 573 additions and 259 deletions

View File

@@ -1,15 +1,16 @@
(push main)
(jmp)
;; Note that this is scheme syntax wrapping asm for a stack machine ;; Note that this is scheme syntax wrapping asm for a stack machine
(variable eom 1024) (variable eol 0)
;; These need to be initialized with the runtime
(variable scan 0) (variable scan 0)
(variable free 0) (variable free 0)
;; These need to be initialized with the runtime
(variable eom 0)
(variable old 0) (variable old 0)
(variable new 0) (variable new 0)
(variable root 0) (variable root 0)
(push main)
(jmp)
alloc ;; ( -- p) alloc ;; ( -- p)
;; Test if free will go beyond eom ;; Test if free will go beyond eom
(ref free) (ref free)
@@ -30,7 +31,7 @@ alloc-do-gc
(drop) (drop)
(drop) (drop)
;; Run garbage collection ;; Run garbage collection
(push gc-start) (push gc-run)
(call) (call)
;; Tail-call allocation ;; Tail-call allocation
(push alloc) (push alloc)
@@ -49,9 +50,13 @@ gc-run ;; ( -- )
(call) (call)
; Flip old and new ; Flip old and new
(ref old) (ref old)
(dup)
(ref new) (ref new)
(set! old) (set! old)
(set! new) (set! new)
(push 512)
(+)
(set! eom)
(return) (return)
relocate-cons ;; (o -- ) relocate-cons ;; (o -- )
@@ -66,19 +71,19 @@ relocate-cons ;; (o -- )
relocate-reg ;; (r -- ) relocate-reg ;; (r -- )
(dup) (dup)
(push cons?) (push #x80000000) ;; Is this a cons?
(call) (and)
(push reg-relocated) (push reg-relocated)
(if) (if)
(dup) (dup)
(push eol?) (ref eol)
(call) (=) ;; Is this eol?
(not) (not)
(push reg-relocated) (push reg-relocated)
(if) (if)
(dup) (dup)
(push broken-heart?) (push #x40000000) ;; Is it a broken heart?
(call) (and)
(push copy-and-construct) (push copy-and-construct)
(if) (if)
(dup) ;; Broken heart, copy updated address from cdr (dup) ;; Broken heart, copy updated address from cdr
@@ -102,9 +107,14 @@ copy-and-construct
(push 4) (push 4)
(+) (+)
(!) (!)
(dup) ;; Construct the broken heart (push #x40000000)
(push install-broken-heart) (over)
(call) (!)
(ref free)
(over)
(push 4)
(+)
(!)
(ref free) ;; Move free pointer (ref free) ;; Move free pointer
(push 8) (push 8)
(+) (+)
@@ -112,5 +122,32 @@ copy-and-construct
reg-relocated reg-relocated
(drop) (drop)
(return) (return)
gc-loop
(ref free)
(ref scan)
(<)
(branch gc-loop-done)
(ref scan)
(push relocate-reg)
(push gc-loop)
(jmp)
gc-loop-done
(return)
main main
;; TODO ;; These need to be initialized with the runtime
(push memory)
(dup)
(set! new)
(push 512)
(+)
(dup)
(set! eom)
(set! old)
;; TODO set up root
;; <<USER PROGRAM HERE >>
(bye)
memory

3
example/c/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.o
scheme
scheme.exe

5
example/c/Makefile Normal file
View File

@@ -0,0 +1,5 @@
CFLAGS=-g
all: scheme
scheme: gc.o runtime.o

View File

@@ -1,17 +1,18 @@
#include <stdint.h> #ifndef _COMMON_H_
#include <stdlib.h> #define _COMMON_H_
#include <stdio.h>
#include <string.h>
#define SIZE 8 #define SIZE 1024
#define BROKEN_HEART 1 #define BROKEN_HEART 1
#define CONS 2 #define CONS 2
#define INTEGER 3 #define INTEGER 3
#define SYMBOL 4
#define BYE 5
typedef struct box_t { typedef struct box_t {
char type; char type;
union { union {
int integer; int integer;
char* symbol;
struct cons_t* cons; struct cons_t* cons;
}; };
} box_t; } box_t;
@@ -21,8 +22,6 @@ typedef struct cons_t {
box_t cdr; box_t cdr;
} cons_t; } cons_t;
void init(); extern cons_t *the_empty_list;
cons_t *alloc();
void gc_run(); #endif // _COMMON_H_
void gc_loop();
void relocate(cons_t*);

View File

@@ -1,11 +1,12 @@
// Cheney style stop and copy garbage collector // Cheney style stop and copy garbage collector
#include "gc.h" #include "gc.h"
cons_t *the_empty_list = NULL;
static cons_t *old, *new, *scanptr, *freeptr, *eom, *root; static cons_t *old, *new, *scanptr, *freeptr, *eom, *root;
static cons_t *the_empty_list = NULL;
size_t tos; size_t tos;
void init() { void gc_init() {
old = calloc(sizeof(cons_t), SIZE); old = calloc(sizeof(cons_t), SIZE);
freeptr = old; freeptr = old;
eom = old + (SIZE / 2); eom = old + (SIZE / 2);
@@ -61,28 +62,3 @@ void relocate(cons_t* cons) {
move(cons->car); move(cons->car);
move(cons->cdr); move(cons->cdr);
} }
int main() {
init();
// Simulate running linear fibonnaci
root->car.type = INTEGER;
root->car.integer = 1;
root->cdr.type = CONS;
root->cdr.cons = alloc();
root->cdr.cons->car.type = INTEGER;
root->cdr.cons->car.integer = 0;
root->cdr.cons->cdr.type = CONS;
root->cdr.cons->cdr.cons = the_empty_list;
for (size_t i = 0; i < 29; i++) {
cons_t *cons = alloc();
cons->car.type = INTEGER;
cons->car.integer = root->car.integer + root->cdr.cons->car.integer;
cons->cdr.type = CONS;
cons->cdr.cons = root;
root->cdr.cons = the_empty_list;
root = cons;
};
printf("%d\n", root->car.integer); // 832040, and we've definitely run gc a few times
}

16
example/c/gc.h Normal file
View File

@@ -0,0 +1,16 @@
#ifndef _GC_H_
#define _GC_H_
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "common.h"
void gc_init();
cons_t *alloc();
void gc_run();
void gc_loop();
void relocate(cons_t*);
#endif // _GC_H_

View File

@@ -1,6 +0,0 @@
CFLAGS=-g
all: gctest
gctest: gc.o
$(CC) $(CFLAGS) -o $@ $<

View File

@@ -1,21 +1,7 @@
(define-module (scmvm) (define-module (scmvm)
#:use-module (scmvm vm)
#:use-module (scmvm assembler)
#:use-module (scmvm debugger)
#:use-module (ice-9 ports) #:use-module (ice-9 ports)
#:re-export ( ;; vm #:export (read-all-instructions
make-vm run-vm vm-load-program! instructions-from-file))
vm-memory-ref vm-memory-set!
vm-pc vm-pc-set!
;; assembler
assemble
;; debugger
make-debugger debugger-continue
debugger-vm
debugger-breakpoints debugger-breakpoints-set!
debugger-breakpoint-add! debugger-breakpoint-ref
debugger-breakpoint-enable! debugger-breakpoint-disable!)
#:export (read-all-instructions instructions-from-file))
(define (read-all-instructions) (define (read-all-instructions)
(let ([inst (read)]) (let ([inst (read)])

View File

@@ -1,85 +1,82 @@
(define-module (scmvm assembler) (define-module (scmvm assembler)
#:use-module (srfi srfi-1)
#:use-module (scmvm vm) #:use-module (scmvm vm)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-9)
#:use-module (rnrs bytevectors) #:use-module (rnrs bytevectors)
#:use-module (rnrs io ports) #:use-module ((scheme base) #:select (write-bytevector))
#:use-module ((scheme base) #:export ((make-assembler* . make-assembler)
#:select (write-u8 write-bytevector)) assembler?
#:export (assemble)) assembler-pos assembler-pos-set!
assembler-buf
assembler-labels
emit-label
emit-instruction
emit-literal
emit-reference
finalize-references
assembler-dump-program))
(define *aliases* (define (make-label) (cons #f '()))
'((if . branch)))
(define (or-alias inst) (define-record-type <assembler>
(or (assq-ref *aliases* inst) inst)) (make-assembler pos buf labels)
assembler?
(pos assembler-pos assembler-pos-set!)
(buf assembler-buf assembler-buf-set!)
(labels assembler-labels))
(define (lookup-instruction inst) (define (make-assembler*)
(define inst-obj (assq (or-alias inst) *instruction-set*)) (make-assembler 0 (make-bytevector 1024) (make-hash-table)))
(if inst-obj
inst-obj
(error (format #f "could not find instruction ~a" inst))))
(define label? (compose not pair?)) (define (assembler-buf-grow! asm)
(define (variable? x) (let ([buf (make-bytevector (ash (bytevector-length (assembler-buf asm)) -1))])
(and (pair? x) (eq? (car x) 'variable))) (bytevector-copy! (assembler-buf asm) 0 buf 0 (bytevector-length (assembler-buf asm)))
(define (ref? x) (assembler-buf-set! asm buf)))
(and (pair? x) (eq? (car x) 'ref)))
(define (set!? x)
(and (pair? x) (eq? (car x) 'set!)))
(define (instruction? x)
(and (not (label? x))
(not (variable? x))
(not (ref? x))
(not (set!? x))))
(define (instruction-size inst)
(case (car inst)
[(push) 5]
[(ref set!) 6]
[else 1]))
(define (label-pass instructions address) (define* (write-word word asm #:optional (pos (assembler-pos asm)))
(cond (when (> (+ pos 4) (bytevector-length (assembler-buf asm)))
[(null? instructions) '()] (assembler-buf-grow! asm))
[(label? (car instructions)) (bytevector-u32-native-set! (assembler-buf asm) pos word))
(acons (car instructions) address (label-pass (cdr instructions) address))]
[(variable? (car instructions))
(acons (cadar instructions) address (label-pass (cdr instructions) (+ address 4)))]
[else
(label-pass (cdr instructions) (+ address (instruction-size (car instructions))))]))
(define (write-word word) (define* (write-byte byte asm #:optional (pos (assembler-pos asm)))
(define bv (make-bytevector 4)) (when (> (+ pos 1) (bytevector-length (assembler-buf asm)))
(bytevector-s32-native-set! bv 0 word) (assembler-buf-grow! asm))
(write-bytevector bv)) (bytevector-u8-set! (assembler-buf asm) pos byte))
(define (assembly-pass seq labels) (define (assembler-label-add-reference asm name addr)
(cond (when (not (hash-ref (assembler-labels asm) name))
[(null? seq) '()] (hash-set! (assembler-labels asm) name (make-label)))
[(label? (car seq)) (assembly-pass (cdr seq) labels)] (let ([label (hash-ref (assembler-labels asm) name)])
[(variable? (car seq)) (set-cdr! label (cons addr (cdr label)))))
(write-word (caddar seq))
(assembly-pass (cdr seq) labels)]
[(ref? (car seq))
(write-u8 (cadr (lookup-instruction 'push)))
(write-word (assq-ref labels (cadar seq)))
(write-u8 (cadr (lookup-instruction '@)))
(assembly-pass (cdr seq) labels)]
[(set!? (car seq))
(write-u8 (cadr (lookup-instruction 'push)))
(write-word (assq-ref labels (cadar seq)))
(write-u8 (cadr (lookup-instruction '!)))
(assembly-pass (cdr seq) labels)]
[else
(let* ([inst (car seq)]
[inst-obj (lookup-instruction (car inst))])
(write-u8 (instruction-code inst-obj))
(when (eq? (car inst) 'push)
(if (number? (cadr inst))
(write-word (cadr inst))
(write-word (assq-ref labels (cadr inst)))))
(assembly-pass (cdr seq) labels))]))
(define (assemble instructions port) (define (assembler-label-add-value asm name val)
(define labels (label-pass instructions 1)) (when (not (hash-ref (assembler-labels asm) name))
(with-output-to-port port (lambda () (assembly-pass instructions labels))) (hash-set! (assembler-labels asm) name (make-label)))
labels) ;; 1+ to fudge for null pointers
(set-car! (hash-ref (assembler-labels asm) name) (1+ val)))
(define (emit-label asm name)
(assembler-label-add-value asm name (assembler-pos asm)))
(define (emit-instruction asm inst)
(let ([inst-object (assq inst *instruction-set*)])
(write-byte (instruction-code inst-object) asm)
(assembler-pos-set! asm (+ (assembler-pos asm) 1))))
(define (emit-literal asm val)
(write-word val asm)
(assembler-pos-set! asm (+ (assembler-pos asm) 4)))
(define (emit-reference asm name)
(assembler-label-add-reference asm name (assembler-pos asm))
(assembler-pos-set! asm (+ (assembler-pos asm) 4)))
(define (finalize-references asm)
(define (install-location _name label)
(for-each
(cute write-word (car label) asm <>)
(cdr label)))
(hash-for-each install-location (assembler-labels asm)))
(define (assembler-dump-program asm port)
(write-bytevector (assembler-buf asm) port 0 (assembler-pos asm)))

View File

@@ -12,22 +12,23 @@
debugger-breakpoint-add! debugger-breakpoint-ref debugger-breakpoint-add! debugger-breakpoint-ref
debugger-breakpoint-enable! debugger-breakpoint-disable! debugger-breakpoint-enable! debugger-breakpoint-disable!
debugger-breakpoint-delete! debugger-breakpoint-delete!
debugger-continue)) debugger-step debugger-continue))
(define-record-type <debugger> (define-record-type <debugger>
(make-debugger vm source breakpoints continuation) (make-debugger vm asm breakpoints continuation stepping)
debugger? debugger?
(vm debugger-vm) (vm debugger-vm)
(source debugger-source) (asm debugger-asm)
(breakpoints debugger-breakpoints) (breakpoints debugger-breakpoints)
(continuation debugger-continuation debugger-continuation-set!)) (continuation debugger-continuation debugger-continuation-set!)
(stepping debugger-stepping? debugger-stepping-set!))
(define (make-breakpoints labels) (define (make-breakpoints asm)
(define the-breakpoints '()) (define the-breakpoints '())
(define (->index index/label) (define (->index index/label)
(if (number? index/label) (if (number? index/label)
index/label index/label
(assq-ref labels index/label))) (car (hash-ref (assembler-labels asm) index/label))))
(define-syntax-rule (ilambda (i) e ...) (define-syntax-rule (ilambda (i) e ...)
(lambda (v) (let ([i (->index v)]) e ...))) (lambda (v) (let ([i (->index v)]) e ...)))
(match-lambda (match-lambda
@@ -42,26 +43,32 @@
['ref ['ref
(ilambda (i) (assq-ref the-breakpoints i))])) (ilambda (i) (assq-ref the-breakpoints i))]))
(define (make-debugger* source) (define (make-debugger* asm)
(define-values (prgm symbols) (define prgm
(call-with-values open-bytevector-output-port (call-with-values open-bytevector-output-port
(lambda (port get-bv) (lambda (port get-bv)
(define symbols (assemble source port)) (assembler-dump-program asm port)
(values (get-bv) symbols)))) (get-bv))))
(define the-debugger #f) (define the-debugger #f)
(define (debug) (define (debug)
(shift k (shift k
(if (((debugger-breakpoints the-debugger) 'ref) (vm-pc (debugger-vm the-debugger))) (if (or (debugger-stepping? the-debugger)
(((debugger-breakpoints the-debugger) 'ref) (vm-pc (debugger-vm the-debugger))))
(debugger-continuation-set! the-debugger k) (debugger-continuation-set! the-debugger k)
(k)))) (k))))
(define vm (make-vm #:debugger debug)) (define vm (make-vm #:debugger debug))
(vm-load-program! vm prgm) (vm-load-program! vm prgm)
(set! the-debugger (make-debugger vm source (make-breakpoints symbols) #f)) (set! the-debugger (make-debugger vm asm (make-breakpoints asm) #f #f))
(debugger-breakpoint-add! the-debugger 1) (debugger-breakpoint-add! the-debugger 1)
(reset (run-vm vm)) (reset (run-vm vm))
the-debugger) the-debugger)
(define (debugger-continue debugger) (define (debugger-continue debugger)
(debugger-stepping-set! debugger #f)
((debugger-continuation debugger)))
(define (debugger-step debugger)
(debugger-stepping-set! debugger #t)
((debugger-continuation debugger))) ((debugger-continuation debugger)))
(define (debugger-breakpoint-add! debugger breakpoint) (define (debugger-breakpoint-add! debugger breakpoint)

View File

@@ -0,0 +1,59 @@
(define-module (scmvm language assembly)
#:use-module (scmvm vm)
#:use-module (scmvm assembler)
#:use-module (srfi srfi-1)
#:use-module ((scheme base) #:select (write-bytevector))
#:export (assemble assemble-instructions))
(define *aliases*
'((if . branch)))
(define (or-alias inst)
(or (assq-ref *aliases* inst) inst))
(define (lookup-instruction inst)
(define inst-obj (assq (or-alias inst) *instruction-set*))
(or inst-obj
(error (format #f "could not find instruction ~a" inst))))
(define label? (negate pair?))
(define (variable? x)
(and (pair? x) (eq? (car x) 'variable)))
(define (ref? x)
(and (pair? x) (eq? (car x) 'ref)))
(define (set!? x)
(and (pair? x) (eq? (car x) 'set!)))
(define (push? x)
(and (pair? x) (eq? (car x) 'push)))
(define (emit-push asm v)
(emit-instruction asm 'push)
(if (number? v)
(emit-literal asm v)
(emit-reference asm v)))
(define (assemble-instructions asm inst-seq)
(when (pair? inst-seq)
(cond
[(label? (car inst-seq))
(emit-label asm (car inst-seq))]
[(variable? (car inst-seq))
(emit-label asm (second (car inst-seq)))
(emit-literal asm (third (car inst-seq)))]
[(ref? (car inst-seq))
(emit-push asm (second (car inst-seq)))
(emit-instruction asm '@)]
[(set!? (car inst-seq))
(emit-push asm (second (car inst-seq)))
(emit-instruction asm '!)]
[(push? (car inst-seq))
(emit-push asm (second (car inst-seq)))]
[else
(emit-instruction asm (instruction-name (lookup-instruction (first (car inst-seq)))))])
(assemble-instructions asm (cdr inst-seq))))
(define (assemble instructions port)
(define asm (make-assembler))
(assemble-instructions asm instructions)
(finalize-references asm)
(assembler-dump-program asm port))

194
scmvm/language/scheme.scm Normal file
View File

@@ -0,0 +1,194 @@
(define-module (scmvm language scheme)
#:use-module (scmvm assembler)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:use-module (ice-9 match)
#:export (compile decompile ir-convert))
;; Scheme compiler
;; Scheme subset we're targeting
;; <prgm> ::= <top> ...
;; <top> ::= <def> | <exp>
;; <def> ::= (define <var> <exp>)
;; | (define (<var> <var> ...) <exp> ...)
;; <exp> ::= (lambda (<var> ...) <exp> ...)
;; | (if <exp> <exp> <exp>)
;; | (<exp> <exp> ...)
;; | (let ((<var> <exp>) ...) <exp> ...)
;; | (begin <exp> ...)
;; | <num> | <sym> | <var> | #t | #f
(define (atomic? x)
(or (number? x)
(symbol? x)
(boolean? x)))
(define (primitive? x)
(memq x '(+ - * / = < > <= >=)))
(define-syntax-rule (define-cps-loop name unit)
(define (name v* k)
(if (null? v*)
(k '())
(unit (car v*)
(lambda (t)
(name (cdr v*)
(lambda (t*)
(k (cons t t*)))))))))
;; Desugaring
;; Transforms to simplify the language
;; - lambdas and lets can only have 1 expression in body position
;; - define is always simple binds, function defs bind a lambda
;; <prgm> ::= <top> ...
;; <top> ::= <def> | <exp>
;; <def> ::= (define <var> <exp>)
;; <exp> ::= (lambda (<var> ...) <exp>)
;; | (if <exp> <exp> <exp>)
;; | (<exp> <exp> ...)
;; | (let ((<var> <exp>) ...) <exp>)
;; | (begin <exp> ...)
;; | <num> | <sym> | <var> | #t | #f
(define (desugar-prgm prgm)
(map (lambda (top)
(if (and (pair? top) (eq? (car top) 'define))
(desugar-define top)
(desugar-exp top)))
prgm))
(define (desugar-define def)
(match def
[`(define ,(name params ...) . ,e*)
`(define ,name ,(desugar-exp `(lambda ,params ,@e*)))]
[`(define ,name ,exp)
`(define ,name ,(desugar-exp exp))]))
(define (desugar-exp exp)
(match exp
[`(lambda ,params . ,body)
`(lambda ,params ,(desugar-body body))]
[`(if ,exp1 ,exp2 ,exp3)
`(if ,(desugar-exp exp1) ,(desugar-exp exp2) ,(desugar-exp exp3))]
[`(,f . ,args)
`(,(desugar-exp f) ,@(map desugar-exp args))]
[`(let ,((v* e*) ...) . ,body)
`(let (,(map (lambda (v e) `(,v ,(desugar-exp e))) v* e*))
,(desugar-body body))]
[`(begin . ,body) (desugar-body body)]
[(? atomic?) exp]))
(define (desugar-body body)
(match body
['() '()]
[(e) (desugar-exp e)]
[(e* ...) `(begin ,@(map desugar-exp e*))]))
;; CPS conversion
;; Re-structure the program into "Continuation Passing Style", where non-atomic
;; expressions must pass their continuations explicitly, changing to a very
;; "lambda-like" format
;; - begin expressions are decomposed
;; - let expressions are transformed into closed function applications
;; <prgm> ::= <top> ...
;; <top> ::= <def> | <exp>
;; <def> ::= (define <var> <exp>)
;; <exp> ::= <aexp>
;; | <cexp>
;; <cexp> ::= (<aexp> <aexp> ...)
;; | (if <aexp> <cexp> <cexp>)
;; | (set-then! <var> <aexp> <cexp>)
;; | (define-then! <var> <aexp> <cexp>)
;; <aexp> ::= (lambda (<var> ...) exp)
;; | <num> | <var> | #t | #f
;;
;; We choose a hybrid transformation based on https://matt.might.net/articles/cps-conversion/
(define undefined-value (make-symbol "undefined"))
(define (M expr)
;; M dispatches to the appropriate transformer
(match expr
[('lambda (var ...) e)
(let ([$k (gensym "$k")])
`(lambda (,@var ,$k) ,(T-c e $k)))]
[(? atomic?) expr]))
(define (T-k expr k)
;; T-k takes an explicit continuation and calls it when done
;; As an invariant, T-k cannot nest a T-c call directly
(match expr
[`(lambda . ,_) (k (M expr))]
[ (? atomic?) (k (M expr))]
[ ('define v e) (T-k `(define-then! ,v ,e) k)]
[ ('begin e) (T-k e k)]
[ ('begin e e* ...)
(T-k e (lambda _ (T-k `(begin ,@e*) k)))]
[ ('let ([v* e*] ...) body)
(T-k `((lambda (,@v*) ,body) ,@e*) k)]
[ ('if exp1 exp2 exp3)
(T-k exp1 (lambda ($exp1)
`(if ,$exp1
,(T-k exp2 k)
,(T-k exp3 k))))]
[ ('set! var expr)
(T-k expr (lambda ($expr)
`(set-then! ,var ,$expr ,(k undefined-value))))]
[((? primitive? f) e* ...)
(let* ([$rv (gensym "$rv")]
[cont `(lambda (,$rv) ,(k $rv))])
(T*-k e* (lambda ($e*)
`((cps ,f) ,@$e* ,cont))))]
[(f e* ...)
(let* ([$rv (gensym "$rv")]
[cont `(lambda (,$rv) ,(k $rv))])
(T-k f (lambda ($f)
(T*-k e* (lambda ($e*)
`(,$f ,@$e* ,cont))))))]))
(define (T-c expr c)
;; T-c takes a symbolic continuation, and uses it to construct CPS
(match expr
[`(lambda . ,_) `(,c ,(M expr))]
[ (? atomic?) `(,c ,(M expr))]
[ ('define v e) (T-c `(define-then! ,v ,e) c)]
[ ('begin e) (T-c e c)]
[ ('begin e e* ...)
(T-k e (lambda _ (T-c `(begin ,@e*) c)))]
[ ('let ([v* e*] ...) body)
(T-c `((lambda (,@v*) ,body) ,@e*) c)]
[ ('if exp1 exp2 exp3)
(let ([$k (gensym "$k")]) ;; Bind cont to avoid blow up
`((lambda (,$k)
,(T-k exp1 (lambda (aexp)
`(if ,aexp
,(T-c exp2 $k)
,(T-c exp3 $k)))))
,c))]
[ ('set! var expr)
(T-k expr (lambda ($expr)
`(set-then! ,var ,$expr (,c ,undefined-value))))]
[ ((? primitive? f) e* ...)
(T*-k e* (lambda ($e*)
`((cps ,f) ,@$e* ,c)))]
[ (f e* ...)
(T-k f (lambda ($f)
(T*-k e* (lambda ($e*)
`(,$f ,@$e* ,c)))))]))
(define (cps-convert-prgm prgm)
(T-c `(begin ,@prgm) 'ktail))
(define-cps-loop T*-k T-k)
(define (ir-convert prgm)
(cps-convert-prgm (desugar-prgm prgm)))
;; Useful for testing
;; (define (cps prim)
;; (lambda vars
;; (let* ([rev (reverse vars)]
;; [k (car rev)]
;; [args (reverse (cdr rev))])
;; (k (apply prim args)))))

View File

@@ -6,13 +6,14 @@
#:use-module (srfi srfi-9) #:use-module (srfi srfi-9)
#:use-module (srfi srfi-26) #:use-module (srfi srfi-26)
#:use-module (srfi srfi-43) #:use-module (srfi srfi-43)
#:use-module (ice-9 format)
#:export (make-stack (push . stack-push) (pop . stack-pop) (peek . stack-peek) stack-ref stack->list #:export (make-stack (push . stack-push) (pop . stack-pop) (peek . stack-peek) stack-ref stack->list
(make-vm* . make-vm) run-vm (make-vm* . make-vm) run-vm
vm-memory-ref vm-memory-byte-ref vm-memory-set! vm-memory vm-load-program! vm-memory-ref vm-memory-byte-ref vm-memory-set! vm-memory vm-load-program!
vm-data-stack vm-ret-stack vm-data-stack vm-ret-stack
vm-debugger vm-debugger-set! vm-debugger vm-debugger-set!
vm-pc vm-pc-set! vm-pc vm-pc-set!
*instruction-set* instruction-type instruction-code)) *instruction-set* instruction-name instruction-code))
;;; Data Structures ;;; Data Structures
(define *stack-size* 512) (define *stack-size* 512)
@@ -57,6 +58,9 @@
[(->list) [(->list)
(lambda () (lambda ()
(reverse-vector->list the-stack 0 top))] (reverse-vector->list the-stack 0 top))]
[(set!)
(lambda (k obj)
(vector-set! the-stack k obj))]
[else (error "stack dispatch unknown value")]))) [else (error "stack dispatch unknown value")])))
(define (push stack v) (define (push stack v)
@@ -80,6 +84,9 @@
(define (stack->list stack) (define (stack->list stack)
((stack '->list))) ((stack '->list)))
(define (stack-set! stack k obj)
((stack 'set!) k obj))
;;; IO ;;; IO
(define (read-word) (define (read-word)
@@ -114,12 +121,18 @@
(swap #x17) (swap #x17)
(rot #x18) (rot #x18)
(over #x19) (over #x19)
(not #x1a)
(set! #x1b)
(bye #xff))) (bye #xff)))
(define instruction-name car)
(define instruction-code cadr) (define instruction-code cadr)
(define (op-lookup code) (define (op-lookup code)
(car (find (lambda (x) (= (instruction-code x) code)) *instruction-set*))) (let ([op (find (lambda (x) (= (instruction-code x) code)) *instruction-set*)])
(if op
(car op)
(error (format #f "tried to execute non-existant instruction ~x" code)))))
(define (binop-lookup op) (define (binop-lookup op)
(case (op-lookup op) (case (op-lookup op)
@@ -129,7 +142,8 @@
[(or) logior] [(or) logior]
[(nand) (compose lognot logand)] [(nand) (compose lognot logand)]
[(nor) (compose lognot logior)] [(nor) (compose lognot logior)]
[(xor) logxor])) [(xor) logxor]
[(not) lognot]))
(define (relop-lookup op) (define (relop-lookup op)
(case (op-lookup op) (case (op-lookup op)
@@ -164,7 +178,7 @@
(define ram-word-set! (cute vm-memory-set! vm <> <>)) (define ram-word-set! (cute vm-memory-set! vm <> <>))
(define debugger (vm-debugger vm)) (define debugger (vm-debugger vm))
(define exit? #f) (define exit? #f)
(define (jump x) (vm-pc-set! vm x)) (define (jump x) (vm-pc-set! vm (logand #x2fffffff x)))
(define (fetch-byte) (define (fetch-byte)
(let ([byte (ram-byte-ref (vm-pc vm))]) (let ([byte (ram-byte-ref (vm-pc vm))])
(vm-pc-set! vm (+ (vm-pc vm) 1)) (vm-pc-set! vm (+ (vm-pc vm) 1))
@@ -236,6 +250,11 @@
(push data-stack b) (push data-stack b)
(push data-stack a) (push data-stack a)
(push data-stack b))] (push data-stack b))]
[(set!)
;; use let* to induce an order of evaluation
(let* ([idx (pop data-stack)]
[obj (pop data-stack)])
(stack-set! data-stack idx obj))]
[(bye) (set! exit? #t)]) [(bye) (set! exit? #t)])
(when (not exit?) (when (not exit?)
(run-vm vm))) (run-vm vm)))

View File

@@ -2,13 +2,15 @@
(scmvm assembler) (scmvm assembler)
(scmvm vm) (scmvm vm)
(scmvm debugger) (scmvm debugger)
(scmvm language assembly)
(scmvm language scheme)
(rnrs bytevectors) (rnrs bytevectors)
(rnrs io ports) (rnrs io ports)
((scheme base) ((scheme base)
#:select (open-output-bytevector get-output-bytevector))) #:select (open-output-bytevector get-output-bytevector)))
;;; Data ;;; Data
(define adder-program-asm (define adder-program-assembly
'((variable result 0) '((variable result 0)
(push 1) (push 1)
(push 2) (push 2)
@@ -17,7 +19,7 @@
(!) (!)
(bye))) (bye)))
(define fib-program-asm (define fib-program-assembly
'( (variable result 0) '( (variable result 0)
(ref result) (ref result)
(push fib) (push fib)
@@ -103,17 +105,16 @@
#x02 ; Store fib(n) #x02 ; Store fib(n)
#xff ; Exit program #xff ; Exit program
)) ))
;;; Tests ;;; Tests
(define-test-suite "assembler" (define-test-suite "assembly"
(define-test "adder" (define-test "adder"
(define out (open-output-bytevector)) (define out (open-output-bytevector))
(assemble adder-program-asm out) (assemble adder-program-assembly out)
(assert-equal adder-program-bytecode (get-output-bytevector out))) (assert-equal adder-program-bytecode (get-output-bytevector out)))
(define-test "fib" (define-test "fib"
(define out (open-output-bytevector)) (define out (open-output-bytevector))
(assemble fib-program-asm out) (assemble fib-program-assembly out)
(assert-equal fib-program-bytecode (get-output-bytevector out)))) (assert-equal fib-program-bytecode (get-output-bytevector out))))
(define-test-suite "vm" (define-test-suite "vm"
@@ -133,6 +134,9 @@
(define-test-suite "debugger" (define-test-suite "debugger"
(define-test "modify-running-program" (define-test "modify-running-program"
(define fib-program-asm (make-assembler))
(assemble-instructions fib-program-asm fib-program-assembly)
(finalize-references fib-program-asm)
(define my-debugger (make-debugger fib-program-asm)) (define my-debugger (make-debugger fib-program-asm))
(define my-vm (debugger-vm my-debugger)) (define my-vm (debugger-vm my-debugger))
(define my-data (vm-data-stack my-vm)) (define my-data (vm-data-stack my-vm))
@@ -144,4 +148,22 @@
(stack-pop my-data) (stack-pop my-data)
(stack-push my-data 1) (stack-push my-data 1)
(debugger-continue my-debugger) (debugger-continue my-debugger)
(assert-equal 1 (vm-memory-ref my-vm 1)))) (assert-equal 1 (vm-memory-ref my-vm 1)))
(define-test "stepping"
(define fib-program-asm (make-assembler))
(assemble-instructions fib-program-asm fib-program-assembly)
(finalize-references fib-program-asm)
(define my-debugger (make-debugger fib-program-asm))
(define my-vm (debugger-vm my-debugger))
(vm-memory-set! my-vm 1 10)
(vm-pc-set! my-vm 5)
(debugger-breakpoint-add! my-debugger 'fib)
(debugger-continue my-debugger)
(assert-equal 23 (vm-pc my-vm))
(debugger-step my-debugger)
(assert-equal 24 (vm-pc my-vm)) ;; dup is a 1 byte instruction
(debugger-step my-debugger)
(assert-equal 29 (vm-pc my-vm)) ;; push is a 5 byte instruction
(debugger-continue my-debugger)
(assert-equal 23 (vm-pc my-vm)) ;; continue stops stepping
))