123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876 |
- ;;; Attaching inlinable definitions of exported bindings to modules
- ;;; Copyright (C) 2021, 2022
- ;;; Free Software Foundation, Inc.
- ;;;
- ;;; This library is free software: you can redistribute it and/or modify
- ;;; it under the terms of the GNU Lesser General Public License as
- ;;; published by the Free Software Foundation, either version 3 of the
- ;;; License, or (at your option) any later version.
- ;;;
- ;;; This library is distributed in the hope that it will be useful, but
- ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;;; Lesser General Public License for more details.
- ;;;
- ;;; You should have received a copy of the GNU Lesser General Public
- ;;; License along with this program. If not, see
- ;;; <http://www.gnu.org/licenses/>.
- (define-module (language tree-il inlinable-exports)
- #:use-module (ice-9 control)
- #:use-module (ice-9 match)
- #:use-module (ice-9 binary-ports)
- #:use-module (language tree-il)
- #:use-module (language tree-il primitives)
- #:use-module (language tree-il fix-letrec)
- #:use-module (language scheme compile-tree-il)
- #:use-module ((srfi srfi-1) #:select (filter-map))
- #:use-module (srfi srfi-9)
- #:use-module (system syntax)
- #:use-module (rnrs bytevectors)
- #:export (inlinable-exports))
- ;;;
- ;;; Inlining, as implemented by peval, is the mother of all
- ;;; optimizations. It opens up space for other optimizations to work,
- ;;; such as constant folding, conditional branch folding, and so on.
- ;;;
- ;;; Inlining works naturally for lexical bindings. Inlining of
- ;;; top-level binding is facilitated by letrectification, which turns
- ;;; top-level definition sequences to letrec*. Here we facilitate
- ;;; inlining across module boundaries, so that module boundaries aren't
- ;;; necessarily optimization boundaries.
- ;;;
- ;;; The high-level idea is to attach a procedure to the module being
- ;;; compiled, which when called with a name of an export of that module
- ;;; will return a Tree-IL expression that can be copied into the use
- ;;; site. There are two parts: first we determine the set of inlinable
- ;;; bindings, and then we compile that mapping to a procedure and attach
- ;;; it to the program being compiled.
- ;;;
- ;;; Because we don't want inter-module inlining to inhibit intra-module
- ;;; inlining, this pass is designed to run late in the Tree-IL
- ;;; optimization pipeline -- after letrectification, after peval, and so
- ;;; on. Unfortunately this does mean that we have to sometimes
- ;;; pattern-match to determine higher-level constructs from lower-level
- ;;; residual code, for example to map back from
- ;;; module-ensure-local-variable! + %variable-set! to toplevel-define,
- ;;; as reduced by letrectification. Ah well.
- ;;;
- ;;; Ultimately we want to leave the decision to peval as to what to
- ;;; inline or not to inline, based on its size and effort counters. But
- ;;; still we do need to impose some limits -- there's no sense in
- ;;; copying a large constant from one module to another, for example.
- ;;; Similarly there's no sense in copying a very large procedure.
- ;;; Inspired by peval, we bound size growth via a counter that will
- ;;; abort an inlinable attempt if the term is too large.
- ;;;
- ;;; Note that there are some semantic limitations -- you wouldn't want
- ;;; to copy a mutable value, nor would you want to copy a closure with
- ;;; free variables.
- ;;;
- ;;; Once the set of inlinables is determined, we copy them and rename
- ;;; their lexicals. Any reference to an exported binding by lexical
- ;;; variable is rewritten in terms of a reference to the exported
- ;;; binding.
- ;;;
- ;;; The result is then compiled to a procedure, which internally has a
- ;;; small interpreter for a bytecode, along with a set of constants.
- ;;; The assumption is that most of the constants will be written to the
- ;;; object file anyway, so we aren't taking up more space there. Any
- ;;; non-immediate is built on demand, so we limit the impact of
- ;;; including inlinable definitions on load-time relocations,
- ;;; allocations, and heap space.
- ;;;
- (define (compute-assigned-lexicals exp)
- (define assigned-lexicals '())
- (define (add-assigned-lexical! var)
- (set! assigned-lexicals (cons var assigned-lexicals)))
- ((make-tree-il-folder)
- exp
- (lambda (exp)
- (match exp
- (($ <lexical-set> _ _ var _)
- (add-assigned-lexical! var)
- (values))
- (_ (values))))
- (lambda (exp)
- (values)))
- assigned-lexicals)
- (define (compute-assigned-toplevels exp)
- (define assigned-toplevels '())
- (define (add-assigned-toplevel! mod name)
- (set! assigned-toplevels (acons mod name assigned-toplevels)))
- ((make-tree-il-folder)
- exp
- (lambda (exp)
- (match exp
- (($ <toplevel-set> _ mod name _)
- (add-assigned-toplevel! mod name)
- (values))
- (($ <module-set> src mod name public? exp)
- (unless public?
- (add-assigned-toplevel! mod name))
- (values))
- (_ (values))))
- (lambda (exp)
- (values)))
- assigned-toplevels)
- ;;; FIXME: Record all bindings in a module, to know whether a
- ;;; toplevel-ref is an import or not. If toplevel-ref to imported
- ;;; variable, transform to module-ref or primitive-ref. New pass before
- ;;; peval.
- (define (compute-module-bindings exp)
- (define assigned-lexicals (compute-assigned-lexicals exp))
- (define assigned-toplevels (compute-assigned-toplevels exp))
- (define module-definitions '())
- (define lexicals (make-hash-table))
- (define module-lexicals '())
- (define variable-lexicals '())
- (define binding-lexicals '())
- (define binding-values '())
- (define (add-module-definition! mod args)
- (set! module-definitions (acons mod args module-definitions)))
- (define (add-lexical! var val)
- (unless (memq var assigned-lexicals)
- (hashq-set! lexicals var val)))
- (define (add-module-lexical! var mod)
- (unless (memq var assigned-lexicals)
- (set! module-lexicals (acons var mod module-lexicals))))
- (define (add-variable-lexical! var mod name)
- (unless (memq var assigned-lexicals)
- (set! variable-lexicals (acons var (cons mod name) variable-lexicals))))
- (define (add-binding-lexical! var mod name)
- (unless (memq var assigned-lexicals)
- (set! binding-lexicals (acons var (cons mod name) binding-lexicals))))
- (define (add-binding-value! mod name val)
- (set! binding-values (acons (cons mod name) val binding-values)))
- (define (record-bindings! mod gensyms vals)
- (for-each
- (lambda (var val)
- (add-lexical! var val)
- (match val
- (($ <call> _ ($ <module-ref> _ '(guile) 'define-module* #f)
- (($ <const> _ mod) . args))
- (add-module-definition! mod args)
- (add-module-lexical! var mod))
- (($ <primcall> _ 'current-module ())
- (when mod
- (add-module-lexical! var mod)))
- (($ <primcall> _ 'module-ensure-local-variable!
- (($ <lexical-ref> _ _ mod-var) ($ <const> _ name)))
- (let ((mod (assq-ref module-lexicals mod-var)))
- (when mod
- (add-variable-lexical! var mod name))))
- (_ #f)))
- gensyms vals))
- ;; Thread a conservative idea of what the current module is through
- ;; the visit. Visiting an expression returns the name of the current
- ;; module when the expression completes, or #f if unknown. Record the
- ;; define-module* forms, if any, and note any assigned or
- ;; multiply-defined variables. Record definitions by matching
- ;; toplevel-define forms, but also by matching separate
- ;; module-ensure-local-variable! + %variable-set, as residualized by
- ;; letrectification.
- (define (visit exp) (visit/mod exp #f))
- (define (visit* exps)
- (unless (null? exps)
- (visit (car exps))
- (visit* (cdr exps))))
- (define (visit+ exps mod)
- (match exps
- (() mod)
- ((exp . exps)
- (let lp ((mod' (visit/mod exp mod)) (exps exps))
- (match exps
- (() mod')
- ((exp . exps)
- (lp (and (equal? mod' (visit/mod exp mod)) mod')
- exps)))))))
- (define (visit/mod exp mod)
- (match exp
- ((or ($ <void>) ($ <const>) ($ <primitive-ref>) ($ <lexical-ref>)
- ($ <module-ref>) ($ <toplevel-ref>))
- mod)
- (($ <call> _ ($ <module-ref> _ '(guile) 'set-current-module #f)
- (($ <lexical-ref> _ _ var)))
- (assq-ref module-lexicals var))
- (($ <primcall> src '%variable-set! (($ <lexical-ref> _ _ var)
- val))
- (match (assq-ref variable-lexicals var)
- ((mod . name)
- (add-binding-value! mod name val)
- ;; Also record lexical for eta-expanded bindings.
- (match val
- (($ <lambda> _ _
- ($ <lambda-case> _ req #f #f #f () (arg ...)
- ($ <call> _
- (and eta ($ <lexical-ref> _ _ var))
- (($ <lexical-ref> _ _ arg) ...))
- #f))
- (add-binding-lexical! var mod name))
- (($ <lambda> _ _
- ($ <lambda-case> _ req #f (not #f) #f () (arg ...)
- ($ <primcall> _ 'apply
- ((and eta ($ <lexical-ref> _ _ var))
- ($ <lexical-ref> _ _ arg) ...))
- #f))
- (add-binding-lexical! var mod name))
- (($ <lexical-ref> _ _ var)
- (add-binding-lexical! var mod name))
- (_ #f)))
- (_ #f))
- (visit/mod val mod))
- (($ <call> _ proc args)
- (visit proc)
- (visit* args)
- #f)
- (($ <primcall> _ _ args)
- ;; There is no primcall that sets the current module.
- (visit+ args mod))
- (($ <conditional> src test consequent alternate)
- (visit+ (list consequent alternate) (visit/mod test mod)))
- (($ <lexical-set> src name gensym exp)
- (visit/mod exp mod))
- (($ <toplevel-set> src mod name exp)
- (visit/mod exp mod))
- (($ <module-set> src mod name public? exp)
- (visit/mod exp mod))
- (($ <toplevel-define> src mod name exp)
- (add-binding-value! mod name exp)
- (visit/mod exp mod))
- (($ <lambda> src meta body)
- (when body (visit body))
- mod)
- (($ <lambda-case> src req opt rest kw inits gensyms body alternate)
- (visit* inits)
- (visit body)
- (when alternate (visit alternate))
- (values))
- (($ <seq> src head tail)
- (visit/mod tail (visit/mod head mod)))
-
- (($ <let> src names gensyms vals body)
- (record-bindings! mod gensyms vals)
- (visit/mod body (visit+ vals mod)))
- (($ <letrec> src in-order? names gensyms vals body)
- (record-bindings! mod gensyms vals)
- (visit/mod body (visit+ vals mod)))
- (($ <fix> src names gensyms vals body)
- (record-bindings! mod gensyms vals)
- (visit/mod body (visit+ vals mod)))
- (($ <let-values> src exp body)
- (visit/mod body (visit/mod exp mod))
- #f)
- (($ <prompt> src escape-only? tag body handler)
- (visit tag)
- (visit body)
- (visit handler)
- #f)
- (($ <abort> src tag args tail)
- (visit tag)
- (visit* args)
- (visit tail)
- #f)))
- (visit exp)
- (values module-definitions lexicals binding-lexicals binding-values))
- ;; - define inlinable? predicate:
- ;; exported && declarative && only references public vars && not too big
- ;;
- ;; - public := exported from a module, at -O2 and less.
- ;; at -O3 and higher public just means defined in any module.
- (define (inlinable-exp mod exports lexicals binding-lexicals exp)
- (define fresh-var!
- (let ((counter 0))
- (lambda ()
- (let ((name (string-append "t" (number->string counter))))
- (set! counter (1+ counter))
- (string->symbol name)))))
- (define (fresh-vars vars)
- (match vars
- (() '())
- ((_ . vars) (cons (fresh-var!) (fresh-vars vars)))))
- (define (add-bound-vars old new bound)
- (match (vector old new)
- (#(() ()) bound)
- (#((old . old*) (new . new*))
- (add-bound-vars old* new* (acons old new bound)))))
- (let/ec return
- (define (abort!) (return #f))
- (define count!
- ;; Same as default operator size limit for peval.
- (let ((counter 40))
- (lambda ()
- (set! counter (1- counter))
- (when (zero? counter) (abort!)))))
- (define (residualize-module-private-ref src mod' name)
- ;; TODO: At -O3, we could residualize a private
- ;; reference. But that could break peoples'
- ;; expectations.
- (abort!))
- (define (eta-reduce exp)
- ;; Undo the result of eta-expansion pass.
- (match exp
- (($ <lambda> _ _
- ($ <lambda-case> _ req #f #f #f () (sym ...)
- ($ <call> _
- (and eta ($ <lexical-ref>)) (($ <lexical-ref> _ _ sym) ...))
- #f))
- eta)
- (($ <lambda> _ _
- ($ <lambda-case> _ req #f (not #f) #f () (sym ...)
- ($ <primcall> _ 'apply
- ((and eta ($ <lexical-ref>)) ($ <lexical-ref> _ _ sym) ...))
- #f))
- eta)
- (_ exp)))
- (let copy ((exp (eta-reduce exp)) (bound '()) (in-lambda? #f))
- (define (recur exp) (copy exp bound in-lambda?))
- (count!)
- (match exp
- ((or ($ <void>) ($ <primitive-ref>) ($ <module-ref>))
- exp)
- (($ <const> src val)
- (match val
- ;; Don't copy values that could be "too big".
- ((? string?) exp) ; Oddly, (array? "") => #t.
- ((or (? pair?) (? syntax?) (? array?))
- (abort!))
- (_ exp)))
- (($ <lexical-ref> src name var)
- (cond
- ;; Rename existing lexicals.
- ((assq-ref bound var)
- => (lambda (var)
- (make-lexical-ref src name var)))
- ;; A free variable reference to a lambda, outside a lambda.
- ;; Could be the lexical-ref residualized by letrectification.
- ;; Copy and rely on size limiter to catch runaways.
- ((and (not in-lambda?) (lambda? (hashq-ref lexicals var)))
- (recur (hashq-ref lexicals var)))
- ((not in-lambda?)
- ;; No advantage to "inline" a toplevel to another toplevel.
- (abort!))
- ;; Some letrectified toplevels will be bound to lexical
- ;; variables, but unless the module has sealed private
- ;; bindings, there may be an associated top-level variable
- ;; as well.
- ((assq-ref binding-lexicals var)
- => (match-lambda
- ((mod' . name)
- (cond
- ((and (equal? mod' mod) (assq-ref exports name))
- => (lambda (public-name)
- (make-module-ref src mod public-name #t)))
- (else
- (residualize-module-private-ref src mod' name))))))
- ;; A free variable reference. If it's in the program at this
- ;; point, that means that peval didn't see fit to copy it, so
- ;; there's no point in trying to do so here.
- (else (abort!))))
- (($ <toplevel-ref> src mod' name)
- (cond
- ;; Rewrite private references to exported bindings into public
- ;; references. Peval can decide whether to continue inlining
- ;; or not.
- ((and (equal? mod mod') (assq-ref exports name))
- => (lambda (public-name)
- (make-module-ref src mod public-name #t)))
- (else
- (residualize-module-private-ref src mod' name))))
- (($ <call> src proc args)
- (unless in-lambda? (abort!))
- (make-call src (recur proc) (map recur args)))
- (($ <primcall> src name args)
- (unless in-lambda? (abort!))
- (make-primcall src name (map recur args)))
- (($ <conditional> src test consequent alternate)
- (unless in-lambda? (abort!))
- (make-conditional src (recur test)
- (recur consequent) (recur alternate)))
- (($ <lexical-set> src name var exp)
- (unless in-lambda? (abort!))
- (cond
- ((assq-ref bound var)
- => (lambda (var)
- (make-lexical-set src name var (recur exp))))
- (else
- (abort!))))
- ((or ($ <toplevel-set>)
- ($ <module-set>)
- ($ <toplevel-define>))
- (abort!))
- (($ <lambda> src meta body)
- ;; Remove any lengthy docstring.
- (let ((meta (filter-map (match-lambda
- (('documentation . _) #f)
- (pair pair))
- meta)))
- (make-lambda src meta (and body (copy body bound #t)))))
- (($ <lambda-case> src req opt rest kw inits vars body alternate)
- (unless in-lambda? (abort!))
- (let* ((vars* (fresh-vars vars))
- (bound (add-bound-vars vars vars* bound)))
- (define (recur* exp) (copy exp bound #t))
- (make-lambda-case src req opt rest
- (match kw
- (#f #f)
- ((aok? . kws)
- (cons aok?
- (map
- (match-lambda
- ((kw name var)
- (list kw name (assq-ref var bound))))
- kws))))
- (map recur* inits)
- vars*
- (recur* body)
- (and alternate (recur alternate)))))
- (($ <seq> src head tail)
- (unless in-lambda? (abort!))
- (make-seq src (recur head) (recur tail)))
-
- (($ <let> src names vars vals body)
- (unless in-lambda? (abort!))
- (let* ((vars* (fresh-vars vars))
- (bound (add-bound-vars vars vars* bound)))
- (define (recur* exp) (copy exp bound #t))
- (make-let src names vars* (map recur vals) (recur* body))))
- (($ <letrec> src in-order? names vars vals body)
- (unless in-lambda? (abort!))
- (let* ((vars* (fresh-vars vars))
- (bound (add-bound-vars vars vars* bound)))
- (define (recur* exp) (copy exp bound #t))
- (make-letrec src in-order? names vars* (map recur* vals)
- (recur* body))))
- (($ <fix> src names vars vals body)
- (unless in-lambda? (abort!))
- (let* ((vars* (fresh-vars vars))
- (bound (add-bound-vars vars vars* bound)))
- (define (recur* exp) (copy exp bound #t))
- (make-fix src names vars* (map recur* vals)
- (recur* body))))
- (($ <let-values> src exp body)
- (unless in-lambda? (abort!))
- (make-let-values src (recur exp) (recur body)))
- (($ <prompt> src escape-only? tag body handler)
- (unless in-lambda? (abort!))
- (make-prompt src escape-only?
- (recur tag) (recur body) (recur handler)))
- (($ <abort> src tag args tail)
- (unless in-lambda? (abort!))
- (make-abort src (recur tag) (map recur args) (recur tail)))))))
- (define (compute-inlinable-bindings exp)
- "Traverse @var{exp}, extracting module-level definitions."
- (define-values (modules lexicals binding-lexicals bindings)
- (compute-module-bindings exp))
- (define (kwarg-ref args kw kt kf)
- (let lp ((args args))
- (match args
- (() (kf))
- ((($ <const> _ (? keyword? kw')) val . args)
- (if (eq? kw' kw)
- (kt val)
- (lp args)))
- ((_ _ . args)
- (lp args)))))
- (define (kwarg-ref/const args kw kt kf)
- (kwarg-ref args kw
- (lambda (exp)
- (match exp
- (($ <const> _ val') (kt val'))
- (_ (kf))))
- kf))
- (define (has-constant-initarg? args kw val)
- (kwarg-ref/const args kw
- (lambda (val')
- (equal? val val'))
- (lambda () #f)))
- ;; Collect declarative modules defined once in this compilation unit.
- (define modules-with-inlinable-exports
- (let lp ((defs modules) (not-inlinable '()) (inlinable '()))
- (match defs
- (() inlinable)
- (((mod . args) . defs)
- (cond ((member mod not-inlinable)
- (lp defs not-inlinable inlinable))
- ((or (assoc mod defs) ;; doubly defined?
- (not (has-constant-initarg? args #:declarative? #t)))
- (lp defs (cons mod not-inlinable) inlinable))
- (else
- (lp defs not-inlinable (cons mod inlinable))))))))
- ;; Omit multiply-defined bindings, and definitions not in declarative
- ;; modules.
- (define non-declarative-definitions
- (let lp ((bindings bindings) (non-declarative '()))
- (match bindings
- (() non-declarative)
- ((((and mod+name (mod . name)) . val) . bindings)
- (cond
- ((member mod+name non-declarative)
- (lp bindings non-declarative))
- ((or (assoc mod+name bindings)
- (not (member mod modules-with-inlinable-exports)))
- (lp bindings (cons mod+name non-declarative)))
- (else
- (lp bindings non-declarative)))))))
- (define exports
- (map (lambda (module)
- (define args (assoc-ref modules module))
- ;; Return list of (PRIVATE-NAME . PUBLIC-NAME) pairs.
- (define (extract-exports kw)
- (kwarg-ref/const args kw
- (lambda (val)
- (map (match-lambda
- ((and pair (private . public)) pair)
- (name (cons name name)))
- val))
- (lambda () '())))
- (cons module
- (append (extract-exports #:exports)
- (extract-exports #:replacements))))
- modules-with-inlinable-exports))
- ;; Compute ((PRIVATE-NAME . PUBLIC-NAME) . VALUE) pairs for each
- ;; module with inlinable bindings, for exported bindings only.
- (define inlinable-candidates
- (map
- (lambda (module)
- (define name-pairs (assoc-ref exports module))
- (define (name-pair private-name)
- (assq private-name name-pairs))
- (cons module
- (filter-map
- (match-lambda
- (((and mod+name (mod . name)) . val)
- (and (equal? module mod)
- (not (member mod+name non-declarative-definitions))
- (and=> (name-pair name)
- (lambda (pair) (cons pair val))))))
- bindings)))
- modules-with-inlinable-exports))
- (define inlinables
- (filter-map
- (match-lambda
- ((mod . exports)
- (let ((name-pairs (map car exports)))
- (match (filter-map
- (match-lambda
- (((private . public) . val)
- (match (inlinable-exp mod name-pairs lexicals
- binding-lexicals val)
- (#f #f)
- (val (cons public val)))))
- exports)
- (() #f)
- (exports (cons mod exports))))))
- inlinable-candidates))
- inlinables)
- (define (put-uleb port val)
- (let lp ((val val))
- (let ((next (ash val -7)))
- (if (zero? next)
- (put-u8 port val)
- (begin
- (put-u8 port (logior #x80 (logand val #x7f)))
- (lp next))))))
- (define (known-vtable vtable)
- (define-syntax-rule (tree-il-case vt ...)
- (cond
- ((eq? vtable vt) (values '(language tree-il) 'vt))
- ...
- (else (values #f #f))))
- (tree-il-case <void>
- <const>
- <primitive-ref>
- <lexical-ref>
- <lexical-set>
- <module-ref>
- <module-set>
- <toplevel-ref>
- <toplevel-set>
- <toplevel-define>
- <conditional>
- <call>
- <primcall>
- <seq>
- <lambda>
- <lambda-case>
- <let>
- <letrec>
- <fix>
- <let-values>
- <prompt>
- <abort>))
- (define-record-type <encoding>
- (%make-encoding constants vtables pair-code vector-code symbol-code next-code)
- encoding?
- (constants constants)
- (vtables vtables)
- (pair-code pair-code set-pair-code!)
- (vector-code vector-code set-vector-code!)
- (symbol-code symbol-code set-symbol-code!)
- (next-code next-code set-next-code!))
- (define (make-encoding)
- (%make-encoding (make-hash-table) (make-hash-table) #f #f #f 0))
- (define (vtable-nfields vtable)
- (define vtable-index-size 5) ; FIXME: pull from struct.h
- (struct-ref/unboxed vtable vtable-index-size))
- (define (build-encoding! term encoding)
- (define (next-code!)
- (let ((code (next-code encoding)))
- (set-next-code! encoding (1+ code))
- code))
- (define (intern-constant! x)
- (unless (hash-ref (constants encoding) x)
- (hash-set! (constants encoding) x (next-code!))))
- (define (intern-vtable! x)
- (unless (hashq-ref (vtables encoding) x)
- (hashq-set! (vtables encoding) x (next-code!))))
- (define (ensure-pair-code!)
- (unless (pair-code encoding)
- (set-pair-code! encoding (next-code!))))
- (define (ensure-vector-code!)
- (unless (vector-code encoding)
- (set-vector-code! encoding (next-code!))))
- (define (ensure-symbol-code!)
- (unless (symbol-code encoding)
- (set-symbol-code! encoding (next-code!))))
- (let visit ((term term))
- (cond
- ((pair? term)
- (ensure-pair-code!)
- (visit (car term))
- (visit (cdr term)))
- ((vector? term)
- (ensure-vector-code!)
- (visit (vector-length term))
- (let lp ((i 0))
- (when (< i (vector-length term))
- (visit (vector-ref term i))
- (lp (1+ i)))))
- ((symbol? term)
- (ensure-symbol-code!)
- (visit (symbol->string term)))
- ((struct? term)
- (let ((vtable (struct-vtable term)))
- (unless (known-vtable vtable)
- (error "struct of unknown type" term))
- (intern-vtable! vtable)
- (let ((nfields (vtable-nfields vtable)))
- (let lp ((i 0))
- (when (< i nfields)
- (visit (struct-ref term i))
- (lp (1+ i)))))))
- (else
- (intern-constant! term)))))
- (define (compute-decoder encoding)
- (define (pair-clause code)
- `((eq? code ,code)
- (let* ((car (lp))
- (cdr (lp)))
- (cons car cdr))))
- (define (vector-clause code)
- `((eq? code ,code)
- (let* ((len (lp))
- (v (make-vector len)))
- (let init ((i 0))
- (when (< i len)
- (vector-set! v i (lp))
- (init (1+ i))))
- v)))
- (define (symbol-clause code)
- `((eq? code ,code)
- (string->symbol (lp))))
- (define (vtable-clause vtable code)
- (call-with-values (lambda () (known-vtable vtable))
- (lambda (mod name)
- (let ((fields (map (lambda (i) (string->symbol (format #f "f~a" i)))
- (iota (vtable-nfields vtable)))))
- `((eq? code ,code)
- (let* (,@(map (lambda (field) `(,field (lp))) fields))
- (make-struct/simple (@ ,mod ,name) ,@fields)))))))
- (define (constant-clause constant code)
- `((eq? code ,code) ',constant))
- (define (map-encodings f table)
- (map (match-lambda
- ((value . code) (f value code)))
- (sort (hash-map->list cons table)
- (match-lambda*
- (((_ . code1) (_ . code2)) (< code1 code2))))))
- `(lambda (bv)
- (define pos 0)
- (define (next-u8!)
- (let ((u8 (bytevector-u8-ref bv pos)))
- (set! pos (1+ pos))
- u8))
- (define (next-uleb!)
- ,(if (< (next-code encoding) #x80)
- ;; No need for uleb decoding in this case.
- '(next-u8!)
- ;; FIXME: We have a maximum code length and probably we
- ;; should just inline the corresponding decoder instead of
- ;; looping.
- '(let lp ((n 0) (shift 0))
- (let ((b (next-u8!)))
- (if (zero? (logand b #x80))
- (logior (ash b shift) n)
- (lp (logior (ash (logxor #x80 b) shift) n)
- (+ shift 7)))))))
- (let lp ()
- (let ((code (next-uleb!)))
- (cond
- ,@(if (pair-code encoding)
- (list (pair-clause (pair-code encoding)))
- '())
- ,@(if (vector-code encoding)
- (list (vector-clause (vector-code encoding)))
- '())
- ,@(if (symbol-code encoding)
- (list (symbol-clause (symbol-code encoding)))
- '())
- ,@(map-encodings vtable-clause (vtables encoding))
- ,@(map-encodings constant-clause (constants encoding))
- (else (error "bad code" code)))))))
- (define (encode term encoding)
- (call-with-output-bytevector
- (lambda (port)
- (define (put x) (put-uleb port x))
- (let visit ((term term))
- (cond
- ((pair? term)
- (put (pair-code encoding))
- (visit (car term))
- (visit (cdr term)))
- ((vector? term)
- (put (vector-code encoding))
- (visit (vector-length term))
- (let lp ((i 0))
- (when (< i (vector-length term))
- (visit (vector-ref term i))
- (lp (1+ i)))))
- ((symbol? term)
- (put (symbol-code encoding))
- (visit (symbol->string term)))
- ((struct? term)
- (let* ((vtable (struct-vtable term))
- (nfields (vtable-nfields vtable)))
- (put (hashq-ref (vtables encoding) vtable))
- (let lp ((i 0))
- (when (< i nfields)
- (visit (struct-ref term i))
- (lp (1+ i))))))
- (else
- (put (hash-ref (constants encoding) term))))))))
- (define (compute-encoding bindings)
- (let ((encoding (make-encoding)))
- (for-each (match-lambda
- ((name . expr) (build-encoding! expr encoding)))
- bindings)
- (let ((encoded (map (match-lambda
- ((name . expr) (cons name (encode expr encoding))))
- bindings)))
- `(lambda (name)
- (define decode ,(compute-decoder encoding))
- (cond
- ,@(map (match-lambda
- ((name . bv)
- `((eq? name ',name) (decode ,bv))))
- encoded)
- (else #f))))))
- (define encoding-module (current-module))
- (define (compile-inlinable-exports bindings)
- (let ((exp (compute-encoding bindings)))
- (fix-letrec
- (expand-primitives
- (resolve-primitives
- (compile-tree-il exp encoding-module '())
- encoding-module)))))
- (define (attach-inlinables exp inlinables)
- (post-order
- (lambda (exp)
- (match exp
- (($ <call> src (and proc ($ <module-ref> _ '(guile) 'define-module* #f))
- ((and m ($ <const> _ mod)) . args))
- (cond
- ((assoc-ref inlinables mod)
- => (lambda (bindings)
- (let ((inlinables (compile-inlinable-exports bindings)))
- (make-call src proc
- (cons* m
- (make-const #f #:inlinable-exports)
- inlinables
- args)))))
- (else exp)))
- (exp exp)))
- exp))
- (define (inlinable-exports exp)
- (attach-inlinables exp (compute-inlinable-bindings exp)))
|