;;; cpcommonize.ss ;;; Copyright 1984-2017 Cisco Systems, Inc. ;;; ;;; Licensed under the Apache License, Version 2.0 (the "License"); ;;; you may not use this file except in compliance with the License. ;;; You may obtain a copy of the License at ;;; ;;; http://www.apache.org/licenses/LICENSE-2.0 ;;; ;;; Unless required by applicable law or agreed to in writing, software ;;; distributed under the License is distributed on an "AS IS" BASIS, ;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ;;; See the License for the specific language governing permissions and ;;; limitations under the License. (begin (define-who commonization-level ($make-thread-parameter 0 (lambda (x) (unless (and (fixnum? x) (<= 0 x 9)) ($oops who "invalid level ~s" x)) x))) (define $cpcommonize (let () (import (nanopass)) (include "base-lang.ss") (define-record-type binding (nongenerative) (sealed #t) (fields x (mutable e) size helper-box (mutable helper-b) (mutable helper-arg*)) (protocol (lambda (new) (lambda (x e size helper-box) (new x e size helper-box #f #f))))) (define-language Lcommonize1 (extends Lsrc) (terminals (+ (fixnum (size)))) (Expr (e body rtd-expr) (- (letrec ([x* e*] ...) body)) (+ (letrec ([x* e* size] ...) body)))) (define-language Lcommonize2 (extends Lcommonize1) (terminals (- (fixnum (size))) (+ (binding (b helper-b)))) (Expr (e body rtd-expr) (- (letrec ([x* e* size] ...) body)) (+ (letrec (helper-b* ...) (b* ...) body)))) (define-syntax iffalse (syntax-rules () [(_ e1 e2) e1 #;(or e1 (begin e2 #f))])) (define-syntax iftrue (syntax-rules () [(_ e1 e2) e1 #;(let ([t e1]) (and t (begin e2 t)))])) (define Lcommonize1-lambda? (lambda (e) (nanopass-case (Lcommonize1 Expr) e [(case-lambda ,preinfo ,cl* ...) #t] [else #f]))) (define-pass cpcommonize0 : Lsrc (ir) -> Lcommonize1 () (Expr : Expr (ir) -> Expr (1) [(set! ,maybe-src ,x ,[e size]) (values `(set! ,maybe-src ,x ,e) (fx+ 1 size))] [(seq ,[e1 size1] ,[e2 size2]) (values `(seq ,e1 ,e2) (fx+ size1 size2))] [(if ,[e1 size1] ,[e2 size2] ,[e3 size3]) (values `(if ,e1 ,e2 ,e3) (fx+ size1 size2 size3))] [(foreign (,conv* ...) ,name ,[e size] (,arg-type* ...) ,result-type) (values `(foreign (,conv* ...) ,name ,e (,arg-type* ...) ,result-type) (fx+ 1 size))] [(fcallable (,conv* ...) ,[e size] (,arg-type* ...) ,result-type) (values `(fcallable (,conv* ...) ,e (,arg-type* ...) ,result-type) (fx+ 1 size))] ; ($top-level-value 'x) adds just 1 to the size [(call ,preinfo ,pr (quote ,d)) (guard (eq? (primref-name pr) '$top-level-value)) (values `(call ,preinfo ,pr (quote ,d)) 1)] ; (let ([x* e*] ...) body) splits into letrec binding unassigned variables to lambdas plus a let for the remaining bindings [(call ,preinfo1 (case-lambda ,preinfo2 (clause (,x* ...) ,interface ,[body size])) ,[e* -> e* size*] ...) (guard (fx= (length e*) interface)) (define-record-type fudge (nongenerative) (sealed #t) (fields x e size)) (let-values ([(lb* ob*) (partition (lambda (b) (and (not (prelex-assigned (fudge-x b))) (Lcommonize1-lambda? (fudge-e b)))) (map make-fudge x* e* size*))]) (values (let ([body (if (null? ob*) body `(call ,preinfo1 (case-lambda ,preinfo2 (clause (,(map fudge-x ob*) ...) ,(length ob*) ,body)) ,(map fudge-e ob*) ...))]) (if (null? lb*) body `(letrec ([,(map fudge-x lb*) ,(map fudge-e lb*) ,(map fudge-size lb*)] ...) ,body))) (apply fx+ size size*)))] [(call ,preinfo ,[e size] ,[e* size*] ...) (values `(call ,preinfo ,e ,e* ...) (apply fx+ size size*))] [(case-lambda ,preinfo (clause (,x** ...) ,interface* ,[body* size*]) ...) (values `(case-lambda ,preinfo (clause (,x** ...) ,interface* ,body*) ...) (apply fx+ 1 size*))] [(letrec ([,x* ,[e* size*]] ...) ,[body size]) (values `(letrec ([,x* ,e* ,size*] ...) ,body) (apply fx+ size size*))] [(record-ref ,rtd ,type ,index ,[e size]) (values `(record-ref ,rtd ,type ,index ,e) (fx+ size 1))] [(record-set! ,rtd ,type ,index ,[e1 size1] ,[e2 size2]) (values `(record-set! ,rtd ,type ,index ,e1 ,e2) (fx+ size1 size2 1))] [(record ,rtd ,[rtd-expr size] ,[e* size*] ...) (values `(record ,rtd ,rtd-expr ,e* ...) (apply fx+ size size*))] [(cte-optimization-loc ,box ,[e size]) (values `(cte-optimization-loc ,box ,e) size)] [(immutable-list (,[e* size*] ...) ,[e size]) (values `(immutable-list (,e* ...) ,e) (apply fx+ size size*))] [(quote ,d) (values `(quote ,d) 1)] [(ref ,maybe-src ,x) (values `(ref ,maybe-src ,x) 1)] [,pr (values pr 1)] [(moi) (values `(moi) 1)] [(pariah) (values `(pariah) 0)] [(profile ,src) (values `(profile ,src) 0)] [else (sorry! who "unhandled record ~s" ir)]) (let-values ([(e size) (Expr ir)]) e)) (define-pass cpcommonize1 : Lcommonize1 (ir worthwhile-size) -> Lcommonize2 () (definitions (define worthwhile-size? (lambda (expr-size) (fx>= expr-size worthwhile-size))) (define worthwhile-ratio? (lambda (expr-size subst-count) (or (fx= subst-count 0) (fx>= (div expr-size subst-count) 4)))) (define-record-type subst (nongenerative) (sealed #t) (fields t e1 e2)) (define-record-type frob (nongenerative) (sealed #t) (fields subst* e b)) (define ht (make-hashtable values fx=)) (define make-sym (lambda x* (string->symbol (apply string-append (map (lambda (x) (if (prelex? x) (symbol->string (prelex-name x)) x)) x*))))) (define same-preinfo? (lambda (p1 p2) ; ignore differences in src and sexpr #t)) (define same-preinfo-lambda? (lambda (p1 p2) ; ignore differences src, sexpr, and name (eq? (preinfo-lambda-libspec p1) (preinfo-lambda-libspec p2)))) (define-who same-type? (lambda (ty1 ty2) (nanopass-case (Ltype Type) ty1 [(fp-integer ,bits1) (nanopass-case (Ltype Type) ty2 [(fp-integer ,bits2) (= bits1 bits2)] [else #f])] [(fp-unsigned ,bits1) (nanopass-case (Ltype Type) ty2 [(fp-unsigned ,bits2) (= bits1 bits2)] [else #f])] [(fp-void) (nanopass-case (Ltype Type) ty2 [(fp-void) #t] [else #f])] [(fp-scheme-object) (nanopass-case (Ltype Type) ty2 [(fp-scheme-object) #t] [else #f])] [(fp-u8*) (nanopass-case (Ltype Type) ty2 [(fp-u8*) #t] [else #f])] [(fp-u16*) (nanopass-case (Ltype Type) ty2 [(fp-u16*) #t] [else #f])] [(fp-u32*) (nanopass-case (Ltype Type) ty2 [(fp-u32*) #t] [else #f])] [(fp-fixnum) (nanopass-case (Ltype Type) ty2 [(fp-fixnum) #t] [else #f])] [(fp-double-float) (nanopass-case (Ltype Type) ty2 [(fp-double-float) #t] [else #f])] [(fp-single-float) (nanopass-case (Ltype Type) ty2 [(fp-single-float) #t] [else #f])] [(fp-ftd ,ftd1) (nanopass-case (Ltype Type) ty2 [(fp-ftd ,ftd2) (eq? ftd1 ftd2)] [else #f])] [else (sorry! who "unhandled foreign type ~s" ty1)]))) (define okay-to-subst? (lambda (e) (define free? (lambda (x) (and (not (prelex-operand x)) #t))) (nanopass-case (Lcommonize1 Expr) e [(ref ,maybe-src1 ,x1) (and (not (prelex-assigned x1)) (free? x1))] [(quote ,d) #t] [,pr (all-set? (prim-mask proc) (primref-flags pr))] [else #f]))) (define constant-equal? (lambda (x y) (define record-equal? (lambda (x y e?) (let ([rtd ($record-type-descriptor x)]) (and (eq? ($record-type-descriptor y) rtd) (let f ([field-name* (csv7:record-type-field-names rtd)] [i 0]) (or (null? field-name*) (and (let ([accessor (csv7:record-field-accessor rtd i)]) (e? (accessor x) (accessor y))) (f (cdr field-name*) (fx+ i 1))))))))) (parameterize ([default-record-equal-procedure record-equal?]) ; equal? should be okay since even mutable constants aren't supposed to be mutated (equal? x y)))) (define same? (lambda (e1 e2) (nanopass-case (Lcommonize1 Expr) e1 [(ref ,maybe-src1 ,x1) (nanopass-case (Lcommonize1 Expr) e2 [(ref ,maybe-src2 ,x2) (or (eq? x1 x2) (eq? (prelex-operand x1) x2))] [else #f])] [(quote ,d1) (nanopass-case (Lcommonize1 Expr) e2 [(quote ,d2) (constant-equal? d1 d2)] [else #f])] [,pr1 (nanopass-case (Lcommonize1 Expr) e2 [,pr2 (eq? pr1 pr2)] [else #f])] [(moi) (nanopass-case (Lcommonize1 Expr) e2 [(moi) #t] [else #f])] [(pariah) (nanopass-case (Lcommonize1 Expr) e2 [(pariah) #t] [else #f])] [(profile ,src1) (nanopass-case (Lcommonize1 Expr) e2 [(profile ,src2) (eq? src1 src2)] [else #f])] [(call ,preinfo1 ,pr1 (quote ,d1)) (guard (eq? (primref-name pr1) '$top-level-value)) (nanopass-case (Lcommonize1 Expr) e2 [(call ,preinfo2 ,pr2 (quote ,d2)) (guard (eq? (primref-name pr2) '$top-level-value)) (and (same-preinfo? preinfo1 preinfo2) (eq? d1 d2))] [else #f])] [else #f]))) (define-who unify (lambda (e1 e2) (module (with-env) (define $with-env (lambda (x1* x2* th) (dynamic-wind (lambda () (map (lambda (x1 x2) (prelex-operand-set! x1 x2) (prelex-operand-set! x2 #t)) x1* x2*)) th (lambda () (map (lambda (x1 x2) (prelex-operand-set! x1 #f) (prelex-operand-set! x2 #f)) x1* x2*))))) (define-syntax with-env (syntax-rules () [(_ x1* x2* e) ($with-env x1* x2* (lambda () e))]))) (call/cc (lambda (return) (let ([subst* '()]) (define lookup-subst (lambda (e1 e2) (define same-subst? (lambda (x) (and (same? (subst-e1 x) e1) (same? (subst-e2 x) e2)))) (cond [(find same-subst? subst*) => (lambda (subst) (let ([t (subst-t subst)]) (set-prelex-multiply-referenced! t #t) t))] [else #f]))) (let ([e (with-output-language (Lcommonize1 Expr) (let () (define fclause (lambda (cl1 cl2) (nanopass-case (Lcommonize1 CaseLambdaClause) cl1 [(clause (,x1* ...) ,interface1 ,body1) (nanopass-case (Lcommonize1 CaseLambdaClause) cl2 [(clause (,x2* ...) ,interface2 ,body2) (if (fx= interface1 interface2) (with-env x1* x2* (with-output-language (Lcommonize1 CaseLambdaClause) `(clause (,x1* ...) ,interface1 ,(f body1 body2)))) (return (iffalse #f (printf "lambda interfaces don't match\n")) '()))])]))) (define f (case-lambda [(e1 e2) (f e1 e2 #f)] [(e1 e2 call-position?) (or (cond [(same? e1 e2) e1] [(and (not call-position?) (okay-to-subst? e1) (okay-to-subst? e2)) `(ref #f ,(or (lookup-subst e1 e2) (let ([t (make-prelex*)]) (set-prelex-referenced! t #t) (set! subst* (cons (make-subst t e1 e2) subst*)) t)))] [else (nanopass-case (Lcommonize1 Expr) e1 [(ref ,maybe-src1 ,x1) #f] [(quote ,d) #f] [,pr #f] [(moi) #f] [(profile ,src1) #f] ; reject non-same top-level-value calls with constant symbol so they ; don't end up being abstracted over the symbol in the residual code [(call ,preinfo ,pr (quote ,d)) (guard (eq? (primref-name pr) '$top-level-value)) #f] ; don't allow abstraction of first (type) argument to $object-ref, foreign-ref, etc., ; since they can't be inlined without a constant type. ; ditto for $tc-field's first (field) argument. ; there are many other primitives we don't catch here for which the compiler generates ; more efficient code when certain arguments are constant. [(call ,preinfo1 ,pr1 (quote ,d1) ,e1* ...) (guard (memq (primref-name pr1) '($object-ref $swap-object-ref $object-set foreign-ref foreign-set! $tc-field))) (nanopass-case (Lcommonize1 Expr) e2 [(call ,preinfo2 ,pr2 (quote ,d2) ,e2* ...) (guard (eq? pr2 pr1) (eq? d1 d2)) (and (same-preinfo? preinfo1 preinfo2) (fx= (length e1*) (length e2*)) `(call ,preinfo1 ,pr1 (quote ,d1) ,(map f e1* e2*) ...))] [else #f])] [(call ,preinfo1 ,e1 ,e1* ...) (nanopass-case (Lcommonize1 Expr) e2 [(call ,preinfo2 ,e2 ,e2* ...) (and (fx= (length e1*) (length e2*)) (same-preinfo? preinfo1 preinfo2) `(call ,preinfo1 ,(f e1 e2 #t) ,(map f e1* e2*) ...))] [else #f])] [(if ,e10 ,e11 ,e12) (nanopass-case (Lcommonize1 Expr) e2 [(if ,e20 ,e21 ,e22) `(if ,(f e10 e20) ,(f e11 e21) ,(f e12 e22))] [else #f])] [(case-lambda ,preinfo1 ,cl1* ...) (nanopass-case (Lcommonize1 Expr) e2 [(case-lambda ,preinfo2 ,cl2* ...) (and (fx= (length cl1*) (length cl2*)) (same-preinfo-lambda? preinfo1 preinfo2) `(case-lambda ,preinfo1 ,(map fclause cl1* cl2*) ...))] [else #f])] [(seq ,e11 ,e12) (nanopass-case (Lcommonize1 Expr) e2 [(seq ,e21 ,e22) `(seq ,(f e11 e21) ,(f e12 e22))] [else #f])] [(set! ,maybe-src1 ,x1 ,e1) (nanopass-case (Lcommonize1 Expr) e2 [(set! ,maybe-src2 ,x2 ,e2) (and (eq? x1 x2) `(set! ,maybe-src1 ,x1 ,(f e1 e2)))] [else #f])] [(letrec ([,x1* ,e1* ,size1*] ...) ,body1) (nanopass-case (Lcommonize1 Expr) e2 [(letrec ([,x2* ,e2* ,size2*] ...) ,body2) (and (fx= (length x2*) (length x1*)) (andmap fx= size1* size2*) (with-env x1* x2* `(letrec ([,x1* ,(map f e1* e2*) ,size1*] ...) ,(f body1 body2))))] [else #f])] [(foreign (,conv1* ...) ,name1 ,e1 (,arg-type1* ...) ,result-type1) (nanopass-case (Lcommonize1 Expr) e2 [(foreign (,conv2* ...) ,name2 ,e2 (,arg-type2* ...) ,result-type2) (and (equal? conv1* conv2*) (equal? name1 name2) (fx= (length arg-type1*) (length arg-type2*)) (andmap same-type? arg-type1* arg-type2*) (same-type? result-type1 result-type2) `(foreign (,conv1* ...) ,name1 ,(f e1 e2) (,arg-type1* ...) ,result-type1))] [else #f])] [(fcallable (,conv1* ...) ,e1 (,arg-type1* ...) ,result-type1) (nanopass-case (Lcommonize1 Expr) e2 [(fcallable (,conv2* ...) ,e2 (,arg-type2* ...) ,result-type2) (and (equal? conv1* conv2*) (fx= (length arg-type1*) (length arg-type2*)) (andmap same-type? arg-type1* arg-type2*) (same-type? result-type1 result-type2) `(fcallable (,conv1* ...) ,(f e1 e2) (,arg-type1* ...) ,result-type1))] [else #f])] [(cte-optimization-loc ,box1 ,e1) (nanopass-case (Lcommonize1 Expr) e2 [(cte-optimization-loc ,box2 ,e2) (and (eq? box1 box2) `(cte-optimization-loc ,box1 ,(f e1 e2)))] [else #f])] [else (sorry! who "unhandled record ~s" e1)])]) (return (iffalse #f (parameterize ([print-level 3] [print-length 5]) (printf "unify failed for ~s and ~s (call-position ~s)\n" e1 e2 call-position?))) '()))])) (f e1 e2)))]) (values e subst*))))))) (define sort-substs ; reestablish original argument order for substituted variables where possible ; so the arguments to an abstracted procedure aren't shuffled around in the ; call to the generated helper. (lambda (subst0* x1* x2*) (define (this? x x*) (and (not (null? x*)) (eq? x (car x*)))) (define (next x*) (if (null? x*) x* (cdr x*))) (let-values ([(new-subst* subst*) (let f ([x1* x1*] [x2* x2*] [subst* subst0*] [n (length subst0*)]) (cond [(fx= n 0) (values '() subst*)] [(find (lambda (subst) (define (is-this-arg? e x*) (nanopass-case (Lcommonize1 Expr) e [(ref ,maybe-src ,x) (this? x x*)] [else #f])) (or (is-this-arg? (subst-e1 subst) x1*) (is-this-arg? (subst-e2 subst) x2*))) subst*) => (lambda (subst) (let-values ([(new-subst* subst*) (f (next x1*) (next x2*) (remq subst subst*) (fx- n 1))]) (values (cons subst new-subst*) subst*)))] [else (let-values ([(new-subst* subst*) (f (next x1*) (next x2*) subst* (fx- n 1))]) (values (cons (car subst*) new-subst*) (cdr subst*)))]))]) (safe-assert (null? subst*)) (safe-assert (fx= (length new-subst*) (length subst0*))) new-subst*))) (define find-match (lambda (b1 ht) (and (iffalse (worthwhile-size? (binding-size b1)) (printf "skipping b1: under worthwhile size ~s ~s\n" (binding-size b1) worthwhile-size)) (ormap (lambda (b2) (iffalse #f (printf "checking ~s & ~s:" (prelex-name (binding-x b1)) (prelex-name (binding-x b2)))) (nanopass-case (Lcommonize1 Expr) (binding-e b1) ; NB: restricting to one clause for now...handling multiple ; NB: clauses should be straightforward with a helper per ; NB: common clause. [(case-lambda ,preinfo1 (clause (,x1* ...) ,interface1 ,body1)) ; NB: no rest interface for now. should be straightforward (guard (fxnonnegative? interface1)) (and (nanopass-case (Lcommonize1 Expr) (binding-e b2) [(case-lambda ,preinfo2 (clause (,x2* ...) ,interface2 ,body2)) (guard (fxnonnegative? interface2)) (let-values ([(e subst*) (unify body1 body2)]) (and e (iffalse (worthwhile-ratio? (binding-size b1) (length subst*)) (printf " no, not worthwhile ratio ~s ~s\n" (binding-size b1) (length subst*))) (let ([subst* (sort-substs subst* x1* x2*)]) (iffalse #f (printf " yes\n")) (make-frob subst* e b2))))] [else (iffalse #f (printf " no, b2 does not meet lambda restrictions\n"))]))] [else (iffalse #f (printf " no, b1 does not meet lambda restrictions\n"))])) (hashtable-ref ht (binding-size b1) '()))))) (define record-helper! (lambda (b next e*) (binding-helper-b-set! b next) (binding-helper-arg*-set! b e*))) (define build-helper (lambda (t t* body size helper-box) (make-binding t (with-output-language (Lcommonize1 Expr) `(case-lambda ,(make-preinfo-lambda) (clause (,t* ...) ,(length t*) ,body))) size helper-box))) (define commonize-letrec (lambda (x* e* size* body) ; e* and body have not been processed (define (prune-and-process! b) (let ([b* (remq b (hashtable-ref ht (binding-size b) '()))]) (if (null? b*) (hashtable-delete! ht (binding-size b)) (hashtable-set! ht (binding-size b) b*))) (unless (binding-helper-b b) (binding-e-set! b (Expr (binding-e b))))) (if (null? x*) body (let ([helper-box (box '())]) (let ([b* (map (lambda (x e size) (make-binding x e size helper-box)) x* e* size*)]) (let ([body (let f ([b* b*]) (if (null? b*) (Expr body) (let ([b (car b*)]) (let ([frob (find-match b ht)]) (if frob (let* ([outer-b (frob-b frob)] [helper-box (binding-helper-box outer-b)] [helper-b (let ([t (make-prelex* (make-sym (binding-x b) "&" (binding-x outer-b)))]) (build-helper t (map subst-t (frob-subst* frob)) (frob-e frob) (binding-size outer-b) helper-box))]) (set-box! helper-box (cons helper-b (unbox helper-box))) (record-helper! b helper-b (map subst-e1 (frob-subst* frob))) (record-helper! outer-b helper-b (map subst-e2 (frob-subst* frob))) (hashtable-update! ht (binding-size outer-b) (lambda (b*) (cons helper-b (remq outer-b b*))) '()) (f (cdr b*))) (begin (hashtable-update! ht (binding-size b) (lambda (b*) (cons b b*)) '()) (let ([body (f (cdr b*))]) (prune-and-process! b) body)))))))]) (let ([helper-b* (unbox helper-box)]) (for-each prune-and-process! helper-b*) (with-output-language (Lcommonize2 Expr) `(letrec (,helper-b* ...) (,b* ...) ,body)))))))))) (Expr : Expr (ir) -> Expr () [(letrec ([,x* ,e* ,size*] ...) ,body) ; only unassigned lambda bindings post-cpletrec (safe-assert (andmap (lambda (x) (not (prelex-assigned x))) x*)) (safe-assert (andmap (lambda (e) (Lcommonize1-lambda? e)) e*)) (commonize-letrec x* e* size* body)] [(letrec* ([,x* ,e*] ...) ,body) ; no letrec* run post-cpletrec (assert #f)])) (define-pass cpcommonize2 : Lcommonize2 (ir) -> Lsrc () (definitions (define build-caller (lambda (e helper-b helper-arg*) (define-who Arg (lambda (e) (with-output-language (Lsrc Expr) (nanopass-case (Lcommonize1 Expr) e [(ref ,maybe-src ,x) `(ref ,maybe-src ,x)] [(quote ,d) `(quote ,d)] [else (sorry! who "unexpected helper arg ~s" e)])))) (define propagate (lambda (alist) (lambda (e) (nanopass-case (Lsrc Expr) e [(ref ,maybe-src ,x) (cond [(assq x alist) => cdr] [else e])] [else e])))) (nanopass-case (Lcommonize1 Expr) e [(case-lambda ,preinfo (clause (,x* ...) ,interface ,body)) (with-output-language (Lsrc Expr) `(case-lambda ,preinfo (clause (,x* ...) ,interface ,(let loop ([helper-b helper-b] [e* (map Arg helper-arg*)]) (if (binding-helper-b helper-b) (nanopass-case (Lcommonize1 Expr) (binding-e helper-b) [(case-lambda ,preinfo (clause (,x* ...) ,interface ,body)) (loop (binding-helper-b helper-b) (map (propagate (map cons x* e*)) (map Arg (binding-helper-arg* helper-b))))]) `(call ,(make-preinfo) ,(let ([t (binding-x helper-b)]) (if (prelex-referenced t) (set-prelex-multiply-referenced! t #t) (set-prelex-referenced! t #t)) `(ref #f ,t)) ,e* ...))))))]))) (define maybe-build-caller (lambda (b) (let ([helper-b (binding-helper-b b)] [e (binding-e b)]) (if helper-b (build-caller e helper-b (binding-helper-arg* b)) (Expr e)))))) (Expr : Expr (ir) -> Expr () [(letrec (,helper-b* ...) (,b* ...) ,[body]) (let loop ([rb* (reverse helper-b*)] [x* (map binding-x b*)] [e* (map maybe-build-caller b*)]) (if (null? rb*) `(letrec ([,x* ,e*] ...) ,body) (let ([b (car rb*)] [rb* (cdr rb*)]) (if (prelex-referenced (binding-x b)) (loop rb* (cons (binding-x b) x*) (cons (maybe-build-caller b) e*)) (loop rb* x* e*)))))])) (lambda (x) (let ([level (commonization-level)]) (if (fx= level 0) x (let ([worthwhile-size (expt 2 (fx- 10 level))]) (cpcommonize2 (cpcommonize1 (cpcommonize0 x) worthwhile-size)))))))) )