File: merge.scm

package info (click to toggle)
scheme48 1.8%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 14,980 kB
  • ctags: 14,127
  • sloc: lisp: 76,272; ansic: 71,514; sh: 3,026; makefile: 637
file content (305 lines) | stat: -rw-r--r-- 10,170 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
; Copyright (c) 1993-2008 by Richard Kelsey.  See file COPYING.

; This code determines which procedures are called from one other form, and
; thus can be compiled as part of that form and called with a `goto' instead
; of a normal procedure call.  This saves much of the overhead of a normal
; procedure call.
;
; The procedures to be merged are annotated; no code is changed.

(define-subrecord form form-merge form-merge
  ((head)           ; self or the form into which this one will be merged
   )
  (
   (status #f)      ; one of #F, DO-NOT-MERGE, MERGED
   tail-clients     ; forms that call this one tail-recursively, this is an
                    ; a-list of forms and reference nodes
   (tail-providers '()) ; forms that are used by this one, this is a simple list
   (merged '())     ; forms merged with this one
   (return-count 0) ; how many returns have been generated so far
   temp             ; handy utility field
   ))

; Two procedures for letting the user know what is going on.

(define (show-merges form)
  (let ((merges (form-merged form)))
    (if (not (null? merges))
	(format #t " ~S: ~S~%" (form-name form) (map form-name merges)))))

(define (show-providers form)
  (cond ((eq? (form-type form) 'lambda)
	 (format #t "~S ~A~%"
		 (form-name form)
		 (if (form-exported? form) " (exported)" ""))
	 (cond ((or (not (null? (form-providers form)))
		    (not (null? (form-tail-providers form))))
		(format #t "  ~S~%  ~S~%"
			(map form-name (form-providers form))
			(map form-name (form-tail-providers form))))))))

; Note that OTHERS should be merged with FORM.

(define (do-merge form others)
  (let ((form (form-head form))
	(secondary (apply append (map form-merged others))))
    (set-form-merged! form (append others
				   secondary
				   (form-merged form)))
    (for-each (lambda (f)
		(set-form-head! f form))
	      secondary)
    (for-each (lambda (f)
		(set-form-head! f form)
		(set-form-status! f 'merged)
		(set-form-type! f 'merged)
		(set-form-merged! f '()))
	      others)))

; Returns the merged form, if any, to which NODE is a reference.

;(define (merged-procedure-reference node)
;  (let ((res (real-merged-procedure-reference node)))
;    (if (and (reference-node? node)
;             (eq? 'trace-value (variable-name (reference-variable node))))
;        (format "  [m-p-r ~S -> ~S]~%" node res))
;    res))
;
(define (merged-procedure-reference node)
  (cond ((and (reference-node? node)
	      (maybe-variable->form (reference-variable node)))
	 => (lambda (form)
	      (if (eq? 'merged (form-type form))
		  form
		  #f)))
	(else
	 #f)))

; Is FORM ever tail called?

(define (form-tail-called? form)
  (and (or (eq? 'lambda (form-type form))
	   (eq? 'merged (form-type form)))
       (memq? 'tail-called (variable-flags (form-var form)))))

; Annotate FORM if it is in fact called tail-recursively anywhere.

(define (note-tail-called-procedure form)
  (if (and (eq? 'lambda (form-type form))
	   (or (any (lambda (r)
		      (used-as-label? r))
		    (variable-refs (form-var form)))
	       (eq? 'tail-called (lambda-protocol (form-value form)))))
      (set-variable-flags! (form-var form)
			   (cons 'tail-called
				 (variable-flags (form-var form))))))

(define (used-as-label? node)
  (and (node? (node-parent node))
       (goto-call? (node-parent node))
       (= 1 (node-index node))))

;------------------------------------------------------------
; Entry point.
;
; First marks the tail-called procedures and adds the MERGE slots to the
; forms.  The C code generator expects FORM-MERGED to work, even if no
; actual merging was done.
;
; Three steps:
;  Find the call graph.
;  Merge the tail-called forms.
;  Merge the non-tail-called forms. 

(define *merge-forms?* #t)

(define (merge-forms forms)
  (for-each (lambda (f)
	      (note-tail-called-procedure f)
	      (set-form-merge! f (form-merge-maker f))
	      (set-form-providers! f '()))
	    forms)
  (if *merge-forms?*
      (let ((mergable-forms (filter determine-merger-graph forms)))
	(format #t "Call Graph:~%<procedure name>~%")
	(format #t "  <called non-tail-recursively>~%")
	(format #t "  <called tail-recursively>~%")
	(for-each show-providers forms)
	(format #t "Merging forms~%")
	(receive (tail other)
	    (partition-list (lambda (f) (null? (form-clients f)))
			    mergable-forms)
	  (merge-tail-forms tail)
	  (for-each merge-non-tail-forms forms)
	  (for-each show-merges forms)
	  (values)))))

; The only forms that can be merged are those that:
;  are lambdas,
;  all uses are calls,
;  are not exported, and
;  every loop containing a non-tail-recursive call must contain a call to
;  at least one non-merged procedure.
; 
; This code doesn't use the last criterion.  Instead it makes sure that each
; procedure is called exclusively tail-recursively or non-tail-recursively
; and doesn't allow non-tail-recursion in loops at all.

(define (determine-merger-graph form)
  (cond ((and (eq? 'lambda (form-type form))
	      (really-determine-merger-graph form)
	      (not (form-exported? form))
	      (or (null? (form-clients form))
		  (null? (form-tail-clients form))))
	 #t)
	(else
	 (set-form-status! form 'do-not-merge)
	 #f)))

; Loop down the references to FORM's variable adding FORM to the providers
; lists of the forms that reference the variable, and adding those forms
; to FORM's clients lists.  OKAY? is #T if all references are calls.

; The full usage graph is needed, even if there are uses of the form's value
; that are not calls.

(define (really-determine-merger-graph form)
  (let loop ((refs (variable-refs (form-var form)))
	     (clients '()) (tail-clients '()) (okay? #t))
    (cond ((null? refs)
	   (set-form-clients! form clients)
	   (set-form-tail-clients! form tail-clients)
	   okay?)
	  (else
	   (let* ((r (car refs))
		  (f (node-form (car refs))))
 	     (if (and (called-node? r)
 		      (or (calls-this-primop? (node-parent r) 'tail-call)
 			  (calls-this-primop? (node-parent r) 'unknown-tail-call)))
 		 (loop (cdr refs)
 		       clients
 		       (add-to-client-list tail-clients r form f
 					   form-tail-providers
 					   set-form-tail-providers!)
 		       okay?)
 		 (loop (cdr refs)
 		       (add-to-client-list clients r form f
 					   form-providers
 					   set-form-providers!)
 		       tail-clients
 		       (and okay? (called-node? r)))))))))

(define (add-to-client-list client-list ref form f getter setter)
  (cond ((assq f client-list)
	 => (lambda (p)
	      (set-cdr! p (cons ref (cdr p)))
	      client-list))
	(else
	 (setter f (cons form (getter f)))
	 (cons (list f ref) client-list))))

; These forms are non-exported procedures that are always tail-called.
; Strongly connected components of the call graph that have a single
; entry point, whether in the component or not, are merged.
; This depends on STRONGLY-CONNECTED-COMPONENTS returning the components
; in a reverse topologically sorted order (which it does).

(define (merge-tail-forms forms)
  (for-each merge-tail-loop
	    (reverse (strongly-connected-components
		      forms
		      (lambda (f)
			(filter (lambda (f) (memq? f forms))
				(map car (form-tail-clients f))))
		      form-temp
		      set-form-temp!))))

; ENTRIES are the forms in the loop that are called from outside.
; FORMS is used as a unique identifier here.

(define (merge-tail-loop forms)
  (for-each (lambda (f) (set-form-temp! f forms)) forms)
  (receive (entries other)
      (partition-list (lambda (f)
			(any? (lambda (p)
				(not (eq? forms
					  (form-temp (car p)))))
			      (form-tail-clients f)))
		      forms)
    (cond ((single-outside-client (if (null? entries)
				      other
				      entries)
				  forms)
	   => (lambda (f) (do-merge f forms)))
	  ((and (not (null? entries))
		(null? (cdr entries))
		(not (null? other)))
	   (do-merge (car entries) other)))
    (for-each (lambda (f) (set-form-temp! f #f)) forms)))

; This checks to see if all non-FLAGged clients of ENTRIES are in
; fact a single form, and then returns that form.
; Forms that have already been merged into another form are treated as that
; other form (by using FORM-HEAD).

(define (single-outside-client entries flag)
  (let loop ((entries entries) (form #f))
    (if (null? entries)
	form
	(let loop2 ((clients (form-tail-clients (car entries))) (form form))
	  (cond ((null? clients)
		 (loop (cdr entries) form))
		((eq? (form-temp (caar clients)) flag)
		 (loop2 (cdr clients) form))
		((not form)
		 (loop2 (cdr clients) (form-head (caar clients))))
		((eq? (form-head (caar clients)) form)
		 (loop2 (cdr clients) form))
		(else
		 #f))))))

; Merge the forms used by FORM into it if possible.

(define (merge-non-tail-forms form)
  (for-each (lambda (f)
	      (maybe-merge-non-tail-form f (form-head form)))
	    (form-providers form)))

; If FORM is not INTO, has not been merged before, and is only used by
; INTO, then merge FORM into INTO and recursively check the forms used
; by FORM.

(define (maybe-merge-non-tail-form form into)
  (cond ((and (not (eq? form into))
	      (not (form-status form))
	      (every? (lambda (p)
			(eq? (form-head (car p)) into))
		      (form-clients form)))
	 (do-merge into (list form))
	 (for-each tail-call->call
		   (variable-refs (form-var form)))
	 (for-each tail-call->call
		   (variable-refs (car (lambda-variables (form-node form)))))
	 (for-each (lambda (f)
		     (maybe-merge-non-tail-form f into))
		   (form-providers form)))))

; Replace tail calls with calls to make the code generator's job easier.
; The user didn't say that these calls had to be tail-recursive.

(define (tail-call->call ref)
  (let ((call (node-parent ref)))
    (if (or (calls-this-primop? call 'tail-call)
	    (calls-this-primop? call 'unknown-tail-call))
	(let ((type (arrow-type-result
		      (maybe-follow-uvar (node-type (call-arg call 1))))))
	  (move (call-arg call 0)
		(lambda (cont)
		  (let-nodes ((new-cont ((v type)) (return 0 cont (* v))))
		    new-cont)))
	  (set-call-exits! call 1)
	  (set-call-primop! call
			    (get-primop (if (calls-this-primop? call 'tail-call)
					    (enum primop call)
					    (enum primop unknown-call))))))))