This repository has been archived on 2022-08-10. You can view files and clone it, but cannot push or open issues or pull requests.
chez-openbsd/ta6ob/s/cpletrec.ss
2022-08-09 23:28:25 +02:00

393 lines
18 KiB
Scheme

;;; cpletrec.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.
#|
Notes:
- cpletrec does not consider a record-ref form or call to a restricted
primitive, like car, to be pure even at optimize-level 3 because it's
possible it will be moved ahead of an explicit test within a sequence
of letrec* bindings.
|#
#|
Handling letrec and letrec*
- call cpletrec on each rhs recursively to determine the new rhs,
whether it's pure, and which of the lhs variables are free in it
- call cpletrec on the body
- build a graph. For letrec, create a link from b1 to b2 iff b2 is free
in b1. for letrec*, also create a link from b1 to b2 if neither is
pure and b1 originally appeared before b2.
- determine the strongly connected components of the graph, partially
sorted so that SCC1 comes before SCC2 if there exists a binding b2
in SCC2 that has a link to a binding b1 in SCC1.
- process each SCC as a separate set of letrec/letrec* bindings:
- for letrec*, sort the bindings of the SCC by their original relative
positions. for letrec, any order will do.
- if SCC contains a single binding b where LHS(b) is not assigned
and RHS(b) is a lambda expression, bind using pure letrec,
- otherwise, if SCC contains a single binding b where LHS(b) is
not free in RHS(b), bind using let
- otherwise, partition into lambda bindings lb ... and complex
bindings cb ... where a binding b is lambda iff LHS(b) is not
assigned and RHS(b) is a lambda expression. Generate:
(let ([LHS(cb) (void)] ...)
(letrec ([LHS(lb) RHS(cb)] ...)
(set! LHS(cb) RHS(cb)) ...
body))
- assimilate nested pure letrec forms
|#
(define $cpletrec
(let ()
(import (nanopass))
(include "base-lang.ss")
(define rtd-flds (csv7:record-field-accessor #!base-rtd 'flds))
(define-pass lift-profile-forms : Lsrc (ir) -> Lsrc ()
(definitions
(with-output-language (Lsrc Expr)
(define lift-profile-forms
; pull out profile forms from simple subforms so the profile
; forms won't interfere with downstream optimizations
(lambda (e* k)
(define extract-profile
(lambda (e profile*)
(define profile?
(lambda (e)
(nanopass-case (Lsrc Expr) e
[(profile ,src) #t]
[(seq ,e1 ,e2) (and (profile? e1) (profile? e2))]
[else #f])))
(define simple?
(lambda (e)
(nanopass-case (Lsrc Expr) e
[(quote ,d) #t]
[(ref ,maybe-src ,x) #t]
[,pr #t]
[(call ,preinfo ,pr ,e*) (eq? (primref-name pr) '$top-level-value)]
[(case-lambda ,preinfo ,cl* ...) #t]
[else #f])))
(nanopass-case (Lsrc Expr) e
[(seq ,e1 ,e2)
(guard (and (profile? e1) (simple? e2)))
(values e2 (cons e1 profile*))]
[else (values e profile*)])))
(let f ([e* e*] [re* '()] [profile* '()])
(if (null? e*)
(fold-left (lambda (e profile) `(seq ,profile ,e))
(k (reverse re*))
profile*)
(let-values ([(e profile*) (extract-profile (car e*) profile*)])
(f (cdr e*) (cons e re*) profile*))))))))
(Expr : Expr (ir) -> Expr ()
[(call ,preinfo ,[e] ,[e*] ...)
(lift-profile-forms (cons e e*)
(lambda (e*)
`(call ,preinfo ,(car e*) ,(cdr e*) ...)))]
[(letrec ([,x* ,[e*]] ...) ,[body])
(lift-profile-forms e*
(lambda (e*)
`(letrec ([,x* ,e*] ...) ,body)))]
[(letrec* ([,x* ,[e*]] ...) ,[body])
(lift-profile-forms e*
(lambda (e*)
`(letrec* ([,x* ,e*] ...) ,body)))]))
(define-pass cpletrec : Lsrc (ir) -> Lsrc ()
(definitions
(define with-initialized-ids
(lambda (old-id* proc)
(let ([new-id* (map (lambda (old-id)
(let ([new-id (make-prelex
(prelex-name old-id)
(let ([flags (prelex-flags old-id)])
(fxlogor
(fxlogand flags (constant prelex-sticky-mask))
(fxsll (fxlogand flags (constant prelex-is-mask))
(constant prelex-was-flags-offset))))
(prelex-source old-id)
#f)])
(prelex-operand-set! old-id new-id)
new-id))
old-id*)])
(let-values ([v* (proc new-id*)])
(for-each (lambda (old-id) (prelex-operand-set! old-id #f)) old-id*)
(apply values v*)))))
(define (Expr* e*)
(if (null? e*)
(values '() #t)
(let-values ([(e e-pure?) (Expr (car e*))]
[(e* e*-pure?) (Expr* (cdr e*))])
(values (cons e e*) (and e-pure? e*-pure?)))))
(with-output-language (Lsrc Expr)
(define build-seq
(lambda (e* body)
(fold-right (lambda (e body) `(seq ,e ,body)) body e*)))
(define build-let
(lambda (call-preinfo lambda-preinfo lhs* rhs* body)
(if (null? lhs*)
body
(let ([interface (length lhs*)])
`(call ,call-preinfo
(case-lambda ,lambda-preinfo
(clause (,lhs* ...) ,interface ,body))
,rhs* ...)))))
(module (cpletrec-letrec)
(define-record-type binding
(fields (immutable lhs) (immutable pos) (mutable rhs) (mutable pure?) (mutable recursive?))
(nongenerative)
(protocol
(lambda (new)
(lambda (lhs pos)
(new lhs pos #f #f #f)))))
(define-record-type node ; isolate stuff needed for compute-sccs!
(parent binding)
(fields (mutable link*) (mutable root) (mutable done))
(nongenerative)
(sealed #t)
(protocol
(lambda (make-new)
(lambda (lhs pos)
((make-new lhs pos) '() #f #f)))))
(define (lambda? x)
(nanopass-case (Lsrc Expr) x
[(case-lambda ,preinfo ,cl* ...) #t]
[else #f]))
(define (cpletrec-bindings *? lhs* rhs*)
(let ([all-b* (map make-node lhs* (enumerate lhs*))])
(let loop ([b* all-b*] [rhs* rhs*] [last-nonpure #f])
(unless (null? b*)
(let ([b (car b*)] [rhs (car rhs*)])
(for-each (lambda (lhs) (set-prelex-seen! lhs #f)) lhs*)
(let-values ([(rhs pure?) (Expr rhs)])
(binding-rhs-set! b rhs)
(binding-pure?-set! b pure?)
(binding-recursive?-set! b (prelex-seen (binding-lhs b)))
(let ([free* (filter (lambda (b) (prelex-seen (binding-lhs b))) all-b*)])
(if (or pure? (not *?))
(begin
(node-link*-set! b free*)
(loop (cdr b*) (cdr rhs*) last-nonpure))
(begin
(node-link*-set! b
(if (and last-nonpure (not (memq last-nonpure free*)))
(cons last-nonpure free*)
free*))
(loop (cdr b*) (cdr rhs*) b))))))))
all-b*))
(define (compute-sccs v*) ; Tarjan's algorithm
(define scc* '())
(define (compute-sccs v)
(define index 0)
(define stack '())
(define (tarjan v)
(let ([v-index index])
(node-root-set! v v-index)
(set! stack (cons v stack))
(set! index (fx+ index 1))
(for-each
(lambda (v^)
(unless (node-done v^)
(unless (node-root v^) (tarjan v^))
(node-root-set! v (fxmin (node-root v) (node-root v^)))))
(node-link* v))
(when (fx= (node-root v) v-index)
(set! scc*
(cons
(let f ([ls stack])
(let ([v^ (car ls)])
(node-done-set! v^ #t)
(cons v^ (if (eq? v^ v)
(begin (set! stack (cdr ls)) '())
(f (cdr ls))))))
scc*)))))
(tarjan v))
(for-each (lambda (v) (unless (node-done v) (compute-sccs v))) v*)
(reverse scc*))
(define (grisly-letrec lb* cb* body)
(let ([rclhs* (fold-right (lambda (b lhs*)
(let ([lhs (binding-lhs b)])
(if (prelex-referenced/assigned lhs)
(cons lhs lhs*)
lhs*)))
'() cb*)])
(build-let (make-preinfo) (make-preinfo-lambda) rclhs* (map (lambda (x) `(quote ,(void))) rclhs*)
(build-letrec (map binding-lhs lb*) (map binding-rhs lb*)
(fold-right (lambda (b body)
(let ([lhs (binding-lhs b)] [rhs (binding-rhs b)])
`(seq
,(if (prelex-referenced lhs)
(begin
(set-prelex-assigned! lhs #t)
`(set! #f ,lhs ,rhs))
rhs)
,body)))
body cb*)))))
(define build-letrec
(lambda (lhs* rhs* body)
(if (null? lhs*)
; dropping source here; could attach to body or add source record
body
(nanopass-case (Lsrc Expr) body
; assimilate nested letrecs
[(letrec ([,x* ,e*] ...) ,body)
`(letrec ([,(append lhs* x*) ,(append rhs* e*)] ...) ,body)]
[else `(letrec ([,lhs* ,rhs*] ...) ,body)]))))
(define (expand-letrec b* body)
(if (null? (cdr b*))
(let* ([b (car b*)] [lhs (binding-lhs b)] [rhs (binding-rhs b)])
(cond
[(and (not (prelex-referenced/assigned lhs)) (binding-pure? b)) body]
[(and (not (prelex-assigned lhs)) (lambda? rhs))
(build-letrec (list lhs) (list rhs) body)]
[(not (memq b (node-link* b)))
(build-let (make-preinfo) (make-preinfo-lambda) (list lhs) (list rhs) body)]
[else (grisly-letrec '() b* body)]))
(let-values ([(lb* cb*) (partition
(lambda (b)
(and (not (prelex-assigned (binding-lhs b)))
(lambda? (binding-rhs b))))
b*)])
(grisly-letrec lb* cb* body))))
(define (cpletrec-letrec *? lhs* rhs* body)
(let ([b* (cpletrec-bindings *? lhs* rhs*)])
(let-values ([(body body-pure?) (Expr body)])
(values
(let f ([scc* (compute-sccs b*)])
(if (null? scc*)
body
(expand-letrec
(if *?
(sort
(lambda (b1 b2) (fx< (binding-pos b1) (binding-pos b2)))
(car scc*))
(car scc*))
(f (cdr scc*)))))
(and body-pure? (andmap binding-pure? b*)))))))))
(Expr : Expr (ir) -> Expr (#t)
[(ref ,maybe-src ,x)
(let ([x (prelex-operand x)])
(safe-assert (prelex? x))
(safe-assert (prelex-was-referenced x))
(when (prelex-referenced x)
(safe-assert (prelex-was-multiply-referenced x))
(set-prelex-multiply-referenced! x #t))
(set-prelex-seen/referenced! x #t)
(values `(ref ,maybe-src ,x) (not (prelex-was-assigned x))))]
[(quote ,d) (values ir #t)]
[(call ,preinfo0 (case-lambda ,preinfo1 (clause (,x* ...) ,interface ,body)) ,e* ...)
(guard (fx= (length e*) interface))
(with-initialized-ids x*
(lambda (x*)
(let-values ([(body body-pure?) (Expr body)])
(let-values ([(pre* lhs* rhs* pure?)
(let f ([x* x*] [e* e*])
(if (null? x*)
(values '() '() '() #t)
(let ([x (car x*)])
(let-values ([(e e-pure?) (Expr (car e*))]
[(pre* lhs* rhs* pure?) (f (cdr x*) (cdr e*))])
(if (prelex-referenced/assigned x)
(values pre* (cons x lhs*) (cons e rhs*) (and e-pure? pure?))
(values (if e-pure? pre* (cons e pre*))
lhs* rhs* (and e-pure? pure?)))))))])
(values
(build-seq pre* (build-let preinfo0 preinfo1 lhs* rhs* body))
(and body-pure? pure?))))))]
[(call ,preinfo ,pr ,e* ...)
(let ()
(define (arity-okay? arity n)
(or (not arity)
(ormap (lambda (a)
(or (fx= n a)
(and (fx< a 0) (fx>= n (fx- -1 a)))))
arity)))
(let-values ([(e* pure?) (Expr* e*)])
(values
`(call ,preinfo ,pr ,e* ...)
(and pure?
(all-set? (prim-mask (or proc pure unrestricted discard)) (primref-flags pr))
(arity-okay? (primref-arity pr) (length e*))))))]
[(call ,preinfo ,[e pure?] ,[e* pure?*] ...)
(values `(call ,preinfo ,e ,e* ...) #f)]
[(if ,[e0 e0-pure?] ,[e1 e1-pure?] ,[e2 e2-pure?])
(values `(if ,e0 ,e1 ,e2) (and e0-pure? e1-pure? e2-pure?))]
[(case-lambda ,preinfo ,[cl*] ...)
(values `(case-lambda ,preinfo ,cl* ...) #t)]
[(seq ,[e1 e1-pure?] ,[e2 e2-pure?])
(values `(seq ,e1 ,e2) (and e1-pure? e2-pure?))]
[(set! ,maybe-src ,x ,[e pure?])
(let ([x (prelex-operand x)])
(safe-assert (prelex? x))
(safe-assert (prelex-was-assigned x))
; NB: cpletrec-letrec assumes assignments to unreferenced ids are dropped
(if (prelex-was-referenced x)
(begin
(set-prelex-seen/assigned! x #t)
(values `(set! ,maybe-src ,x ,e) #f))
(if pure? (values `(quote ,(void)) #t) (values `(seq ,e (quote ,(void))) #f))))]
[(letrec ([,x* ,e*] ...) ,body)
(with-initialized-ids x*
(lambda (x*)
(cpletrec-letrec #f x* e* body)))]
[(letrec* ([,x* ,e*] ...) ,body)
(with-initialized-ids x*
(lambda (x*)
(cpletrec-letrec #t x* e* body)))]
[(foreign (,conv* ...) ,name ,[e pure?] (,arg-type* ...) ,result-type)
(values `(foreign (,conv* ...) ,name ,e (,arg-type* ...) ,result-type)
(and (fx= (optimize-level) 3) pure?))]
[(fcallable (,conv* ...) ,[e pure?] (,arg-type* ...) ,result-type)
(values `(fcallable (,conv* ...) ,e (,arg-type* ...) ,result-type)
(and (fx= (optimize-level) 3) pure?))]
[(record-ref ,rtd ,type ,index ,[e pure?])
(values `(record-ref ,rtd ,type ,index ,e) #f)]
[(record-set! ,rtd ,type ,index ,[e1 pure1?] ,[e2 pure2?])
(values `(record-set! ,rtd ,type ,index ,e1 ,e2) #f)]
[(record ,rtd ,[rtd-expr rtd-pure?] ,e* ...)
(let-values ([(e* pure?) (Expr* e*)])
(values
`(record ,rtd ,rtd-expr ,e* ...)
(and (and rtd-pure? pure?)
(andmap
(lambda (fld)
(and (not (fld-mutable? fld))
(eq? (filter-foreign-type (fld-type fld)) 'scheme-object)))
(rtd-flds rtd)))))]
[(record-type ,rtd ,e) (Expr e)]
[(record-cd ,rcd ,rtd-expr ,e) (Expr e)]
[(immutable-list (,[e* pure?*] ...) ,[e pure?])
(values `(immutable-list (,e* ...) ,e) pure?)]
[,pr (values pr #t)]
[(moi) (values ir #t)]
[(pariah) (values ir #t)]
[(cte-optimization-loc ,box ,[e pure?])
(values `(cte-optimization-loc ,box ,e) pure?)]
[(profile ,src) (values ir #f)]
[else (sorry! who "unhandled record ~s" ir)])
(CaseLambdaClause : CaseLambdaClause (ir) -> CaseLambdaClause ()
[(clause (,x* ...) ,interface ,body)
(with-initialized-ids x*
(lambda (x*)
(let-values ([(body pure?) (Expr body)])
`(clause (,x* ...) ,interface ,body))))])
(let-values ([(ir pure?) (Expr ir)]) ir))
(lambda (x)
(let ([x (if (eq? ($compile-profile) 'source) (lift-profile-forms x) x)])
(cpletrec x)))
))