eta-expand.scm 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. ;;; Making lexically-bound procedures well-known
  2. ;; Copyright (C) 2020, 2024 Free Software Foundation, Inc.
  3. ;;;; This library is free software; you can redistribute it and/or
  4. ;;;; modify it under the terms of the GNU Lesser General Public
  5. ;;;; License as published by the Free Software Foundation; either
  6. ;;;; version 3 of the License, or (at your option) any later version.
  7. ;;;;
  8. ;;;; This library is distributed in the hope that it will be useful,
  9. ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;;;; Lesser General Public License for more details.
  12. ;;;;
  13. ;;;; You should have received a copy of the GNU Lesser General Public
  14. ;;;; License along with this library; if not, write to the Free Software
  15. ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  16. (define-module (language tree-il eta-expand)
  17. #:use-module (ice-9 match)
  18. #:use-module (language tree-il)
  19. #:export (eta-expand))
  20. ;; A lexically-bound procedure that is used only in operator position --
  21. ;; i.e. the F in (F ARG ...) -- is said to be "well-known" if all of
  22. ;; its use sites are calls and they can all be enumerated. Well-known
  23. ;; procedures can be optimized in a number of important ways:
  24. ;; contification, call-by-label, shared closures, optimized closure
  25. ;; representation, and closure elision.
  26. ;;
  27. ;; All procedures in a source program can be converted to become
  28. ;; well-known by eta-expansion: wrapping them in a `lambda' that
  29. ;; dispatches to the target procedure. However, reckless eta-expansion
  30. ;; has two downsides. One drawback is that in some use cases,
  31. ;; eta-expansion just adds wrappers for no purpose: if there aren't
  32. ;; other uses of the procedure in operator position that could have
  33. ;; gotten the call-by-label treatment and closure optimization, there's
  34. ;; no point in making the closure well-known.
  35. ;;
  36. ;; The other drawback is that eta-expansion can confuse users who expect
  37. ;; a `lambda' term in a source program to have a unique object identity.
  38. ;; One might expect to associate a procedure with a value in an alist
  39. ;; and then look up that value later on, but if the looked-up procedure
  40. ;; is an eta-expanded wrapper, it won't be `eq?' to the previously-added
  41. ;; procedure. While this behavior is permitted by the R6RS, it breaks
  42. ;; user expectations, often for no good reason due to the first problem.
  43. ;;
  44. ;; Therefore in Guile we have struck a balance: we will eta-expand
  45. ;; procedures that are:
  46. ;; - lexically bound
  47. ;; - not assigned
  48. ;; - referenced at least once in operator position
  49. ;; - referenced at most once in value position
  50. ;;
  51. ;; These procedures will be eta-expanded in value position only. (We do
  52. ;; this by eta-expanding all qualifying references, then reducing those
  53. ;; expanded in call position.)
  54. ;;
  55. ;; In this way eta-expansion avoids introducing new procedure
  56. ;; identities.
  57. ;;
  58. ;; Additionally, for implementation simplicity we restrict to procedures
  59. ;; that only have required and possibly rest arguments.
  60. (define for-each-fold (make-tree-il-folder))
  61. (define (tree-il-for-each f x)
  62. (for-each-fold x (lambda (x) (f x) (values)) (lambda (x) (values))))
  63. (define (eta-expand expr)
  64. (define (analyze-procs)
  65. (define (proc-info proc)
  66. (vector 0 0 proc))
  67. (define (set-refcount! info count)
  68. (vector-set! info 0 count))
  69. (define (set-op-refcount! info count)
  70. (vector-set! info 1 count))
  71. (define proc-infos (make-hash-table))
  72. (define (maybe-add-proc! gensym val)
  73. (match val
  74. (($ <lambda> src1 meta
  75. ($ <lambda-case> src2 req () rest #f () syms body #f))
  76. (hashq-set! proc-infos gensym (proc-info val)))
  77. (_ #f)))
  78. (tree-il-for-each
  79. (lambda (expr)
  80. (match expr
  81. (($ <lexical-ref> src name gensym)
  82. (match (hashq-ref proc-infos gensym)
  83. (#f #f)
  84. ((and info #(total op proc))
  85. (set-refcount! info (1+ total)))))
  86. (($ <lexical-set> src name gensym)
  87. (hashq-remove! proc-infos gensym))
  88. (($ <call> src1 ($ <lexical-ref> src2 name gensym) args)
  89. (match (hashq-ref proc-infos gensym)
  90. (#f #f)
  91. ((and info #(total op proc))
  92. (set-op-refcount! info (1+ op)))))
  93. (($ <let> src names gensyms vals body)
  94. (for-each maybe-add-proc! gensyms vals))
  95. (($ <letrec> src in-order? names gensyms vals body)
  96. (for-each maybe-add-proc! gensyms vals))
  97. (($ <fix> src names gensyms vals body)
  98. (for-each maybe-add-proc! gensyms vals))
  99. (_ #f)))
  100. expr)
  101. (define to-expand (make-hash-table))
  102. (hash-for-each (lambda (sym info)
  103. (match info
  104. (#(total op proc)
  105. (when (and (not (zero? op))
  106. (= (- total op) 1))
  107. (hashq-set! to-expand sym proc)))))
  108. proc-infos)
  109. to-expand)
  110. (let ((to-expand (analyze-procs)))
  111. (define (eta-expand lexical)
  112. (match lexical
  113. (($ <lexical-ref> src name sym)
  114. (match (hashq-ref to-expand sym)
  115. (#f #f)
  116. (($ <lambda> src1 meta
  117. ($ <lambda-case> src2 req () rest #f () syms body #f))
  118. (let* ((syms (map gensym (map symbol->string syms)))
  119. (args (map (lambda (req sym) (make-lexical-ref src2 req sym))
  120. (if rest (append req (list rest)) req)
  121. syms))
  122. (body (if rest
  123. (make-primcall src 'apply (cons lexical args))
  124. (make-call src lexical args))))
  125. (make-lambda src1 meta
  126. (make-lambda-case src2 req '() rest #f '() syms
  127. body #f))))))))
  128. (define (eta-reduce proc)
  129. (match proc
  130. (($ <lambda> _ meta
  131. ($ <lambda-case> _ req () #f #f () syms
  132. ($ <call> src ($ <lexical-ref> _ name sym)
  133. (($ <lexical-ref> _ _ arg) ...))
  134. #f))
  135. (and (equal? arg syms)
  136. (make-lexical-ref src name sym)))
  137. (($ <lambda> _ meta
  138. ($ <lambda-case> _ req () (not #f) #f () syms
  139. ($ <primcall> src 'apply
  140. (($ <lexical-ref> _ name sym) ($ <lexical-ref> _ _ arg) ...))
  141. #f))
  142. (and (equal? arg syms)
  143. (make-lexical-ref src name sym)))
  144. (_ #f)))
  145. (post-order
  146. (lambda (expr)
  147. (match expr
  148. (($ <lexical-ref>)
  149. (or (eta-expand expr)
  150. expr))
  151. (($ <call> src proc args)
  152. (match (eta-reduce proc)
  153. (#f expr)
  154. (proc (make-call src proc args))))
  155. (_ expr)))
  156. expr)))