File: to-cps.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 (631 lines) | stat: -rw-r--r-- 20,449 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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
; Copyright (c) 1993-2008 by Richard Kelsey.  See file COPYING.

; Convert a byte-code-compiler node into a cps node.

; Entry point.

(define (x->cps node name)
  (receive (value first-call last-lambda)
      (cps node)
    (if first-call
	(bug "(X->CPS ~S) got a non-value" node))
    (maybe-add-name! value name)
    value))

;----------------------------------------------------------------
; (CPS <node>)
;   -> <value> <first-call> <last-lambda>
; <value> is the CPSed value of <node>.  If <node> contains no non-trivial
; calls, <first-call> and <last-lambda> are both #f.  Otherwise they are
; the first of the non-trivial calls and the continuation of the last.

(define (cps node)
  (receive (value first-call last-lambda)
      (real-cps node)
    (let ((value (cond ((not (list? value))
			value)
		       ((or (null? value)
			    (not (null? (cdr value))))
			(bug "value expression did not return one value ~S"
			     (schemify node)))
		       (else
			(car value)))))
      (values value first-call last-lambda))))
    
; Same as above except that <value> is a list of values.

(define (values-cps node)
  (receive (value first-call last-lambda)
      (real-cps node)
    (values (if (list? value)
		value
		(list value))
	    first-call
	    last-lambda)))

(define (real-cps node)
  ((operator-table-ref cps-converters
		       (node-operator-id node))
     node))

(define cps-converters
  (make-operator-table
   (lambda (node id)
     (error "no cps-converter for node ~S" node))))

(define (define-cps-converter name proc)
  (operator-define! cps-converters name #f proc))

;----------------------------------------------------------------
; (TAIL-CPS <node> <continuation-variable>)
;   -> <first-call>

(define (tail-cps node cont-var)
  ((operator-table-ref tail-cps-converters (node-operator-id node))
     node
     cont-var))

(define tail-cps-converters
  (make-operator-table
    (lambda (node cont-var)
      (error "no tail-cps-converter for node ~S" node))))

(define (define-tail-cps-converter name proc)
  (operator-define! tail-cps-converters name #f proc))

; Use PROC in the CPS table and give it a wrapper that makes a return for use
; in the TAIL-CPS table.

(define (define-value-cps-converter name proc)
  (operator-define! cps-converters name #f
		    (lambda (node)
		      (values (proc node) #f #f)))
  (operator-define! tail-cps-converters name #f
		    (lambda (node cont-var)
		      (make-return cont-var (proc node)))))

; El Hacko Grande: we use the name of the CONT-VAR to determine whether
; it is a return or a join point.

(define (join? var)
  (case (variable-name var)
    ((c) #f)
    ((j) #t)
    (else
     (bug "funny continuation variable name ~S" var))))

(define (make-return cont-var value)
  (really-make-return cont-var (list value)))

(define (make-multiple-value-return cont-var values)
  (really-make-return cont-var values))

(define (really-make-return cont-var values)
  (let ((return (make-call-node
		 (get-primop (if (join? cont-var)
				 (enum primop jump)
				 (enum primop unknown-return)))
		 (+ 1 (length values))
		 0)))
    (attach-call-args return (cons (make-reference-node cont-var) values))
    return))

;----------------------------------------------------------------
; Constants are easy.

(define-value-cps-converter 'literal
  (lambda (node)
    (cps-literal (node-form node) node)))

(define-value-cps-converter 'quote
  (lambda (node)
    (cps-literal (cadr (node-form node)) node)))

(define (cps-literal value node)
  (make-literal-node value (node-type node)))

(define-value-cps-converter 'unspecific
  (lambda (node)
    (make-unspecific)))

(define (make-unspecific)
  (make-call-node (get-prescheme-primop 'unspecific) 0 0))

; Used for primitives in non-call position.  The CDR of the form is a
; variable that will be bound to the primitive's closed-compiled value.

(define-value-cps-converter 'primitive
  (lambda (node)
    (make-reference-node (cdr (node-form node)))))

;----------------------------------------------------------------

(define-value-cps-converter 'lambda
  (lambda (node)
    (let ((form (node-form node))
	  (cont-var (make-variable 'c (lambda-node-return-type node)))
	  (vars (map (lambda (name)
		       (let ((var (make-variable (name-node->symbol name)
						 (node-type name))))
			 (node-set! name 'variable var)
			 var))
		     (cadr (node-form node)))))
      (let ((lnode (make-lambda-node 'p 'proc (cons cont-var vars))))
	(attach-body lnode (tail-cps (caddr form) cont-var))
	lnode))))

;----------------------------------------------------------------
; References and SET!

(define-value-cps-converter 'name
  (lambda (node)
    (cond ((node-ref node 'variable)
	   => make-reference-node)    ; eventually have to check for SET!'s
	  ((node-ref node 'binding)
	   => (lambda (binding)
		(let ((var (binding-place binding)))
		  (cond ((not (variable? var))
			 (bug "binding for ~S has funny place ~S" node var))
			((variable-set!? var)
			 (make-global-ref var))
			(else
			 (make-reference-node var))))))
	  (else
	   (bug "name node ~S has neither variable nor binding" node)))))
  
(define (make-global-ref var)
  ((structure-ref node let-nodes)
     ((call (global-ref 0 (* var))))
   call))

; Stolen from form.scm as an expedient.  This needs to be moved to somewhere
; that both FORMS and TO-CPS can see it.

(define (variable-set!? var)
  (memq 'set! (variable-flags var)))

(define-cps-converter 'set!
  (lambda (node)
    (receive (first-call last-lambda)
	(make-global-set! (node-form node))
      (values (make-unspecific) first-call last-lambda))))

(define-tail-cps-converter 'set!
  (lambda (node cont-var)
    (receive (first-call last-lambda)
	(make-global-set! (node-form node))
      (attach-body last-lambda (make-return cont-var (make-unspecific)))
      first-call)))

(define (make-global-set! form)
  (let ((name (cadr form))
	(value (caddr form)))
    (receive (value first-call last-lambda)
	(cps value)
      (maybe-add-name! value name)
      (let ((cont (make-lambda-node 'c 'cont '()))
	    (var (name-node->variable name)))
	((structure-ref node let-nodes)
	 ((call (global-set! 1 cont (* var) value)))
	 (values (splice!->first first-call last-lambda call)
		 cont))))))

(define (name-node->variable name-node)
  (let ((binding (node-ref name-node 'binding)))
    (if (and binding
             (variable? (binding-place binding)))
        (binding-place binding)
	(bug "name node ~S has no variable" name-node))))

;----------------------------------------------------------------
; CALL & GOTO

(define-cps-converter 'call
  (lambda (node)
    (let ((exp (node-form node)))
      (convert-call (car exp) (cdr exp) node))))

; Treat non-tail-recursive GOTO's as normal calls.

(define-cps-converter 'goto
  (lambda (node)
    (let ((exp (node-form node)))
      (user-warning "Ignoring non-tail-recursive GOTO: ~S" (schemify node))
      (convert-call (cadr exp) (cddr exp) node))))

; Dispatch on the procedure.  Do something special with lambdas, primitives,
; primops (in literal nodes).  Everything else is turned into an unknown call.
; Calls to primitives are expanded and then CPS'ed.

(define (convert-call proc args node)
  (cond ((lambda-node? proc)
	 (convert-let (node-form proc) args node))
	((primitive-node? proc)
	 (values-cps (expand-primitive-call proc args node)))
	((and (literal-node? proc)
	      (primop? (node-form proc)))
	 (convert-primop-call (node-form proc) args (node-type node)))
	(else
	 (convert-primop-call (get-primop (enum primop unknown-call))
			      (cons proc    ; add protocol argument
				    (cons (make-literal normal-protocol)
					  args))
			      (node-type node)))))

; Same again, except that for unknown tail-recursive calls we use different
; protocols for CALL and GOTO.

(define-tail-cps-converter 'call
  (lambda (node cont-var)
    (if (join? cont-var)
	(convert-and-add-jump node cont-var)
	(let ((exp (node-form node)))
	  (tail-convert-call (car exp) (cdr exp) node cont-var normal-protocol)))))

(define-tail-cps-converter 'goto
  (lambda (node cont-var)
    (if (join? cont-var)
	(convert-and-add-jump node cont-var)
	(let ((exp (node-form node)))
	  (tail-convert-call (cadr exp) (cddr exp) node cont-var goto-protocol)))))

(define (convert-and-add-jump node join-var)
  (receive (values first-call last-lambda)
      (values-cps node)
    (let ((jump (make-multiple-value-return join-var values)))
      (cond (first-call
	     (attach-body last-lambda jump)
	     first-call)
	    (else
	     jump)))))

(define (tail-convert-call proc args node cont-var protocol)
  (cond ((lambda-node? proc)
	 (convert-tail-let (node-form proc) args node cont-var))
	((primitive-node? proc)
	 (tail-cps (expand-primitive-call proc args node)
		   cont-var))
	((and (literal-node? proc)
	      (primop? (node-form proc)))
	 (convert-primop-tail-call (node-form proc) args cont-var))
	(else
	 (convert-unknown-tail-call (cons proc args) cont-var protocol))))

; Every primitive has its own expander.

(define (expand-primitive-call proc args node)
  ((primitive-expander (node-form proc)) args (node-type node)))

(define lambda-node? (node-predicate 'lambda))
(define primitive-node? (node-predicate 'primitive))
(define literal-node? (node-predicate 'literal))

(define literal-op (get-operator 'literal))

(define (make-literal value)
  (make-node literal-op value))

;----------------------------------------------------------------
; LET (= a call whose procedure is a LAMBDA)

; REALLY-CONVERT-LET does all the work.  These convert the body of the LET
; using either CPS or TAIL-CPS and connect everything up.

(define (convert-let proc args node)
  (receive (lnode first-call)
      (really-convert-let proc args node)
    (receive (vals body-first-call body-last-lambda)
	(values-cps (caddr proc))
      (values vals
	      first-call
	      (splice!->last lnode body-first-call body-last-lambda)))))
	
(define (convert-tail-let proc args node cont-var)
  (receive (lnode first-call)
      (really-convert-let proc args node)
    (attach-body lnode (tail-cps (caddr proc) cont-var))
    first-call))

;  Make the call to the LET primop and build the lambda node for the procedure.

(define (really-convert-let proc args node)
  (receive (call first-call last-lambda)
      (cps-call (get-primop (enum primop let)) 1 1 args cps)
    (let ((vars (map (lambda (name)
		       (let ((var (make-variable (name-node->symbol name)
						 (node-type name))))
			 (node-set! name 'variable var)
			 var))
		     (cadr proc))))
      (do ((names (cadr proc) (cdr names))
	   (index 1 (+ index 1)))
	  ((null? names))
	(maybe-add-argument-name! call index (node-form (car names))))
      (let ((lnode (make-lambda-node #f 'cont vars)))
	(attach call 0 lnode)
	(values lnode (splice!->first first-call last-lambda call))))))

; Primitive calls
; Use CPS-CALL to do the work and then make a continuation if the primop is
; not trivial.

(define (convert-primop-call primop args type)
  (let ((trivial? (primop-trivial? primop)))
    (receive (call first-call last-lambda)
	(cps-call primop (if trivial? 0 1) (if trivial? 0 1) args cps)
      (if (not trivial?)
	  (add-continuation call first-call last-lambda type)
	  (values call first-call last-lambda)))))

(define (add-continuation call first-call last-lambda type)
  (let* ((vars (map (lambda (type)
		      (make-variable 'v type))
		    (if (tuple-type? type)
			(tuple-type-types type)
			(list type))))
	 (cont (make-lambda-node 'c 'cont vars)))
    (attach call 0 cont)
    (values (if (tuple-type? type)
		(map make-reference-node vars)
		(make-reference-node (car vars)))
	    (splice!->first first-call last-lambda call)
	    cont)))

; Call CONVERT-PRIMOP-CALL and then make a return.

(define (convert-primop-tail-call primop args cont-var)
  (receive (value first-call last-lambda)
      (convert-primop-call primop args (variable-type cont-var))
    (splice!->first first-call
		    last-lambda
		    (if (list? value)
			(make-multiple-value-return cont-var value)
			(make-return cont-var value)))))

; Another front for CPS-CALL, passing it the UNKNOWN-TAIL-CALL primop and
; its arguments, which are the procedure being called, the protocol, and
; the actual arguments.

(define (convert-unknown-tail-call args cont-var protocol)
  (receive (call first-call last-lambda)
      (cps-call (get-primop (enum primop unknown-tail-call)) 0 1
		(cons (car args)
		      (cons (make-literal protocol) (cdr args)))
		cps)
    (attach call 0 (make-reference-node cont-var))
    (splice!->first first-call last-lambda call)))

;----------------------------------------------------------------
; BEGIN
; These are fronts for CPS-SEQUENCE.

(define-cps-converter 'begin
  (lambda (node)
    (receive (last-node real-first-call last-lambda)
	(cps-sequence (cdr (node-form node)) values-cps)
      (if (not real-first-call)
	  (cps last-node)
	  (receive (vals first-call real-last-lambda)
	      (values-cps last-node)
	    (values vals
		    real-first-call
		    (splice!->last last-lambda first-call real-last-lambda)))))))

(define-tail-cps-converter 'begin
  (lambda (node cont-var)
    (receive (last-node first-call last-lambda)
	(cps-sequence (cdr (node-form node)) values-cps)
      (splice!->first first-call last-lambda (tail-cps last-node cont-var)))))

;----------------------------------------------------------------
;
; (IF <a> <b> <c>)
;  =>
; (LET ((J (LAMBDA (V) [rest-goes-here])))
;   (TEST (LAMBDA () [tail-cps <b> J])
;         (LAMBDA () [tail-cps <c> J])
;         <a>))

(define-cps-converter 'if
  (lambda (node)
    (let ((exp (node-form node))
	  (join-var (make-variable 'j type/unknown))
	  (res-vars (make-variables (node-type node))))
      (receive (call first-call last-lambda)
	  (convert-if exp join-var)
	(let ((let-lambda (make-lambda-node 'c 'cont (list join-var)))
	      (let-call (make-call-node (get-primop (enum primop let)) 2 1))
	      (join-lambda (make-lambda-node 'j 'jump res-vars)))
	  (attach let-call 0 let-lambda)
	  (attach let-call 1 join-lambda)
	  (attach-body let-lambda call)
	  (values (map make-reference-node res-vars)
		  (splice!->first first-call last-lambda let-call )
		  join-lambda))))))

(define (make-variables type)
  (map (lambda (type)
	 (make-variable 'v type))
       (if (tuple-type? type)
	   (tuple-type-types type)
	   (list type))))

; Tail-recursive IFs do not require a join point.

(define-tail-cps-converter 'if
  (lambda (node cont-var)
    (let ((exp (node-form node)))
      (receive (call first-call last-lambda)
	  (convert-if exp cont-var)
	(splice!->first first-call last-lambda call)))))

; Actually build the two-continuation call to the TEST primop.

(define (convert-if exp cont-var)	
  (receive (call first-call last-lambda)
      (cps-call (get-prescheme-primop 'test) 2 2 (list (cadr exp)) cps)
    (let ((true-cont (make-lambda-node 'c 'cont '()))
	  (true-call (tail-cps (caddr exp) cont-var))
	  (false-cont (make-lambda-node 'c 'cont '()))
	  (false-call (tail-cps (cadddr exp) cont-var)))
      (attach-body true-cont true-call)
      (attach-body false-cont false-call)
      (attach call 0 true-cont)
      (attach call 1 false-cont)
      (values call first-call last-lambda))))

;----------------------------------------------------------------

(define-cps-converter 'values
  (lambda (node)
    (let ((args (cdr (node-form node))))
      (receive (call first-call last-lambda)
	  (cps-call (get-prescheme-primop 'unspecific) 0 0 args cps)
	(let ((vals (vector->list (call-args call))))
	  (map detach vals)
	  (values vals first-call last-lambda))))))
	  
(define-tail-cps-converter 'values
  (lambda (node cont-var)
    (let ((args (cdr (node-form node))))
      (receive (call first-call last-lambda)
	  (cps-call (get-primop (enum primop unknown-return)) 0 1 args cps)
	(attach call 0 (make-reference-node cont-var))
	(splice!->first first-call last-lambda call)))))
	  
(define-cps-converter 'call-with-values
  (lambda (node)
    (convert-call-with-values node #f)))

(define-tail-cps-converter 'call-with-values
  (lambda (node cont-var)
    (convert-call-with-values node cont-var)))

; Consumer is known to be a lambda node.

(define (convert-call-with-values node maybe-cont-var)
  (receive (vals first-call last-lambda)
      (values-cps (cadr (node-form node)))
    (let ((consumer (x->cps (caddr (node-form node)) #f))
	  (call (make-call-node (get-primop (if maybe-cont-var
						(enum primop tail-call)
						(enum primop call)))
				(+ 2 (length vals))
				(if maybe-cont-var 0 1))))
      (attach-call-args call `(#f ,consumer . ,vals))
      (cond (maybe-cont-var
	     (attach call 0 (make-reference-node maybe-cont-var))
	     (splice!->first first-call last-lambda call))
	    (else
	     (add-continuation call first-call last-lambda (node-type node)))))))

;----------------------------------------------------------------
; LETRECs have been analyzed and restructured by FLATTEN, so we know that
; the values are all lambdas.

(define-cps-converter 'letrec
  (lambda (node)
    (let ((form (node-form node)))
      (receive (first-call last-lambda)
	  (convert-letrec form)
	(receive (vals body-first-call body-last-lambda)
	    (values-cps (caddr form))
	  (values vals
		  first-call
		  (splice!->last last-lambda
				 body-first-call
				 body-last-lambda)))))))

(define-tail-cps-converter 'letrec
  (lambda (node cont-var)
    (let ((form (node-form node)))
      (receive (first-call last-lambda)
	  (convert-letrec form)
	(attach-body last-lambda (tail-cps (caddr form) cont-var))
	first-call))))

(define (convert-letrec form)
  (let ((vars (map (lambda (l)
		     (let ((var (make-variable (name-node->symbol (car l))
					       (node-type (car l)))))
		       (node-set! (car l) 'variable var)
		       var))
		   (cadr form)))
	(vals (map (lambda (l)
		     (receive (value first-call last-lambda)
			 (cps (cadr l))
		       value))
		   (cadr form)))
	(cont (make-lambda-node 'c 'cont '())))
    ((structure-ref node let-nodes)
       ((top (letrec1 1 l1))
        (l1 ((x #f) . vars) call2)
        (call2 (letrec2 1 cont (* x) . vals)))
      (do ((names (cadr form) (cdr names))
	   (index 2 (+ index 1)))
	  ((null? names))
	(maybe-add-argument-name! call2 index (node-form (caar names))))
      (values top cont))))

;----------------------------------------------------------------
; Utilities.

; Stuff is a list of alternating call and lambda nodes, with possible #Fs.
; This joins the nodes together by making the calls be the bodies of the
; lambdas (the call->lambda links are already done).  The last node is
; returned.

(define (splice! stuff)
  (let loop ((stuff stuff) (first #f) (last #f))
    (if (null? stuff)
	(values first last)
	(receive (first last)
	    (let ((next (car stuff)))
	      (cond ((not next)
		     (values first last))
		    ((not first)
		     (values next next))
		    (else
		     (if (and ((structure-ref node lambda-node?) last)
			      ((structure-ref node call-node?) next))
			 (attach-body last next))
		     (values first next))))
	  (loop (cdr stuff) first last)))))

(define (splice!->first . stuff)
  (receive (first last)
      (splice! stuff)
    first))

(define (splice!->last . stuff)
  (receive (first last)
      (splice! stuff)
    last))

; Stuff for making CPS nodes
(define make-reference-node (structure-ref node make-reference-node))
(define make-lambda-node    (structure-ref node make-lambda-node))
(define make-literal-node   (structure-ref node make-literal-node))
(define make-call-node      (structure-ref node make-call-node))
(define attach              (structure-ref node attach))
(define detach              (structure-ref node detach))
(define attach-body         (structure-ref node attach-body))
(define attach-call-args    (structure-ref node attach-call-args))
(define call-args           (structure-ref node call-args))

; Adding names to lambda nodes for debugging help.

(define (maybe-add-argument-name! call index name)
  (maybe-add-name! ((structure-ref node call-arg) call index) name))

(define (maybe-add-name! value name)
  (if ((structure-ref node lambda-node?) value)
      ((structure-ref node set-lambda-name!) value (schemify name))))

; Getting symbols for use as variable names.

(define (name-node->symbol node)
  (let loop ((name (node-form node)))
    (if (generated? name)
	(loop (generated-name name))
	name)))