jump.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. ;;; Ported from Scheme 48 1.9. See file COPYING for notices and license.
  2. ;;;
  3. ;;; Port Author: Andrew Whatson
  4. ;;;
  5. ;;; Original Authors: Richard Kelsey, Mike Sperber, Eric Knauel
  6. ;;;
  7. ;;; scheme48-1.9.2/ps-compiler/front/jump.scm
  8. ;;;
  9. ;;; Code to turn PROC lambdas into JUMP lambdas.
  10. ;;;
  11. ;;; FIND-JUMP-PROCS returns two lists. The first contains lists of the form
  12. ;;; (<continuation> <proc-lambda> ...)
  13. ;;; indicating that these lambda nodes are all
  14. ;;; called with <continuation> as their only continuation. The second list
  15. ;;; is of procedures that are called only by each other. The procedures in
  16. ;;; the second list are deleted. Those in the first list are converted to
  17. ;;; JUMP lambdas
  18. ;;;
  19. ;;; INTEGRATE-JUMP-PROCS! returns #T if any change is made to the program.
  20. (define-module (ps-compiler front jump)
  21. #:use-module (srfi srfi-9)
  22. #:use-module (prescheme scheme48)
  23. #:use-module (prescheme record-discloser)
  24. #:use-module (ps-compiler node arch)
  25. #:use-module (ps-compiler node node)
  26. #:use-module (ps-compiler node node-equal)
  27. #:use-module (ps-compiler node node-letrec)
  28. #:use-module (ps-compiler node node-util)
  29. #:use-module (ps-compiler node primop)
  30. #:use-module (ps-compiler node variable)
  31. #:use-module (ps-compiler param)
  32. #:use-module (ps-compiler util ssa)
  33. #:use-module (ps-compiler util util)
  34. #:export (integrate-jump-procs!
  35. find-jump-procs
  36. procs->jumps))
  37. (define (integrate-jump-procs! node)
  38. (receive (hits useless)
  39. (find-jump-procs (filter proc-lambda?
  40. (make-lambda-list))
  41. find-calls)
  42. (remove-unused-procedures! useless)
  43. (let loop ((changed? #f))
  44. (receive (hits useless)
  45. (find-jump-procs (filter proc-lambda?
  46. (make-lambda-list))
  47. find-calls)
  48. (if (null? hits)
  49. changed?
  50. (let ((p (car hits)))
  51. (procs->jumps (cdr p)
  52. (map bound-to-variable (cdr p))
  53. (car p))
  54. (loop #t)))))))
  55. ;; We want to find subsets of ALL-PROCS such that all elements of a subset
  56. ;; are always called with the same continuation. (PROC->USES <proc>) returns
  57. ;; the references to <proc> that are calls, or #f if there are references that
  58. ;; are not calls.
  59. ;;
  60. ;; We proceed as follows:
  61. ;; 1. Partition the procs depending on whether all their calls are known or not.
  62. ;; 2. Build a call graph:
  63. ;; Nodes represent either procedures or continuations. If there is a
  64. ;; tail-recursive call to procedure B in procedure A, then there is an
  65. ;; edge from A to B. For continuation C such that there is a call in
  66. ;; procedure A to procedure B with that continuation, there are edges
  67. ;; from A to C and from C to B.
  68. ;; In other words, it is a call graph where the edges that represent
  69. ;; non-tail-recursive calls are replaced by two edges, with a node for
  70. ;; the continuation in between.
  71. ;; There is a special root node (representing `outside'), that has
  72. ;; edges to the nodes representing procedures whose call sites have not
  73. ;; been identified.
  74. ;; 3. Determine the dominance frontiers in the graph.
  75. ;; 4. Find the nodes in the graph that are reachable from more than one
  76. ;; continuation (the joins).
  77. ;; 5. Starting from each node that represents a continuation (the joins,
  78. ;; procs whose calls aren't known, and the continuations themselves),
  79. ;; find the set of nodes reachable from that node without going through
  80. ;; some other continuation node.
  81. (define (find-jump-procs all-procs proc->uses)
  82. (for-each (lambda (l)
  83. (set-lambda-block! l (make-node l #f)))
  84. all-procs)
  85. (receive (known unknown)
  86. (partition-list calls-known? all-procs)
  87. (let ((root (make-node #f #f))
  88. (conts-cell (list '()))
  89. (known-blocks (map lambda-block known))
  90. (procs-cell (list (map lambda-block unknown))))
  91. (note-calls! known conts-cell procs-cell proc->uses)
  92. (let ((unknown-blocks (car procs-cell))
  93. (conts (car conts-cell)))
  94. (set-node-successors! root unknown-blocks)
  95. (graph->ssa-graph! root node-successors node-temp set-node-temp!)
  96. (let ((joins (find-joins (append conts unknown-blocks) node-temp)))
  97. (for-each (lambda (n)
  98. (set-node-join?! n #t))
  99. joins)
  100. (let* ((mergable (filter-map find-mergable
  101. (append joins unknown-blocks conts)))
  102. (useless (filter (lambda (p)
  103. (not (or (node-join? (lambda-block p))
  104. (node-merged? (lambda-block p)))))
  105. known)))
  106. (for-each (lambda (p)
  107. (set-lambda-block! p #f))
  108. all-procs)
  109. (values mergable useless)))))))
  110. ;; Make a call graph with extra nodes inserted for continuations:
  111. ;;
  112. ;; If F calls G tail-recursively, add an edge F->G
  113. ;; If F calls G ... with continuation K, add a node K and edges F->K, K->G ...
  114. ;;
  115. ;; Then FIND-JOINS will return a list of the nodes that are passed two or
  116. ;; more distinct continuations. The rest can be merged with their callers.
  117. ;;
  118. ;; Need a root node, so make one that points to all procs with unknown calls.
  119. (define-record-type :node
  120. (really-make-node proc cont successors join? merged?)
  121. node?
  122. (proc node-proc) ;; lambda node (or #f for continuation holders)
  123. (cont node-cont) ;; lambda node (or #f for procs)
  124. (successors node-successors set-node-successors!)
  125. (temp node-temp set-node-temp!)
  126. (join? node-join? set-node-join?!)
  127. (merged? node-merged? set-node-merged?!))
  128. (define (make-node proc cont)
  129. (really-make-node proc cont '() #f #f))
  130. (define-record-discloser :node
  131. (lambda (node)
  132. (list 'node (node-proc node) (node-cont node))))
  133. (define (add-child! parent child)
  134. (if (not (memq? child (node-successors parent)))
  135. (set-node-successors! parent
  136. (cons child
  137. (node-successors parent)))))
  138. ;; Walk KNOWN-PROCS adding edges to the call graph.
  139. (define (note-calls! known-procs conts-cell procs-cell proc->uses)
  140. (for-each (lambda (proc)
  141. (for-each (lambda (ref)
  142. (note-call! (lambda-block proc)
  143. ref
  144. conts-cell procs-cell))
  145. (proc->uses proc)))
  146. known-procs))
  147. ;; Add an edge from the node containing REF to PROC-NODE. Tail calls add an
  148. ;; edge directly from the calling node, non-tail calls add an edge from the
  149. ;; successor to the calling node that represents the call's continuation.
  150. (define (note-call! proc-node ref conts-cell procs-cell)
  151. (let ((caller (get-lambda-block (containing-procedure ref) procs-cell)))
  152. (add-child! (if (calls-this-primop? (node-parent ref) 'tail-call)
  153. caller
  154. (get-cont-block caller
  155. (call-arg (node-parent ref) 0)
  156. conts-cell))
  157. proc-node)))
  158. ;; Get the block for lambda-node PROC, making a new one if necessary.
  159. (define (get-lambda-block proc procs-cell)
  160. (let ((block (lambda-block proc)))
  161. (if (node? block)
  162. block
  163. (let ((new (make-node proc #f)))
  164. (set-lambda-block! proc new)
  165. (set-car! procs-cell (cons new (car procs-cell)))
  166. new))))
  167. ;; Get the successor to CALLER containing CONT, making it if necessary.
  168. (define (get-cont-block caller cont conts-cell)
  169. (or (any (lambda (node)
  170. (and (node-cont node)
  171. (node-equal? cont (node-cont node))))
  172. (node-successors caller))
  173. (let ((cont-node (make-node #f cont)))
  174. (set-car! conts-cell (cons cont-node (car conts-cell)))
  175. (add-child! caller cont-node)
  176. cont-node)))
  177. ;;----------------
  178. (define (find-mergable node)
  179. (let ((mergable (really-find-mergable node)))
  180. (if (null? mergable)
  181. #f
  182. (cons (or (node-cont node)
  183. (car (variable-refs
  184. (car (lambda-variables (node-proc node))))))
  185. mergable))))
  186. (define (really-find-mergable node)
  187. (let recur ((nodes (node-successors node)) (res '()))
  188. (if (null? nodes)
  189. res
  190. (recur (cdr nodes)
  191. (let ((node (car nodes)))
  192. (cond ((or (node-join? node) ;; gets two or more continuations
  193. (node-merged? node) ;; already merged
  194. (node-cont node)) ;; different continuation
  195. res)
  196. ;; ((node-cont node) ;; not a lambda
  197. ;; (recur (node-successors node) res))
  198. (else
  199. (set-node-merged?! node #t)
  200. (recur (node-successors node)
  201. (cons (node-proc node) res)))))))))
  202. ;;----------------
  203. ;; Part 2. PROCS is a list of procedures that are only called by each other;
  204. ;; with no entry point they are useless and can be removed.
  205. (define (remove-unused-procedures! procs)
  206. (for-each (lambda (proc)
  207. (let ((var (bound-to-variable proc)))
  208. (if (not var)
  209. (bug "known procedure has no variable ~S" proc))
  210. (format #t "Removing unused procedure: ~S_~S~%"
  211. (variable-name var) (variable-id var))
  212. (let ((parent (node-parent proc)))
  213. (mark-changed proc)
  214. (detach-bound-value var proc)
  215. (erase proc))))
  216. procs))
  217. ;;----------------
  218. ;; Part 3. Turn JUMP-PROCS from procs to jumps. CONT is the continuation they
  219. ;; all receive, and is also turned into a jump.
  220. ;; This creates a LETREC to bind all CONT and any of JUMP-PROCS that are
  221. ;; passed CONT directly and are bound above the LCA of all calls to JUMP-PROCS
  222. ;; that use CONT. Then every jump-proc is changed from a proc lambda to a
  223. ;; jump lambda and has its continuation removed. Returns are replaced with
  224. ;; jumps to CONT. If CONT is not a variable some protocol adjustment may be
  225. ;; required.
  226. (define (procs->jumps jump-procs vars cont)
  227. (receive (called-vars called-procs lca)
  228. (find-cont-uses cont vars jump-procs)
  229. (let ((proc (containing-procedure cont))
  230. (lca (if (call-node? lca) lca (node-parent lca)))
  231. (cvar (if (lambda-node? cont)
  232. (make-variable 'w (node-type cont))
  233. #f)))
  234. (receive (called-vars called-procs)
  235. (bound-above? lca called-vars called-procs)
  236. (receive (called-vars called-procs)
  237. (filter-ancestors called-vars called-procs)
  238. (for-each detach-bound-value called-vars called-procs)
  239. (cond ((lambda-node? cont)
  240. (determine-continuation-protocol cont jump-procs)
  241. (let ((cont-copy (copy-node-tree cont)))
  242. (change-lambda-type cont-copy 'jump)
  243. (put-in-letrec (cons cvar
  244. called-vars)
  245. (cons cont-copy
  246. called-procs)
  247. lca)))
  248. (else
  249. (put-in-letrec called-vars called-procs lca))))
  250. (for-each proc-calls->jumps jump-procs)
  251. (for-each (lambda (p)
  252. (let* ((v (car (lambda-variables p)))
  253. (refs (variable-refs v)))
  254. (set-variable-refs! v '())
  255. (for-each (lambda (r)
  256. (if (lambda-node? cont)
  257. (return->jump (node-parent r) cvar)
  258. (replace r (make-reference-node
  259. (car (lambda-variables proc))))))
  260. refs)
  261. (remove-variable p v)))
  262. jump-procs)
  263. (values)))))
  264. ;; Returns those of VARS and VALS where there is a call to the variable that
  265. ;; passes CONT as a continuation, or where the variable is not bound. The
  266. ;; third value returned is the least-common-ancestor of all calls to VARS
  267. ;; that use CONT.
  268. ;;
  269. ;; Why exclude uncalled variables just because they are bound?
  270. (define (find-cont-uses cont vars vals)
  271. (let loop ((vars vars) (vals vals) (r-vars '()) (r-vals '()) (uses '()))
  272. (if (null? vars)
  273. (values r-vars
  274. r-vals
  275. (least-common-ancestor uses))
  276. (let ref-loop ((refs (variable-refs (car vars))) (my-uses uses))
  277. (cond ((not (null? refs))
  278. (ref-loop (cdr refs)
  279. (if (node-equal? cont
  280. (call-arg (node-parent (car refs))
  281. 0))
  282. (cons (car refs) my-uses)
  283. my-uses)))
  284. ;; Why was this here? It breaks for some examples.
  285. ;; ((and (variable-binder (car vars))
  286. ;; (eq? my-uses uses))
  287. ;; (loop (cdr vars) (cdr vals) r-vars r-vals uses))
  288. (else
  289. (loop (cdr vars) (cdr vals)
  290. (cons (car vars) r-vars)
  291. (cons (car vals) r-vals)
  292. my-uses)))))))
  293. ;; Return the list of VARS and VALS where the variable is either global
  294. ;; or bound above CALL.
  295. (define (bound-above? call vars vals)
  296. (set-node-flag! call #t)
  297. (let loop ((vars vars) (vals vals) (r-vars '()) (r-vals '()))
  298. (cond ((null? vars)
  299. (set-node-flag! call #f)
  300. (values r-vars r-vals))
  301. ((and (variable-binder (car vars))
  302. (marked-ancestor (variable-binder (car vars))))
  303. (loop (cdr vars) (cdr vals) r-vars r-vals))
  304. (else
  305. (loop (cdr vars) (cdr vals)
  306. (cons (car vars) r-vars)
  307. (cons (car vals) r-vals))))))
  308. ;; Filter the list of VARS and VALS so that none of the nodes
  309. ;; contained in any of the other is included.
  310. ;; If we didn't do this, we might hoist them and mess with the scoping.
  311. (define (filter-ancestors all-vars all-vals)
  312. (let loop ((vars all-vars) (vals all-vals) (r-vars '()) (r-vals '()))
  313. (cond
  314. ((null? vars)
  315. (values r-vars r-vals))
  316. ((any (lambda (other-val)
  317. (and (not (eq? (car vals) other-val))
  318. (node-ancestor? other-val (car vals))))
  319. all-vals)
  320. (loop (cdr vars) (cdr vals) r-vars r-vals))
  321. (else
  322. (loop (cdr vars) (cdr vals)
  323. (cons (car vars) r-vars)
  324. (cons (car vals) r-vals))))))
  325. (define (detach-bound-value var node)
  326. (if (variable-binder var)
  327. (let ((binder (variable-binder var))
  328. (parent (node-parent node))
  329. (index (node-index node)))
  330. (set-lambda-variables! binder (delq! var (lambda-variables binder)))
  331. (detach node)
  332. (remove-call-arg parent index))))
  333. ;; Turn all calls to PROC into jumps.
  334. (define (proc-calls->jumps proc)
  335. (for-each (lambda (n)
  336. (call->jump (node-parent n)))
  337. (find-calls proc))
  338. (change-lambda-type proc 'jump))
  339. ;; Change a call to a jump by changing the primop and removing the continuation.
  340. (define (call->jump call)
  341. (case (primop-id (call-primop call))
  342. ((call tail-call)
  343. (set-call-primop! call (get-primop (enum primop-enum jump)))
  344. (remove-call-arg call 0))
  345. (else
  346. (bug "odd call primop ~S" (call-primop call)))))
  347. ;; Change a return to a jump. VAR is a variable bound to JUMP, the lambda
  348. ;; being jumped to.
  349. (define (return->jump call var)
  350. (case (primop-id (call-primop call))
  351. ((return)
  352. (set-call-primop! call (get-primop (enum primop-enum jump)))
  353. (replace (call-arg call 0) (make-reference-node var)))
  354. (else
  355. (bug "odd return primop ~S" (call-primop call)))))