wingo pushed a commit to branch wip-inlinable-exports
in repository guile.

commit cff797f7179f009114f26a4a715fe35a943169b3
Author: Andy Wingo <wi...@pobox.com>
AuthorDate: Tue Mar 23 21:11:44 2021 +0100

    Add support for recording inlinable module exports
    
    * module/language/tree-il/inlinable-exports.scm: New module.
    * am/bootstrap.am:
    * module/Makefile.am:
    * module/language/tree-il/optimize.scm (make-optimizer):
    * module/system/base/optimize.scm (available-optimizations): Wire up new
    module.
---
 am/bootstrap.am                               |   1 +
 module/Makefile.am                            |   1 +
 module/language/tree-il/inlinable-exports.scm | 771 ++++++++++++++++++++++++++
 module/language/tree-il/optimize.scm          |   6 +-
 module/system/base/optimize.scm               |   6 +-
 5 files changed, 781 insertions(+), 4 deletions(-)

diff --git a/am/bootstrap.am b/am/bootstrap.am
index 19e7af8..db8ec32 100644
--- a/am/bootstrap.am
+++ b/am/bootstrap.am
@@ -63,6 +63,7 @@ SOURCES =                                     \
   language/tree-il/effects.scm                 \
   language/tree-il/eta-expand.scm              \
   language/tree-il/fix-letrec.scm              \
+  language/tree-il/inlinable-exports.scm       \
   language/tree-il/letrectify.scm              \
   language/tree-il/optimize.scm                        \
   language/tree-il/peval.scm                   \
diff --git a/module/Makefile.am b/module/Makefile.am
index 30deeb7..e4f8e5c 100644
--- a/module/Makefile.am
+++ b/module/Makefile.am
@@ -88,6 +88,7 @@ SOURCES =                                     \
   language/tree-il/effects.scm                 \
   language/tree-il/eta-expand.scm              \
   language/tree-il/fix-letrec.scm              \
+  language/tree-il/inlinable-exports.scm       \
   language/tree-il/letrectify.scm              \
   language/tree-il/optimize.scm                        \
   language/tree-il/peval.scm                   \
diff --git a/module/language/tree-il/inlinable-exports.scm 
b/module/language/tree-il/inlinable-exports.scm
new file mode 100644
index 0000000..0c8057c
--- /dev/null
+++ b/module/language/tree-il/inlinable-exports.scm
@@ -0,0 +1,771 @@
+;;; Serializing Tree-IL to bytes
+;;; Copyright (C) 2021
+;;;   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 ((srfi srfi-1) #:select (filter-map))
+  #:use-module (srfi srfi-9)
+  #:use-module (system syntax)
+  #:export (inlinable-exports))
+
+;; versions.
+
+(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>))
+
+;;
+;; Design -- assembler can already encode constants
+
+(define-record-type <encoding>
+  (%make-encoding constants vtables pair-code next-code)
+  encoding?
+  (constants constants)
+  (vtables vtables)
+  (pair-code pair-code set-pair-code!)
+  (next-code next-code set-next-code!))
+
+(define (make-encoding)
+  (%make-encoding (make-hash-table) (make-hash-table) #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!))))
+
+  (let visit ((term term))
+    (cond
+     ((pair? term)
+      (ensure-pair-code!)
+      (visit (car term))
+      (visit (cdr 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 (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/no-tail (@ ,mod ,name) ,@fields)))))))
+  (define (constant-clause constant code)
+    `((eq? code ,code) ',constant))
+
+  `(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!)
+            '(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)))
+                '())
+          ,@(hash-map->list vtable-clause (vtables encoding))
+          ,@(hash-map->list 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)))
+        ((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))))))
+
+;; pending:
+;;
+;; - add module field.
+;;
+;; - 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.
+;;
+;; - yeet the generated encoding into the define-module invocation.
+;;
+;; - make peval reach into these inlinable procs.
+
+;; Two forms of top-level definitions:
+;;   toplevel-define
+;;   module-ensure-local-variable! + %variable-set!
+
+(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!) (pk 'abort!) (return #f))
+    (define count!
+      ;; Same as default operator size limit for peval.
+      (let ((counter 40))
+        (lambda ()
+          (set! counter (1- counter))
+          (when (zero? counter) (pk 'counter-exceeded) (abort!)))))
+    (define (residualize-module-private-ref src mod' name)
+      ;; TODO: At -O3, we could residualize a private
+      ;; reference.  But that could break peoples'
+      ;; expectations.
+      (pk 'private-ref! src mod' name)
+      (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?))
+            (pk 'constant-too-big val) (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.
+           (pk 'toplevel-outside-lambda) (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 (pk 'free-var-ref) (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? (pk 'in-lambda) (abort!))
+         (make-call src (recur proc) (map recur args)))
+
+        (($ <primcall> src name args)
+         (unless in-lambda? (pk 'in-lambda) (abort!))
+         (make-primcall src name (map recur args)))
+
+        (($ <conditional> src test consequent alternate)
+         (unless in-lambda? (pk 'in-lambda) (abort!))
+         (make-conditional src (recur test)
+                           (recur consequent) (recur alternate)))
+
+        (($ <lexical-set> src name var exp)
+         (unless in-lambda? (pk 'in-lambda) (abort!))
+         (cond
+          ((assq-ref bound var)
+           => (lambda (var)
+                (make-lexical-set src name var (recur exp))))
+          (else
+           (pk 'set-to-free-var)
+           (abort!))))
+
+        ((or ($ <toplevel-set>)
+             ($ <module-set>)
+             ($ <toplevel-define>))
+         (pk 'bad-exp exp)
+         (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? (pk '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? (pk 'in-lambda) (abort!))
+         (make-seq src (recur head) (recur tail)))
+        
+        (($ <let> src names vars vals body)
+         (unless in-lambda? (pk '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? (pk '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? (pk '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? (pk 'in-lambda) (abort!))
+         (make-let-values src (recur exp) (recur body)))
+
+        (($ <prompt> src escape-only? tag body handler)
+         (unless in-lambda? (pk 'in-lambda) (abort!))
+         (make-prompt src escape-only?
+                      (recur tag) (recur body) (recur handler)))
+
+        (($ <abort> src tag args tail)
+         (unless in-lambda? (pk '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
+                    ((name-pair . val)
+                     (pk 'attempting name-pair val)
+                     (match (inlinable-exp mod name-pairs lexicals
+                                           binding-lexicals val)
+                       (#f (pk 'nope #f))
+                       (val (pk 'yep (cons name-pair val))))))
+                  exports)
+            (() #f)
+            (exports (cons mod exports))))))
+     inlinable-candidates))
+
+  inlinables)
+
+(use-modules (system base compile) (language tree-il optimize))
+(define* (test-inlinables file #:key (optimization-level 2))
+  (define env (current-module))
+  (define term (call-with-input-file file
+                 (lambda (p) (read-and-compile p #:to 'tree-il #:env env))))
+  (define opt ((make-lowerer optimization-level '()) term env))
+  (compute-inlinable-bindings opt))
+
+(define (inlinable-exports exp)
+  ;(attach-inlinables exp (compute-inlinable-bindings exp))
+  exp)
diff --git a/module/language/tree-il/optimize.scm 
b/module/language/tree-il/optimize.scm
index ba55f97..264cd64 100644
--- a/module/language/tree-il/optimize.scm
+++ b/module/language/tree-il/optimize.scm
@@ -1,6 +1,6 @@
 ;;; Tree-il optimizer
 
-;; Copyright (C) 2009, 2010-2015, 2018-2020 Free Software Foundation, Inc.
+;; Copyright (C) 2009, 2010-2015, 2018-2021 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
@@ -45,7 +45,8 @@
         (letrectify (lookup #:letrectify? letrectify))
         (seal?      (assq-ref opts #:seal-private-bindings?))
         (peval      (lookup #:partial-eval? peval))
-        (eta-expand (lookup #:eta-expand? eta-expand)))
+        (eta-expand (lookup #:eta-expand? eta-expand))
+        (inlinables (lookup #:inlinable-exports? inlinable-exports)))
     (define-syntax-rule (run-pass! (proc exp arg ...))
       (when proc (set! exp (verify (proc exp arg ...)))))
     (lambda (exp env)
@@ -57,6 +58,7 @@
       (run-pass! (fix-letrec exp))
       (run-pass! (peval exp env))
       (run-pass! (eta-expand exp))
+      (run-pass! (inlinables exp))
       exp)))
 
 (define (optimize x env opts)
diff --git a/module/system/base/optimize.scm b/module/system/base/optimize.scm
index 93f692f..94a79e3 100644
--- a/module/system/base/optimize.scm
+++ b/module/system/base/optimize.scm
@@ -1,6 +1,6 @@
 ;;; Optimization flags
 
-;; Copyright (C) 2018, 2020 Free Software Foundation, Inc.
+;; Copyright (C) 2018, 2020, 2021 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
@@ -34,7 +34,9 @@
        (#:letrectify? 2)
        (#:seal-private-bindings? 3)
        (#:partial-eval? 1)
-       (#:eta-expand? 2)))
+       (#:eta-expand? 2)
+       (#:inlinable-exports? 1)
+       (#:cross-module-inlining? 2)))
     ('cps
      '( ;; (#:split-rec? #t)
        (#:simplify? 2)

Reply via email to