1#lang racket/base 2(require "match.rkt" 3 "wrap.rkt" 4 "gensym.rkt") 5 6;; Reduces closure allocation by lifting bindings that are only used 7;; in calls that have the right number of arguments. 8 9;; The output uses `letrec` to bind lifted and closed functions, while 10;; `letrec*` is still used for any other recursive binding. 11 12(provide lift-in-schemified-linklet 13 lift-in-schemified-body) 14 15;; An identifier registered in `lifts` is one of 16;; 17;; * `liftable` - a function binding that is (so far) only referenced 18;; in an application position with a correct number of 19;; arguments, so each call can supply the free 20;; variables of the function and the closure 21;; allocation (if any) can be lifted to the top level 22;; 23;; * `indirected` - a variable that is `set!`ed, which means that it can't be 24;; replaced by an argument if it appears as a free 25;; variable in a liftable function; instead, the 26;; argument must be a box 27;; 28;; There's nothing analogous to `mutator` and `var-ref` for 29;; synthesized accessors, because they're relevant only for the second 30;; pass and recorded in an `indirected`. 31;; 32;; The `lifts` table can also contain `lambda` and `case-lambda` forms 33;; mapped to '#:empty, meaning that the closure is empty relative to the 34;; enclosing linklet and can be lifted so that each is allocated once per 35;; linklet. 36;; 37;; An identifier registered in `locals` maps to either 'ready or 'early, 38;; where 'early is used during the right-hand side of a letrec that is 39;; not all `lambda`s. 40 41(struct liftable (expr ; a `lambda` or `case-lambda` RHS of the binding 42 [frees #:mutable] ; set of variables free in `expr`, plus any lifted bindings 43 [binds #:mutable])) ; set of variables bound in `expr` 44 45(struct indirected ([check? #:mutable])) 46 47(struct mutator (orig)) ; `orig` maps back to the original identifier 48(struct var-ref (orig)) ; ditto 49 50;; As we traverse expressions, we thread through free- and 51;; bound-variable sets 52(define empty-frees+binds (cons #hasheq() #hasheq())) 53 54(define (lift-in-schemified-linklet v [leave-loops-intact? #f]) 55 ;; Match outer shape of a linklet produced by `schemify-linklet` 56 ;; and lift in the linklet body: 57 (let loop ([v v]) 58 (match v 59 [`(lambda ,args . ,body) 60 (define new-body (lift-in-schemified-body body leave-loops-intact?)) 61 (if (for/and ([old (in-list body)] 62 [new (in-list new-body)]) 63 (eq? old new)) 64 v 65 `(lambda ,args . ,new-body))] 66 [`(let* ,bindings ,body) 67 (define new-body (loop body)) 68 (if (eq? body new-body) 69 v 70 `(let* ,bindings ,new-body))]))) 71 72(define (lift-in-schemified-body body [leave-loops-intact? #f]) 73 (for/list ([v (in-list body)]) 74 (lift-in-schemified v leave-loops-intact?))) 75 76(define (lift-in-schemified v leave-loops-intact?) 77 ;; Quick pre-check: do any lifts appear to be possible? 78 (define (lift-in? v) 79 (match v 80 [`(define ,_ ,rhs) 81 (lift-in-expr? rhs)] 82 [`(define-values ,_ ,rhs) 83 (lift-in-expr? rhs)] 84 [`(begin . ,vs) 85 (for/or ([v (in-wrap-list vs)]) 86 (lift-in? v))] 87 [`,_ (lift-in-expr? v)])) 88 89 (define (lift-in-expr? v) 90 (match v 91 [`(lambda ,_ . ,body) 92 (lift?/seq body)] 93 [`(case-lambda [,_ . ,bodys] ...) 94 (for/or ([body (in-list bodys)]) 95 (lift?/seq body))] 96 [`(let . ,_) (lift-in-let? v)] 97 [`(letrec . ,_) (lift-in-let? v)] 98 [`(letrec* . ,_) (lift-in-let? v)] 99 [`(let-values . ,_) (error 'internal-error "unexpected let-values")] 100 [`(letrec-values . ,_) (error 'internal-error "unexpected letrec-values")] 101 [`(begin . ,vs) 102 (for/or ([v (in-wrap-list vs)]) 103 (lift-in-expr? v))] 104 [`(if ,tst ,thn ,els) 105 (or (lift-in-expr? tst) (lift-in-expr? thn) (lift-in-expr? els))] 106 [`(with-continuation-mark* ,_ ,key ,val ,body) 107 (or (lift-in-expr? key) (lift-in-expr? val) (lift-in-expr? body))] 108 [`(quote ,_) #f] 109 [`(#%variable-reference . ,_) (error 'internal-error "unexpected variable reference")] 110 [`(set! ,_ ,rhs) 111 (lift-in-expr? rhs)] 112 [`(,_ ...) 113 (lift-in-seq? v)] 114 [`,_ #f])) 115 116 (define (lift-in-let? v) 117 (match v 118 [`(,_ ([,_ ,rhss] ...) . ,body) 119 (or (for/or ([rhs (in-list rhss)]) 120 (lift-in-expr? rhs)) 121 (lift-in-seq? body))])) 122 123 (define (lift-in-seq? vs) 124 (for/or ([v (in-wrap-list vs)]) 125 (lift-in-expr? v))) 126 127 ;; Under a `lambda`; any local bindings to functions or 128 ;; `[case-]lambda`s that might be closed? 129 (define (lift? v) 130 (match v 131 [`(let . ,_) (lift?/let v)] 132 [`(letrec . ,_) (lift?/let v)] 133 [`(letrec* . ,_) (lift?/let v)] 134 [`(let-values . ,_) (lift?/let v)] 135 [`(letrec-values . ,_) (lift?/let v)] 136 [`(lambda ,_ . ,body) #t #;(lift?/seq body)] 137 [`(case-lambda [,_ . ,bodys] ...) 138 #t 139 #; 140 (for/or ([body (in-list bodys)]) 141 (lift?/seq body))] 142 [`(begin . ,vs) (lift?/seq vs)] 143 [`(begin0 . ,vs) (lift?/seq vs)] 144 [`(begin-unsafe . ,vs) (lift?/seq vs)] 145 [`(quote . ,_) #f] 146 [`(if ,tst ,thn ,els) 147 (or (lift? tst) (lift? thn) (lift? els))] 148 [`(with-continuation-mark* ,_ ,key ,val ,body) 149 (or (lift? key) (lift? val) (lift? body))] 150 [`(set! ,_ ,rhs) (lift? rhs)] 151 [`(#%variable-reference) #f] 152 [`(#%variable-reference ,id) #f] 153 [`(,rator . ,rands) 154 (or (lift? rator) (lift?/seq rands))] 155 [`,_ #f])) 156 157 (define (lift?/let v) 158 (match v 159 [`(,_ ([,_ ,rhss] ...) . ,body) 160 (or (for/or ([rhs (in-list rhss)]) 161 (or (lambda? rhs) 162 (lift? rhs))) 163 (lift?/seq body))])) 164 165 (define (lift?/seq vs) 166 (for/or ([v (in-wrap-list vs)]) 167 (lift? v))) 168 169 ;; ---------------------------------------- 170 171 ;; Look for a `lambda` to lift out of: 172 (define (lift-in v) 173 (match v 174 [`(define ,id ,rhs) 175 (reannotate v `(define ,id ,(lift-in-expr rhs)))] 176 [`(define-values ,ids ,rhs) 177 (reannotate v `(define-values ,ids ,(lift-in-expr rhs)))] 178 [`(begin ,vs ...) 179 (reannotate v `(begin ,@(for/list ([v (in-wrap-list vs)]) 180 (lift-in v))))] 181 [`,_ (lift-in-expr v)])) 182 183 ;; Look for a `lambda` to lift out of: 184 (define (lift-in-expr v) 185 (match v 186 [`(lambda ,args . ,body) 187 (define lifts (make-hasheq)) 188 (define locals (add-args args #hasheq())) 189 (define frees+binds/ignored (compute-seq-lifts! body empty-frees+binds lifts locals)) 190 (define loops (if leave-loops-intact? 191 (find-seq-loops body lifts #hasheq() #hasheq()) 192 #hasheq())) 193 (let ([lifts (if (zero? (hash-count lifts)) 194 lifts 195 (close-and-convert-lifts lifts loops))]) 196 (cond 197 [(zero? (hash-count lifts)) v] 198 [else 199 (define empties (box null)) 200 (define lifted-bindings (extract-lifted-bindings lifts empties)) 201 (define new-body 202 (reannotate v `(lambda ,args . ,(convert-lifted-calls-in-seq/box-mutated body args lifts #hasheq() empties)))) 203 `(letrec ,(append (unbox empties) 204 lifted-bindings) 205 ,new-body)]))] 206 [`(case-lambda [,argss . ,bodys] ...) 207 ;; Lift each clause separately, then splice results: 208 (let ([lams (for/list ([args (in-list argss)] 209 [body (in-list bodys)]) 210 (lift-in-expr `(lambda ,args . ,body)))]) 211 (reannotate 212 v 213 (let loop ([lams lams] [clauses null] [bindings null]) 214 (cond 215 [(null? lams) 216 (if (null? bindings) 217 `(case-lambda ,@(reverse clauses)) 218 `(letrec ,bindings ,(loop null clauses null)))] 219 [else 220 (match (car lams) 221 [`(letrec ,new-bindings ,lam) 222 (loop (cons lam (cdr lams)) clauses (append (unwrap-list new-bindings) bindings))] 223 [`(lambda ,args . ,body) 224 (loop (cdr lams) (cons `[,args . ,body] clauses) bindings)])]))))] 225 [`(let . ,_) (lift-in-let v)] 226 [`(letrec . ,_) (lift-in-let v)] 227 [`(letrec* . ,_) (lift-in-let v)] 228 [`(let-values . ,_) (error 'internal-error "unexpected let-values")] 229 [`(letrec-values . ,_) (error 'internal-error "unexpected letrec-values")] 230 [`(begin . ,vs) 231 (reannotate v `(begin ,@(for/list ([v (in-wrap-list vs)]) 232 (lift-in-expr v))))] 233 [`(if ,tst ,thn ,els) 234 (reannotate v `(if ,(lift-in-expr tst) 235 ,(lift-in-expr thn) 236 ,(lift-in-expr els)))] 237 [`(with-continuation-mark* ,mode ,key ,val ,body) 238 (reannotate v `(with-continuation-mark* 239 ,mode 240 ,(lift-in-expr key) 241 ,(lift-in-expr val) 242 ,(lift-in-expr body)))] 243 [`(quote ,_) v] 244 [`(#%variable-reference . ,_) (error 'internal-error "unexpected variable reference")] 245 [`(set! ,id ,rhs) 246 (reannotate v `(set! ,id ,(lift-in-expr rhs)))] 247 [`(,_ ...) 248 (lift-in-seq v)] 249 [`,_ v])) 250 251 (define (lift-in-let v) 252 (match v 253 [`(,let-id ([,ids ,rhss] ...) . ,body) 254 (reannotate v `(,let-id 255 ,(for/list ([id (in-list ids)] 256 [rhs (in-list rhss)]) 257 `[,id ,(lift-in-expr rhs)]) 258 . ,(lift-in-seq body)))])) 259 260 (define (lift-in-seq vs) 261 (reannotate vs (for/list ([v (in-wrap-list vs)]) 262 (lift-in-expr v)))) 263 264 ;; ---------------------------------------- 265 ;; Pass 1: figure out which bindings can be lifted, and also record 266 ;; information about mutated and `#%variable-reference` variables. 267 ;; We only care about local variables within a top-level `lambda` or 268 ;; `case-lambda` form. 269 270 ;; Returns a set of free variables and a set of bound variables 271 ;; (paired together) while potentially mutating `lifts` 272 (define (compute-lifts! v frees+binds lifts locals [called? #f]) 273 (match v 274 [`(let ([,ids ,rhss] ...) . ,body) 275 (for ([id (in-list ids)] 276 [rhs (in-list rhss)]) 277 (when (lambda? rhs) 278 ;; RHS is a candidate for lifting 279 (hash-set! lifts (unwrap id) (liftable rhs #f #f)))) 280 (let* ([frees+binds (compute-rhs-lifts! ids rhss frees+binds lifts locals)] 281 [frees+binds (compute-seq-lifts! body frees+binds lifts (add-args ids locals))]) 282 (remove-frees/add-binds ids frees+binds lifts))] 283 [`(letrec . ,_) 284 (compute-letrec-lifts! v frees+binds lifts locals)] 285 [`(letrec* . ,_) 286 (compute-letrec-lifts! v frees+binds lifts locals)] 287 [`((letrec ([,id ,rhs]) ,rator) ,rands ...) 288 (compute-lifts! `(letrec ([,id ,rhs]) (,rator . ,rands)) frees+binds lifts locals)] 289 [`((letrec* ([,id ,rhs]) ,rator) ,rands ...) 290 (compute-lifts! `(letrec ([,id ,rhs]) (,rator . ,rands)) frees+binds lifts locals)] 291 [`(lambda ,args . ,body) 292 (let* ([body-frees+binds (cons (car empty-frees+binds) (cdr frees+binds))] 293 [body-frees+binds (compute-seq-lifts! body body-frees+binds lifts (add-args args locals))] 294 [body-frees+binds (remove-frees/add-binds args body-frees+binds lifts)]) 295 (when (and (zero? (frees-count body-frees+binds)) (not called?)) 296 (record-empty-closure! lifts v)) 297 (cons (union (car body-frees+binds) (car frees+binds)) 298 (cdr body-frees+binds)))] 299 [`(case-lambda [,argss . ,bodys] ...) 300 (define init-frees+binds (cons (car empty-frees+binds) (cdr frees+binds))) 301 (define new-frees+binds 302 (for/fold ([frees+binds init-frees+binds]) ([args (in-list argss)] 303 [body (in-list bodys)]) 304 (let ([frees+binds (compute-seq-lifts! body frees+binds lifts (add-args args locals))]) 305 (remove-frees/add-binds args frees+binds lifts)))) 306 (when (and (zero? (frees-count new-frees+binds)) (not called?)) 307 (record-empty-closure! lifts v)) 308 (cons (union (car new-frees+binds) (car frees+binds)) 309 (cdr new-frees+binds))] 310 [`(begin . ,vs) 311 (compute-seq-lifts! vs frees+binds lifts locals)] 312 [`(begin-unsafe . ,vs) 313 (compute-seq-lifts! vs frees+binds lifts locals)] 314 [`(begin0 . ,vs) 315 (compute-seq-lifts! vs frees+binds lifts locals)] 316 [`(quote . ,_) frees+binds] 317 [`(if ,tst ,thn ,els) 318 (let* ([frees+binds (compute-lifts! tst frees+binds lifts locals)] 319 [frees+binds (compute-lifts! thn frees+binds lifts locals)] 320 [frees+binds (compute-lifts! els frees+binds lifts locals)]) 321 frees+binds)] 322 [`(with-continuation-mark* ,_ ,key ,val ,body) 323 (let* ([frees+binds (compute-lifts! key frees+binds lifts locals)] 324 [frees+binds (compute-lifts! val frees+binds lifts locals)] 325 [frees+binds (compute-lifts! body frees+binds lifts locals)]) 326 frees+binds)] 327 [`(set! ,id ,rhs) 328 (define var (unwrap id)) 329 (let ([frees+binds (cond 330 [(hash-ref locals var #f) 331 => (lambda (status) 332 (lookup-indirected-variable lifts var (eq? status 'early)) 333 (add-free frees+binds var))] 334 [else frees+binds])]) 335 (compute-lifts! rhs frees+binds lifts locals))] 336 [`(#%variable-reference . ,_) 337 (error 'internal-error "lift: unexpected variable reference")] 338 [`(call-with-values ,producer ,consumer) 339 (let* ([frees+binds (compute-lifts! producer frees+binds lifts locals #t)] 340 [frees+binds (compute-lifts! consumer frees+binds lifts locals #t)]) 341 frees+binds)] 342 [`(,rator . ,rands) 343 (define f (unwrap rator)) 344 (let ([frees+binds 345 (cond 346 [(symbol? f) 347 (let ([proc (hash-ref lifts f #f)]) 348 (when (liftable? proc) 349 (unless (consistent-argument-count? (liftable-expr proc) (length (unwrap-list rands))) 350 (hash-remove! lifts f)))) 351 ;; Don't recur on `rator`, because we don't want 352 ;; to mark `f` as unliftable 353 (if (hash-ref locals f #f) 354 (add-free frees+binds f) 355 frees+binds)] 356 [else 357 (compute-lifts! rator frees+binds lifts locals)])]) 358 (compute-seq-lifts! rands frees+binds lifts locals))] 359 [`,_ 360 (define x (unwrap v)) 361 (cond 362 [(or (string? x) (bytes? x) (boolean? x) (number? x)) 363 frees+binds] 364 [else 365 (unless (symbol? x) 366 (error 'lift-in-schemified 367 "unrecognized expression form: ~e" 368 v)) 369 ;; If this identifier is mapped to a liftable, then 370 ;; the function is not liftable after all, since 371 ;; the reference isn't in an application position 372 (let ([proc (hash-ref lifts x #f)]) 373 (when (liftable? proc) 374 (hash-remove! lifts x))) 375 (let ([loc-status (hash-ref locals x #f)]) 376 (cond 377 [loc-status 378 (let ([frees+binds (add-free frees+binds x)]) 379 (cond 380 [(eq? loc-status 'early) 381 (lookup-indirected-variable lifts x #t) 382 (add-free frees+binds x)] 383 [else frees+binds]))] 384 [else frees+binds]))])])) 385 386 ;; Like `compute-lifts!`, but for a sequence of expressions 387 (define (compute-seq-lifts! vs frees+binds lifts locals) 388 (for/fold ([frees+binds frees+binds]) ([v (in-wrap-list vs)]) 389 (compute-lifts! v frees+binds lifts locals))) 390 391 ;; Similar to `compute-seq-lifts!`, but installs free-variable 392 ;; information in the `lifts` table for each identifier in `ids`: 393 (define (compute-rhs-lifts! ids rhss frees+binds lifts locals) 394 (for/fold ([frees+binds frees+binds]) ([id (in-list ids)] 395 [rhs (in-list rhss)]) 396 (let ([rhs-frees+binds (compute-lifts! rhs empty-frees+binds lifts locals)] 397 [f (unwrap id)]) 398 (let ([proc (hash-ref lifts f #f)]) 399 (when (liftable? proc) 400 (set-liftable-frees! proc (car rhs-frees+binds)) 401 (set-liftable-binds! proc (cdr rhs-frees+binds)))) 402 (cons (union (car rhs-frees+binds) (car frees+binds)) 403 (union (cdr rhs-frees+binds) (cdr frees+binds)))))) 404 405 ;; Handle a letrec[*] form 406 (define (compute-letrec-lifts! v frees+binds lifts locals) 407 (match v 408 [`(,_ ([,ids ,rhss] ...) . ,body) 409 (define all-lambda-or-immediate? 410 (for/and ([rhs (in-list rhss)]) 411 (or (lambda? rhs) 412 (immediate? rhs)))) 413 (when all-lambda-or-immediate? 414 ;; Each RHS is a candidate for lifting 415 (for ([id (in-list ids)] 416 [rhs (in-list rhss)]) 417 (when (lambda? rhs) 418 (hash-set! lifts (unwrap id) (liftable rhs #f #f))))) 419 (let* ([rhs-locals (add-args ids locals (if all-lambda-or-immediate? 'ready 'early))] 420 [frees+binds (compute-rhs-lifts! ids rhss frees+binds lifts rhs-locals)] 421 [locals (if all-lambda-or-immediate? 422 rhs-locals 423 (add-args ids locals))] 424 [frees+binds (compute-seq-lifts! body frees+binds lifts locals)]) 425 (remove-frees/add-binds ids frees+binds lifts))])) 426 427 ;; ---------------------------------------- 428 ;; Pass 1b (optonal): find loops that don't need to be lifted, 429 ;; on the assumption they'll be recognized as loops 430 431 ;; Returns updated `loops` table 432 (define (find-loops v lifts loop-if-tail loops) 433 (match v 434 [`(letrec . ,_) 435 (find-letrec-loops v lifts loop-if-tail loops)] 436 [`(letrec* . ,_) 437 (find-letrec-loops v lifts loop-if-tail loops)] 438 [`((letrec ([,id ,rhs]) ,rator) ,rands ...) 439 (find-loops `(letrec ([,id ,rhs]) (,rator . ,rands)) lifts loop-if-tail loops)] 440 [`((letrec* ([,id ,rhs]) ,rator) ,rands ...) 441 (find-loops `(letrec ([,id ,rhs]) (,rator . ,rands)) lifts loop-if-tail loops)] 442 [`(let . ,_) 443 (find-let-loops v lifts loop-if-tail loops)] 444 [`(lambda ,args . ,body) 445 (find-seq-loops body lifts #hasheq() loops)] 446 [`(case-lambda [,argss . ,bodys] ...) 447 (for/fold ([loops loops]) ([body (in-list bodys)]) 448 (find-seq-loops body lifts #hasheq() loops))] 449 [`(begin . ,vs) 450 (find-seq-loops vs lifts loop-if-tail loops)] 451 [`(begin-unsafe . ,vs) 452 (find-seq-loops vs lifts loop-if-tail loops)] 453 [`(begin0 ,v . ,vs) 454 (define new-loops (find-loops v lifts #hasheq() loops)) 455 (if (null? vs) 456 new-loops 457 (find-seq-loops vs lifts #hasheq() new-loops))] 458 [`(quote . ,_) loops] 459 [`(if ,tst ,thn ,els) 460 (let* ([loops (find-loops tst lifts #hasheq() loops)] 461 [loops (find-loops thn lifts loop-if-tail loops)] 462 [loops (find-loops els lifts loop-if-tail loops)]) 463 loops)] 464 [`(with-continuation-mark* ,_ ,key ,val ,body) 465 (let* ([loops (find-loops key lifts #hasheq() loops)] 466 [loops (find-loops val lifts #hasheq() loops)]) 467 (find-loops body lifts loop-if-tail loops))] 468 [`(set! ,id ,rhs) 469 (find-loops rhs lifts #hasheq() loops)] 470 [`(#%variable-reference . ,_) 471 (error 'internal-error "lift: unexpected variable reference")] 472 [`(call-with-values ,producer ,consumer) 473 (let ([loops (find-loops producer lifts #hasheq() loops)]) 474 (find-loops-in-tail-called consumer lifts loop-if-tail loops))] 475 [`(,rator . ,rands) 476 (define f (unwrap rator)) 477 (let ([loops 478 (cond 479 [(and (symbol? f) 480 (hash-ref loop-if-tail f #f)) 481 => (lambda (bx) 482 (set-box! bx #t) ; record reference to loop 483 loops)] 484 [else (find-loops rator lifts #hasheq() loops)])]) 485 (for/fold ([loops loops]) ([rand (in-list rands)]) 486 (find-loops rand lifts #hasheq() loops)))] 487 [`,_ 488 (define x (unwrap v)) 489 (if (symbol? x) 490 (hash-remove loops x) 491 loops)])) 492 493 (define (find-seq-loops vs lifts loop-if-tail loops) 494 (let loop ([vs vs] [loops loops]) 495 (cond 496 [(wrap-null? (wrap-cdr vs)) 497 (find-loops (wrap-car vs) lifts loop-if-tail loops)] 498 [else 499 (loop (wrap-cdr vs) 500 (find-loops (wrap-car vs) lifts #hasheq() loops))]))) 501 502 (define (find-let-loops v lifts loop-if-tail loops) 503 (match v 504 [`(,_ ([,_ ,rhss] ...) . ,body) 505 (define new-loops 506 (for/fold ([loops loops]) ([rhs (in-list rhss)]) 507 (find-loops rhs lifts #hasheq() loops))) 508 (find-seq-loops body lifts loop-if-tail new-loops)])) 509 510 (define (find-letrec-loops v lifts loop-if-tail loops) 511 (match v 512 [`(,_ ([,id ,rhs]) (,id2 . ,rands)) 513 (define u-id (unwrap id)) 514 (cond 515 [(and (eq? (unwrap id2) u-id) 516 (hash-ref lifts u-id #f)) 517 ;; It's liftable, so potentially a loop 518 (let* ([loops (hash-set loops u-id #t)] 519 [loops (for/fold ([loops loops]) ([rand (in-list rands)]) 520 (find-loops rand lifts #hasheq() loops))]) 521 (cond 522 [(not (hash-ref loops u-id #f)) 523 (find-loops rhs lifts #hasheq() loops)] 524 [else 525 (define new-loop-if-tail 526 (hash-set (for/hasheq ([(id bx) (in-hash loop-if-tail)]) 527 ;; If box is set, create a new one to find out if it's 528 ;; specifically set here. Otherwise, use existing box 529 ;; to propagate from here to elsewhere 530 (if (unbox bx) 531 (values id (box #f)) 532 (values id bx))) 533 u-id 534 (box #f))) 535 (define new-loops 536 (find-loops-in-tail-called rhs lifts new-loop-if-tail loops)) 537 (cond 538 [(hash-ref new-loops u-id #f) 539 new-loops] 540 [else 541 ;; Not a loop, so any reference added in `new-loop-if-tail` 542 ;; is also a non-loop 543 (for/fold ([loops new-loops]) ([(id bx) (in-hash new-loop-if-tail)]) 544 (if (unbox bx) 545 (hash-remove loops id) 546 loops))])]))] 547 [else (find-let-loops v lifts loop-if-tail loops)])] 548 [`,_ (find-let-loops v lifts loop-if-tail loops)])) 549 550 (define (find-loops-in-tail-called v lifts loop-if-tail loops) 551 (match v 552 [`(lambda ,args . ,body) 553 (find-seq-loops body lifts loop-if-tail loops)] 554 [`(case-lambda [,argss . ,bodys] ...) 555 (for/fold ([loops loops]) ([body (in-list bodys)]) 556 (find-seq-loops body lifts loop-if-tail loops))] 557 [`,_ (find-loops v lifts #hasheq() loops)])) 558 559 ;; ---------------------------------------- 560 ;; Bridge between pass 1 and 2: transitive closure of free variables 561 562 ;; Close a liftable's free variables over other variables needed by 563 ;; other lifted functions that it calls. Also, clear `mutated` and 564 ;; `var-ref` information from `lifts` in the returned table. 565 (define (close-and-convert-lifts lifts loops) 566 (define new-lifts (make-hasheq)) 567 ;; Copy over `liftable`s: 568 (for ([(f info) (in-hash lifts)]) 569 (when (liftable? info) 570 (hash-set! new-lifts f info))) 571 ;; Compute the closure of free-variable sets, where a function 572 ;; to be lifted calls another function to be lifted, and also 573 ;; re-register mutators and variable references that are 574 ;; used. 575 (for ([proc (in-list (hash-values new-lifts))]) 576 (define frees (liftable-frees proc)) 577 (define binds (liftable-binds proc)) 578 (define closed-frees 579 (let loop ([frees frees] [todo (hash-keys frees)]) 580 (cond 581 [(null? todo) frees] 582 [else 583 (define v (car todo)) 584 (define info (hash-ref lifts v #f)) 585 (cond 586 [(liftable? info) 587 ;; A liftable function called by ths liftable function, 588 ;; so we'll need to be able to supply all of its free 589 ;; variables 590 (define v-binds (liftable-binds info)) 591 (let v-loop ([v-frees (hash-keys (liftable-frees info))] 592 [frees frees] 593 [todo (cdr todo)]) 594 (if (null? v-frees) 595 (loop frees todo) 596 (let ([g (car v-frees)]) 597 (cond 598 [(or (hash-ref frees g #f) ; avoid cycles 599 (hash-ref binds g #f) ; don't add if bound in this function 600 (hash-ref v-binds g #f)) ; don't add if local to `v` 601 (v-loop (cdr v-frees) frees todo)] 602 [else 603 (v-loop (cdr v-frees) 604 (hash-set frees g #t) 605 (cons g todo))]))))] 606 [(indirected? info) 607 ;; Preserve recording of this variable as boxed 608 (hash-set! new-lifts v info) 609 (loop frees (cdr todo))] 610 [else 611 ;; Normal variable: 612 (loop frees (cdr todo))])]))) 613 (set-liftable-frees! proc closed-frees)) 614 ;; Remove references to lifted from free-variable sets, and also 615 ;; convert free-variable sets to lists for consistent ordering: 616 (for ([proc (in-hash-values new-lifts)] 617 #:when (liftable? proc)) 618 (set-liftable-frees! proc (sort (for/list ([f (in-hash-keys (liftable-frees proc))] 619 #:unless (liftable? (hash-ref lifts f #f))) 620 f) 621 symbol<?))) 622 ;; Copy over empty-closure records: 623 (for ([(f info) (in-hash lifts)]) 624 (when (eq? info '#:empty) 625 (hash-set! new-lifts f info))) 626 ;; Remove any loops, which should be left alone after all 627 (for ([f (in-hash-keys loops)]) 628 (hash-remove! new-lifts f)) 629 ;; Return new lifts 630 new-lifts) 631 632 ;; ---------------------------------------- 633 ;; Pass 2: convert calls based on previously collected information 634 635 (define (convert-lifted-calls-in-expr v lifts frees empties) 636 (let convert ([v v]) 637 (match v 638 [`(let . ,_) 639 (convert-lifted-calls-in-let v lifts frees empties)] 640 [`(letrec . ,_) 641 (convert-lifted-calls-in-letrec v lifts frees empties)] 642 [`(letrec* . ,_) 643 (convert-lifted-calls-in-letrec v lifts frees empties)] 644 [`((letrec ([,id ,rhs]) ,rator) ,rands ...) 645 (convert (reannotate v `(letrec ([,id ,rhs]) (,rator . ,rands))))] 646 [`((letrec* ([,id ,rhs]) ,rator) ,rands ...) 647 (convert (reannotate v `(letrec* ([,id ,rhs]) (,rator . ,rands))))] 648 [`(lambda ,args . ,body) 649 (lift-if-empty 650 v lifts empties 651 (reannotate v `(lambda ,args . ,(convert-lifted-calls-in-seq/box-mutated body args lifts frees empties))))] 652 [`(case-lambda [,argss . ,bodys] ...) 653 (lift-if-empty 654 v lifts empties 655 (reannotate v `(case-lambda 656 ,@(for/list ([args (in-list argss)] 657 [body (in-list bodys)]) 658 `[,args . ,(convert-lifted-calls-in-seq/box-mutated body args lifts frees empties)]))))] 659 [`(begin . ,vs) 660 (reannotate v `(begin . ,(convert-lifted-calls-in-seq vs lifts frees empties)))] 661 [`(begin-unsafe . ,vs) 662 (reannotate v `(begin-unsafe . ,(convert-lifted-calls-in-seq vs lifts frees empties)))] 663 [`(begin0 . ,vs) 664 (reannotate v `(begin0 . ,(convert-lifted-calls-in-seq vs lifts frees empties)))] 665 [`(quote . ,_) v] 666 [`(if ,tst ,thn ,els) 667 (reannotate v `(if ,(convert tst) ,(convert thn) ,(convert els)))] 668 [`(with-continuation-mark* ,mode ,key ,val ,body) 669 (reannotate v `(with-continuation-mark* ,mode ,(convert key) ,(convert val) ,(convert body)))] 670 [`(set! ,id ,rhs) 671 (define info (and (hash-ref lifts (unwrap id) #f))) 672 (cond 673 [(indirected? info) 674 (reannotate v (if (indirected-check? info) 675 `(set-box!/check-undefined ,id ,(convert rhs) ',id) 676 `(unsafe-set-box*! ,id ,(convert rhs))))] 677 [else 678 (reannotate v `(set! ,id ,(convert rhs)))])] 679 [`(#%variable-reference . ,_) 680 (error 'internal-error "lift: unexpected variable reference")] 681 [`(,rator . ,rands) 682 (let ([rands (convert-lifted-calls-in-seq rands lifts frees empties)]) 683 (define f (unwrap rator)) 684 (cond 685 [(and (symbol? f) 686 (let ([p (hash-ref lifts f #f)]) 687 (and (liftable? p) p))) 688 => (lambda (proc) 689 (reannotate v `(,rator ,@(liftable-frees proc) . ,rands)))] 690 [else 691 (reannotate v `(,(convert rator) . ,rands))]))] 692 [`,_ 693 (define var (unwrap v)) 694 (define info (and (symbol? var) 695 (hash-ref lifts var #f))) 696 (cond 697 [(indirected? info) 698 (reannotate v (if (indirected-check? info) 699 `(unbox/check-undefined ,v ',v) 700 `(unsafe-unbox* ,v)))] 701 [else v])]))) 702 703 (define (convert-lifted-calls-in-seq vs lifts frees empties) 704 (reannotate vs (for/list ([v (in-wrap-list vs)]) 705 (convert-lifted-calls-in-expr v lifts frees empties)))) 706 707 (define (convert-lifted-calls-in-let v lifts frees empties) 708 (match v 709 [`(,let-id ([,ids ,rhss] ...) . ,body) 710 (define bindings 711 (for/list ([id (in-list ids)] 712 [rhs (in-list rhss)] 713 #:unless (liftable? (hash-ref lifts (unwrap id) #f))) 714 `[,id ,(let ([rhs (convert-lifted-calls-in-expr rhs lifts frees empties)]) 715 (if (indirected? (hash-ref lifts (unwrap id) #f)) 716 `(box ,rhs) 717 rhs))])) 718 (define new-body 719 (convert-lifted-calls-in-seq body lifts frees empties)) 720 (reannotate 721 v 722 (rebuild-let let-id bindings new-body))])) 723 724 (define (convert-lifted-calls-in-letrec v lifts frees empties) 725 (match v 726 [`(,let-id ([,ids ,rhss] ...) . ,body) 727 (define pre-bindings 728 (for/list ([id (in-list ids)] 729 [rhs (in-list rhss)] 730 #:when (indirected? (hash-ref lifts (unwrap id) #f))) 731 `[,id (box unsafe-undefined)])) 732 (define bindings 733 (for/list ([id (in-list ids)] 734 [rhs (in-list rhss)] 735 #:unless (liftable? (hash-ref lifts (unwrap id) #f))) 736 (define new-rhs (convert-lifted-calls-in-expr rhs lifts frees empties)) 737 (cond 738 [(indirected? (hash-ref lifts (unwrap id) #f)) 739 `[,(deterministic-gensym "seq") (unsafe-set-box*! ,id ,new-rhs)]] 740 [else `[,id ,new-rhs]]))) 741 (define new-bindings 742 (if (null? bindings) 743 pre-bindings 744 (append pre-bindings bindings))) 745 (define new-body 746 (convert-lifted-calls-in-seq body lifts frees empties)) 747 (reannotate 748 v 749 (rebuild-let let-id new-bindings new-body))])) 750 751 (define (convert-lifted-calls-in-seq/box-mutated vs ids lifts frees empties) 752 (let loop ([ids ids]) 753 (cond 754 [(wrap-null? ids) 755 (convert-lifted-calls-in-seq vs lifts frees empties)] 756 [(wrap-pair? ids) 757 (define id (wrap-car ids)) 758 (if (indirected? (hash-ref lifts (unwrap id) #f)) 759 `((let ([,id (box ,id)]) 760 . ,(loop (wrap-cdr ids)))) 761 (loop (wrap-cdr ids)))] 762 [else (loop (list ids))]))) 763 764 ;; Create bindings for lifted functions, adding new arguments 765 ;; as the functions are lifted 766 (define (extract-lifted-bindings lifts empties) 767 (define liftables 768 ;; Improve determinsism by sorting liftables: 769 (sort (for/list ([(f proc) (in-hash lifts)] 770 #:when (liftable? proc)) 771 (cons f proc)) 772 symbol<? 773 #:key car)) 774 (for/list ([f+proc (in-list liftables)]) 775 (let* ([f (car f+proc)] 776 [proc (cdr f+proc)] 777 [new-args (liftable-frees proc)] 778 [frees (for/hash ([arg (in-list new-args)]) 779 (values arg #t))] 780 [rhs (liftable-expr proc)]) 781 `[,f ,(match rhs 782 [`(lambda ,args . ,body) 783 (let ([body (convert-lifted-calls-in-seq/box-mutated body args lifts frees empties)]) 784 (reannotate rhs `(lambda ,(append new-args args) . ,body)))] 785 [`(case-lambda [,argss . ,bodys] ...) 786 (reannotate rhs `(case-lambda 787 ,@(for/list ([args (in-list argss)] 788 [body (in-list bodys)]) 789 (let ([body (convert-lifted-calls-in-seq/box-mutated body args lifts frees empties)]) 790 `[,(append new-args args) . ,body]))))])]))) 791 792 ;; ---------------------------------------- 793 ;; Helpers 794 795 (define (lambda? v) 796 (match v 797 [`(lambda . ,_) #t] 798 [`(case-lambda . ,_) #t] 799 [`,_ #f])) 800 801 (define (immediate? v) 802 (match v 803 [`(quote . ,_) #t] 804 [`(,_ . ,_) #f] 805 [`,_ 806 (not (symbol? (unwrap v)))])) 807 808 (define (consistent-argument-count? proc n) 809 (define (consistent? args n) 810 (let loop ([args args] [n n]) 811 (cond 812 [(negative? n) #f] 813 [(wrap-null? args) (zero? n)] 814 [(wrap-pair? args) 815 (loop (wrap-cdr args) (sub1 n))] 816 [else #t]))) 817 (match proc 818 [`(lambda ,args . ,_) 819 (consistent? args n)] 820 [`(case-lambda [,argss . ,_] ...) 821 (for/or ([args (in-list argss)]) 822 (consistent? args n))] 823 [`,_ #f])) 824 825 ;; Find or create an `indirected` record for a variable 826 (define (lookup-indirected-variable lifts var need-check?) 827 (define ind (hash-ref lifts var #f)) 828 (or (and (indirected? ind) 829 (begin 830 (when need-check? 831 (set-indirected-check?! ind #t)) 832 ind)) 833 (let ([ind (indirected need-check?)]) 834 (hash-set! lifts var ind) 835 ind))) 836 837 ;; Add a group of arguments (a list or improper list) to a set 838 (define (add-args args s [mode 'ready]) 839 (let loop ([args args] [s s]) 840 (cond 841 [(wrap-null? args) s] 842 [(wrap-pair? args) 843 (loop (wrap-cdr args) 844 (hash-set s (unwrap (wrap-car args)) mode))] 845 [else (hash-set s (unwrap args) mode)]))) 846 847 ;; Add a free variable 848 (define (add-free frees+binds var) 849 (cons (hash-set (car frees+binds) var #t) 850 (cdr frees+binds))) 851 852 (define (frees-count frees+binds) 853 (hash-count (car frees+binds))) 854 855 ;; Remove a group of arguments (a list or improper list) from a set 856 ;; as the variable go out of scope, including any associated mutator 857 ;; and variable-reference variables, but keep variables for lifted 858 ;; functions 859 (define (remove-frees/add-binds args frees+binds lifts) 860 (define (remove-free/add-bind frees+binds arg) 861 (define info (hash-ref lifts arg #f)) 862 (cond 863 [(liftable? info) 864 ;; Since `arg` will be lifted to the top, it 865 ;; stays in our local set of free variables, 866 ;; but also add it to binds so that callers 867 ;; will know that they don't need to chain 868 (cons (car frees+binds) 869 (hash-set (cdr frees+binds) arg #t))] 870 [else (cons (hash-remove (car frees+binds) arg) 871 (hash-set (cdr frees+binds) arg #t))])) 872 (let loop ([args args] [frees+binds frees+binds]) 873 (cond 874 [(wrap-null? args) frees+binds] 875 [(wrap-pair? args) 876 (loop (wrap-cdr args) 877 (remove-free/add-bind frees+binds (unwrap (wrap-car args))))] 878 [else (remove-free/add-bind frees+binds (unwrap args))]))) 879 880 ;; Set union 881 (define (union s1 s2) 882 (cond 883 [((hash-count s1) . > . (hash-count s2)) 884 (union s2 s1)] 885 [else 886 (for/fold ([s2 s2]) ([k (in-hash-keys s1)]) 887 (hash-set s2 k #t))])) 888 889 (define (rebuild-let let-id bindings body) 890 (cond 891 [(not (null? bindings)) 892 `(,let-id ,bindings . ,body)] 893 [(and (pair? body) (null? (cdr body))) 894 (car body)] 895 [else `(begin . ,body)])) 896 897 (define (record-empty-closure! lifts v) 898 (hash-set! lifts v '#:empty)) 899 900 (define (lift-if-empty v lifts empties new-v) 901 (cond 902 [(hash-ref lifts v #f) 903 (define id (deterministic-gensym "procz")) 904 (set-box! empties (cons `[,id ,new-v] (unbox empties))) 905 id] 906 [else new-v])) 907 908 ;; ---------------------------------------- 909 ;; Go 910 911 (if (lift-in? v) 912 (with-deterministic-gensym 913 (lift-in v)) 914 v)) 915 916;; ============================================================ 917 918(module+ main 919 (require racket/pretty) 920 (pretty-print 921 (lift-in-schemified-linklet 922 '(lambda () 923 (define f0 924 (lambda () 925 (letrec ([loop (lambda (x) 926 (if (zero? x) 927 (let ([z 0]) 928 z) 929 (call-with-values 930 (lambda () (values 1 10)) 931 (lambda (v1 v2) 932 (loop (sub1 x))))))]) 933 (loop 8)))) 934 (define f0 935 (lambda () 936 (letrec ([l1 (lambda (x) 937 (if (zero? x) 938 'done 939 (letrec ([l2 (lambda (y) 940 (if (zero? y) 941 (l1 (sub1 x)) 942 (l2 (sub1 y))))]) 943 (l2 10))))]) 944 (l1 8)))) 945 (define f2 946 (lambda () 947 (letrec ([not-a-loop (lambda (x) 948 (if (zero? x) 949 0 950 (add1 (not-a-loop (sub1 x)))))]) 951 (not-a-loop 8)))) 952 (define f3 953 (lambda () 954 (letrec ([nl1 (lambda (x) 955 (if (zero? x) 956 0 957 (letrec ([nl2 (lambda (y) 958 (nl1 (nl2 (sub1 x))))]) 959 (nl2 10))))]) 960 (nl1 8)))) 961 10) 962 #t))) 963