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