1#| doc
2Compile AST to a code instruction tree suitable for assembly
3|#
4
5(define-library (owl eval compile)
6
7   (export
8      compile)
9
10   (import
11      (owl core)
12      (owl math)
13      (owl list)
14      (owl bytevector)
15      (only (owl syscall) error)
16      (owl function)
17      (owl symbol)
18      (owl list-extra)
19      (owl eval ast)
20      (owl lazy)
21      (owl sort)
22      (owl primop)
23      (owl io)
24      (only (owl eval env) primop-of)
25      (owl eval assemble)
26      (owl eval data)
27      (owl eval closure))
28
29   (begin
30
31      (define try-n-perms 1000) ;; how many load permutations to try before evicting more registers
32
33       ; regs = (node ...), biggest id first
34      ; node = #(var <sym> id)
35      ;      = #(val <value> id)
36      ;      = #(env <regs> id)
37      ;      = #(lit <values> id)
38
39      ; [r0 = MCP] [r1 = Clos] [r2 = Env] [r3 = a0, often cont] [r4] ... [rn]
40
41      (define a0 3) ;;; number of first argument register (may change)
42
43      (define (next-free-register regs)
44         (if (null? regs)
45            a0
46            (+ (ref (car regs) 3) 1)))
47
48      ; get index of thing at (future) tuple
49      ; lst = (l0 l1 ... ln) -> #(header <code/proc> l0 ... ln)
50      (define (index-of thing lst pos)
51         (cond
52            ((null? lst) #false)
53            ((eq? (car lst) thing) pos)
54            (else (index-of thing (cdr lst) (+ pos 1)))))
55
56      (define (find-any regs sym type subtype)
57         (if (null? regs)
58            #false
59            (let ((this (car regs)))
60               (cond
61                  ((and (eq? type (ref this 1))
62                     (eq? (ref this 2) sym))
63                     (ref this 3))
64                  ((eq? subtype (ref this 1))
65                     (or
66                        (find-any (cdr regs) sym type subtype)
67                        (let
68                           ((sub
69                              (index-of sym (ref this 2) 2)))
70                           ;; FIXME, 2 will not be correct for shared envs
71                           (if sub
72                              (cons (ref this 3) sub)
73                              #false))))
74                  (else
75                     (find-any (cdr regs) sym type subtype))))))
76
77      ;; find which register has the literals-tuple
78      (define (find-literals env)
79         (if (null? env)
80            (error "No literals found: " env)
81            (tuple-case (car env)
82               ((lit vals id)
83                  id)
84               (else
85                  (find-literals (cdr env))))))
86
87      ;; find a register or an env address containing the thing
88      (define (find-variable regs var)
89         (find-any regs var 'var 'env))
90
91      ;; find a register or address to literals where it can be found
92      (define (find-value regs var)
93         (find-any regs var 'val 'lit))
94
95      (define (rtl-value regs val cont)
96         (let ((position (find-value regs val)))
97            (cond
98               ((fixnum? position)
99                  (cont regs position))
100               ((small-value? val)
101                  (let ((reg (next-free-register regs)))
102                     (tuple 'ld val reg
103                        (cont (cons (tuple 'val val reg) regs) reg))))
104               ((not position)
105                  (error "rtl-value: cannot make a load for a " val))
106               ((fixnum? (cdr position))
107                  (let ((this (next-free-register regs)))
108                     (tuple 'refi (car position) (cdr position) this
109                        (cont (cons (tuple 'val val this) regs) this))))
110               (else
111                  (error "tried to use old chain load in " val)))))
112
113      (define (rtl-variable regs sym cont)
114         (let ((position (find-variable regs sym)))
115            (cond
116               ((fixnum? position)
117                  (cont regs position))
118               ((not position)
119                  (error "rtl-variable: cannot find the variable " sym))
120               ((fixnum? (cdr position))
121                  (let ((this (next-free-register regs)))
122                     (tuple 'refi (car position) (cdr position) this
123                        (cont (cons (tuple 'var sym this) regs) this))))
124               (else
125                  (error "no chain load: " position)))))
126
127
128      (define (rtl-close regs lit-offset env lit cont)
129         (let ((this (next-free-register regs)))
130            (cond
131               ((null? env)
132                  ;; no need to close, just refer the executable procedure
133                  (tuple 'refi (find-literals regs) lit-offset this
134                     (cont
135                        (cons (tuple 'val (list 'a-closure) this) regs)
136                        this)))
137               ((null? lit)
138                  ;; the function will be of the form
139                  ;; #(closure-header <code> e0 ... en)
140                  (tuple 'cons-close #false (find-literals regs) lit-offset env this
141                     (cont
142                        (cons (tuple 'val (list 'a-closure) this) regs)
143                        this)))
144               (else
145                  ;; the function will be of the form
146                  ;; #(clos-header #(proc-header <code> l0 .. ln) e0 .. em)
147                  (tuple 'cons-close #true (find-literals regs) lit-offset env this
148                     (cont
149                        (cons (tuple 'val (list 'a-closure) this) regs)
150                        this))))))
151
152      (define (env->loadable env)
153         (map
154            (λ (x)
155               (if (symbol? x)
156                  (tuple 'var x)
157                  (error "Cannot yet load this env node: " env)))
158            env))
159
160      (define (create-alias regs name position)
161         (let ((this (car regs)))
162            (if (eq? (ref this 3) position)
163               (cons (tuple 'var name position) regs)
164               (cons this
165                  (create-alias (cdr regs) name position)))))
166
167      (define (create-aliases regs names positions)
168         (fold (λ (regs alias) (create-alias regs (car alias) (cdr alias)))
169            regs (zip cons names positions)))
170
171      (define (rtl-arguments one?)
172
173         (define (one regs a cont)
174            (tuple-case a
175               ((value val)
176                  (rtl-value regs val cont))
177               ((var sym)
178                  (rtl-variable regs sym cont))
179               ((make-closure lpos env lit)
180                  (many regs (env->loadable env) #n
181                     (λ (regs envp)
182                        (rtl-close regs lpos envp lit cont))))
183               (else
184                  (error "rtl-simple: unknown thing: " a))))
185
186         (define (many regs args places cont)
187            (if (null? args)
188               (cont regs (reverse places))
189               (one regs (car args)
190                  (λ (regs pos)
191                     (many regs (cdr args) (cons pos places) cont)))))
192         (if one?
193            one
194            (λ (regs args cont)
195               (many regs args #n cont))))
196
197
198      (define rtl-simple (rtl-arguments #true))
199
200      (define rtl-args (rtl-arguments #false))
201
202      ; -> [reg] + regs'
203      (define (rtl-bind regs formals)
204         (let loop ((regs regs) (formals formals) (taken #n))
205            (if (null? formals)
206               (tuple (reverse taken) regs)
207               (let ((this (next-free-register regs)))
208                  (loop
209                     (cons (tuple 'var (car formals) this) regs)
210                     (cdr formals)
211                     (cons this taken))))))
212
213      ;; fixme: mkt chugs the type to the instruction
214      (define (rtl-primitive regs op formals args cont)
215         (if (eq? op 23) ; generalize this later. mkt is not a safe instruction!
216            (if (null? args)
217               (error "rtl-primitive: no type for mkt" args)
218               (rtl-primitive regs
219                  (fxior (<< op 8) (band (value-of (car args)) #xff))
220                  formals (cdr args) cont))
221            (rtl-args regs args
222               (λ (regs args)
223                  ;; args = input registers
224                  (cond
225                     ;; a run-of-the-mill a0 .. an → rval -primop
226                     ((and (= (length formals) 1) (not (special-bind-primop? op)))
227                        (let ((this (next-free-register regs)))
228                           (tuple 'prim op args this
229                              (cont
230                                 (cons
231                                    (tuple 'var (car formals) this)
232                                    regs)))))
233                     (else
234                        ; bind or ff-bind, or arithmetic
235                        (bind (rtl-bind regs formals)
236                           (λ (selected regs)
237                              (tuple 'prim op args selected
238                                 (cont regs))))))))))
239
240
241      (define (rtl-make-moves sequence tail)
242         (foldr
243            (λ (move rest)
244               (if (eq? (car move) (cdr move))
245                  rest
246                  (tuple 'move (car move) (cdr move) rest)))
247            tail sequence))
248
249      (define (rtl-moves-ok? moves)
250         (cond
251            ((null? moves) #true)
252            ((assq (cdar moves) (cdr moves))
253               #false)
254            (else
255               (rtl-moves-ok? (cdr moves)))))
256
257      ;;; (from ...) -> ((from . to) ...)
258      (define (rtl-add-targets args)
259         (zip cons args
260            (iota a0 1 (+ (length args) a0))))
261
262      (define (rtl-safe-registers n call)
263         (let loop
264            ((hp (+ (length call) (+ a0 1)))
265             (safe #n)
266             (n n))
267            (cond
268               ((= n 0)
269                  (reverse safe))
270               ((memq hp call)
271                  (loop (+ hp 1) safe n))
272               (else
273                  (loop (+ hp 1) (cons hp safe) (- n 1))))))
274
275      ;;; -> replace the to-save registers in call
276      (define (apply-saves to-save safes call)
277         (let ((new (zip cons to-save safes)))
278            (map
279               (λ (reg)
280                  (let ((node (assq reg new)))
281                     (if node (cdr node) reg)))
282               call)))
283
284
285      (define (rtl-check-moves perms n)
286         (call/cc
287            (λ (ret)
288               (lfor 0 perms
289                  (λ (nth perm)
290                     (cond
291                        ((rtl-moves-ok? perm) (ret perm))
292                        ((eq? nth try-n-perms) (ret #false))
293                        (else (+ nth 1)))))
294                  #false)))
295
296      ;;; find the first set of saves that works
297      (define (rtl-try-saves saves free call rest)
298         (lets
299            ((call-prime (apply-saves saves free call))
300             (call-prime (rtl-add-targets call-prime))
301             (call-prime
302               (remove
303                  (λ (move) (eq? (car move) (cdr move)))
304                  call-prime))
305             (call-prime (sort (λ (a b) (< (car a) (car b))) call-prime))
306             (perms (permutations call-prime))
307             (ok-moves (rtl-check-moves perms 1)))
308            (if ok-moves
309               (rtl-make-moves
310                  (append (zip cons saves free) ok-moves)
311                  rest)
312               #false)))
313
314      (define (rtl-make-jump call free rest)
315         (call/cc
316            (λ (ret)
317               (or
318                  (lfor #false (subsets call)
319                     (λ (foo subset)
320                        (cond
321                           ((rtl-try-saves subset free call rest) => ret)
322                           (else #false))))
323                  ; has never happened in practice
324                  (error "failed to compile call: " call)))))
325
326      (define (rtl-jump rator rands free inst)
327         (let ((nargs (length rands)))
328            (cond
329               ;; cont is usually at 3, and usually there is
330               ;; 1 return value -> special instruction
331               ((and (eq? rator a0) (= nargs 1))
332                  (tuple 'ret (car rands)))
333               ;;; rator is itself in rands, and does not need rescuing
334               ((memq rator rands)
335                  (rtl-make-jump rands free
336                     (tuple (or inst 'goto) (index-of rator rands a0) nargs)))
337               ;;; rator is above rands, again no need to rescue
338               ((> rator (+ 2 nargs))
339                  (rtl-make-jump rands free
340                     (if inst
341                        (tuple inst rator nargs)
342                        (tuple 'goto rator (length rands)))))
343               (else
344                  (tuple 'move rator (car free)
345                     (rtl-jump (car free) rands (cdr free) inst))))))
346
347      (define (known-arity obj type)
348         (let ((op (ref obj 0)))
349            (if (eq? op 60) ;; fixed arity, new instruction
350               (tuple type (ref obj 1))
351               (begin
352                  ; (print "no " op)
353                  (tuple type #false)))))
354
355      ;; value-to-be-called → #(<functype> <arity>) | #false = don't know, just call and see what happens at runtime
356      (define (fn-type obj)
357         ;; known call check doesn't work as such anymore (arity check can fail in other branches and most common case is not handled) so disabled for now
358         ;; resulting in all calls going via a regular call instruction
359         (let ((t (type obj)))
360            (cond
361               ((eq? type-bytecode t) ;; raw bytecode
362                  (known-arity obj 'code))
363               ((eq? t type-proc)
364                  (known-arity (ref obj 1) 'proc))
365               ((eq? t type-clos)
366                  (known-arity (ref (ref obj 1) 1) 'clos))
367               (else
368                  (tuple 'bad-fn 0))))
369         ; #false
370         )
371
372      (define (arity-fail op wanted would-get)
373         (error "Would be an error: " (list op 'wants wanted 'but 'would 'get would-get 'arguments)))
374
375      ; rator nargs → better call opcode | #false = no better known option, just call | throw error if bad function or arity
376      ;; currently only checks arity, since goto-* are no currently missing from vm
377      (define (rtl-pick-call regs rator nargs)
378         (tuple-case rator
379            ((value rator)
380               (tuple-case (fn-type rator) ;; <- fixme, can be enabled again
381                  ((code n)
382                     (if (or (not n) (eq? n nargs))
383                        ;'goto-code
384                        #false
385                        (arity-fail rator n nargs)))
386                  ((proc n)
387                     (if (or (not n) (eq? n nargs))
388                        ; 'goto-proc
389                        #false
390                        (arity-fail rator n nargs)))
391                  ((clos n)
392                     (if (or (not n) (eq? n nargs))
393                        ; 'goto-clos
394                        #false
395                        (arity-fail rator n nargs)))
396                  (else
397                     ;; operator type not known at compile time
398                     (error "bad operator: " rator)
399                     #false)))
400            (else
401               ; (print "non value call " rator)
402               ; (print "ENV:")
403               ; (for-each (λ (x) (print " - " x)) regs)
404               #false)))
405
406      (define (rtl-call regs rator rands)
407         ; rator is here possibly #(value #<func>) and much of the call can be inlined
408         ; change the flag if can check call here
409         (rtl-args regs (cons rator rands)
410            (λ (regs all)
411               (let ((free (rtl-safe-registers (length all) all)))
412                  (rtl-jump (car all) (cdr all) free
413                     (rtl-pick-call regs rator (length rands)))))))
414
415      (define (value-simple? val)
416         (tuple-case val
417            ((value val) (simple-value? val))
418            (else #f)))
419
420      (define (simple-first a b cont)
421         (if (value-simple? b)
422            (cont b a)
423            (cont a b)))
424
425      (define (extract-value node)
426         (tuple-case node
427            ((value val) val)
428            (else #false)))
429
430
431      ;; compile any AST node node to RTL
432      (define (rtl-any regs exp)
433         (tuple-case exp
434            ((branch kind a b then else)
435               (cond
436                  ((eq? kind 0)      ; branch on equality (jump if equal)
437                     (simple-first a b
438                        ;;; move simple to a, if any
439                        (λ (a b)
440                           (if-lets ((i (value-simple? a)))
441                              (rtl-simple regs b
442                                 (λ (regs bp)
443                                    (let
444                                       ((then (rtl-any regs then))
445                                        (else (rtl-any regs else)))
446                                       (tuple 'jeqi i bp then else))))
447                              (rtl-simple regs a
448                                 (λ (regs ap)
449                                    (rtl-simple regs b (λ (regs bp)
450                                       (let
451                                          ((then (rtl-any regs then))
452                                           (else (rtl-any regs else)))
453                                          (tuple 'jeq ap bp then else))))))))))
454                  (else
455                     (error "rtl-any: unknown branch type: " kind))))
456            ((call rator rands)
457               ;; compile as primop call, bind if rator is lambda or a generic call
458               (let ((op (and (eq? (ref rator 1) 'value) (primop-of (ref rator 2)))))
459                  (if op
460                     (tuple-case (car rands)
461                        ((lambda-var fixed? formals body)
462                           (if (and fixed? (opcode-arity-ok? op (length (cdr rands))))
463                              (rtl-primitive regs op formals (cdr rands)
464                                 (C rtl-any body))
465                              ;; fixme: should be a way to show just parts of AST nodes, which may look odd
466                              (error "Bad number of arguments for primitive: "
467                                 (list 'op (primop-name op) 'got (length (cdr rands)) 'arguments))))
468                        (else
469                           (error "bad primitive args: " rands)))
470                     (tuple-case rator
471                        ((lambda-var fixed? formals body)
472                           ;; ((lambda (args) ...) ...) => add new aliases for values
473                           (if fixed?
474                              (rtl-args regs rands
475                                 (λ (regs args)
476                                    ;;; note that this is an alias thing...
477                                    (if (= (length formals) (length args))
478                                       (rtl-any (create-aliases regs formals args) body)
479                                       (error "Bad argument count in lambda call: " (list 'args args 'formals formals)))))
480                              (rtl-call regs rator rands)))
481                        (else
482                           (rtl-call regs rator rands))))))
483            (else
484               (error "rtl-any: wtf: " exp))))
485
486      (define (formals->regs formals pos)
487         (if (null? formals)
488            #n
489            (cons (tuple 'var (car formals) pos)
490               (formals->regs (cdr formals) (+ pos 1)))))
491
492      ; r0 = mcp, r1 = clos, r2 = lit, r3 aka a0 = arg0, r4 = arg1, ...
493
494      (define (entry-regs clos literals formals)
495         (append
496            (reverse (formals->regs formals a0))
497            (if (null? clos)
498               (list
499                  (tuple 'env #n 2)          ; <- really just empty
500                  (tuple 'lit literals 1))   ; <- may be empty
501               (list
502                  (tuple 'lit literals 2)    ; <- may be empty
503                  (tuple 'env clos 1)))))
504
505      ;;; closure -> executable procedure (closed from elsewhere if not independent)
506
507      (define (rtl-literal rtl thing)
508         (if (uncompiled-closure? thing)
509            (rtl (cdr thing))
510            thing))
511
512      ; code .. → code' ...
513      (define (rtl-literals rtl-procedure lits)
514         ;;; convert all uncompiled closures to procedures
515         (map (H rtl-literal rtl-procedure) lits))
516
517      (define (list->proc lst)
518         (listuple type-proc (length lst) lst))
519
520      ;; rtl-procedure now passes the intended new form here - replace it later in the AST node also
521      (define (rtl-plain-lambda rtl exp clos literals tail)
522         (tuple-case exp
523            ((lambda-var fixed? formals body)
524               (let
525                  ((exec
526                     (assemble-code
527                        (tuple 'code-var fixed?
528                           (length formals)
529                           (rtl-any (entry-regs clos literals formals) body))
530                        tail)))
531                  (if (null? literals)
532                     exec ; #<bytecode>
533                     (list->proc (cons exec literals)))))
534            (else
535               (error "rtl-plain-lambda: bad node " exp))))
536
537      ;; temporary back-conversion for jump compiling
538      (define (bytecode->list thing)
539         (cond
540            ((bytecode? thing)
541               (bytevector->list thing))
542            ((function? thing)
543               ;; get the bytecode
544               (bytecode->list (ref thing 1)))
545            (else
546               (error "bytecode->list: " thing))))
547
548      ;; todo: control flow analysis time - if we can see what the arguments are here, the info could be used to make most continuation returns direct via known call opcodes, which could remove an important branch prediction killer
549      ;;; proc = #(procedure-header <code-ptr> l0 ... ln)
550      ; env node → env' owl-func
551      (define (rtl-procedure node)
552         (tuple-case node
553            ((closure-var fixed? formals body clos literals)
554               (rtl-plain-lambda rtl-procedure
555                  (tuple 'lambda-var fixed? formals body)
556                  clos (rtl-literals rtl-procedure literals) #n))
557            (else
558               (error "rtl-procedure: bad input: " node))))
559
560      ; exp → exp'
561      (define (rtl-exp exp)
562         (tuple-case exp
563            ((closure-var fixed? formals body clos literals)
564               (if (null? clos)
565                  (rtl-procedure exp)
566                  (error "rtl-exp: free variables in entry closure: " clos)))
567            (else
568               #false)))
569
570      ;; todo: exit via fail cont on errors
571      (define (compile exp env)
572         (ok (rtl-exp exp) env))
573))
574