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