325 lines
9.7 KiB
Scheme
325 lines
9.7 KiB
Scheme
|
; Copyright (c) 1994 by Richard Kelsey. See file COPYING.
|
||
|
|
||
|
; Substituting new variables for old in expressions.
|
||
|
|
||
|
(define *free-exp-vars* #f)
|
||
|
|
||
|
(define (substitute-in-expression exp)
|
||
|
(set! *free-exp-vars* '())
|
||
|
(set! *letrec-datas* '())
|
||
|
(let* ((exp (substitute-in-exp exp))
|
||
|
(free *free-exp-vars*))
|
||
|
(set! *free-exp-vars* '())
|
||
|
(for-each (lambda (var)
|
||
|
(set-variable-flag! var #f))
|
||
|
free)
|
||
|
(values exp free)))
|
||
|
|
||
|
(define global-marker (list 'global))
|
||
|
|
||
|
(define (note-binding-use! binding)
|
||
|
(let ((var (binding-place binding)))
|
||
|
(if (variable? var)
|
||
|
(note-variable-use! var))))
|
||
|
|
||
|
(define (note-variable-use! var)
|
||
|
(cond ((not (eq? (variable-flag var) global-marker))
|
||
|
(set! *free-exp-vars* (cons var *free-exp-vars*))
|
||
|
(set-variable-flag! var global-marker))))
|
||
|
|
||
|
; Main dispatch
|
||
|
|
||
|
(define (substitute-in-exp node)
|
||
|
((operator-table-ref substitutions (node-operator-id node))
|
||
|
node))
|
||
|
|
||
|
; Particular operators
|
||
|
|
||
|
(define substitutions
|
||
|
(make-operator-table
|
||
|
(lambda (node)
|
||
|
(error "no substitution for node ~S" node))))
|
||
|
|
||
|
(define (default-substitution node)
|
||
|
(make-similar-node node
|
||
|
(cons (car (node-form node))
|
||
|
(map substitute-in-exp (cdr (node-form node))))))
|
||
|
|
||
|
(define (define-substitution name proc)
|
||
|
(operator-define! substitutions name #f proc))
|
||
|
|
||
|
(define-substitution 'literal identity)
|
||
|
(define-substitution 'quote identity)
|
||
|
(define-substitution 'unspecific identity)
|
||
|
|
||
|
(define-substitution 'real-external
|
||
|
(lambda (node)
|
||
|
(let* ((exp (node-form node))
|
||
|
(type (expand-type-spec (cadr (node-form (caddr exp))))))
|
||
|
(make-literal-node (make-external-value (node-form (cadr exp))
|
||
|
type)))))
|
||
|
|
||
|
(define op/literal (get-operator 'literal))
|
||
|
|
||
|
(define (make-literal-node x)
|
||
|
(make-node op/literal x))
|
||
|
|
||
|
; We copy the names because the same node may occur in multiple places
|
||
|
; in the tree.
|
||
|
|
||
|
(define-substitution 'lambda
|
||
|
(lambda (node)
|
||
|
(let* ((new-names (copy-names (cadr (node-form node))))
|
||
|
(body (substitute-in-exp (caddr (node-form node)))))
|
||
|
(make-similar-node node
|
||
|
(list (car (node-form node))
|
||
|
new-names
|
||
|
body)))))
|
||
|
|
||
|
(define (copy-names names)
|
||
|
(map (lambda (name)
|
||
|
(let ((new (make-similar-node name (node-form name))))
|
||
|
(node-set! name 'substitute new)
|
||
|
new))
|
||
|
names))
|
||
|
|
||
|
(define-substitution 'name
|
||
|
(lambda (node)
|
||
|
(substitute-name-node node #f)))
|
||
|
|
||
|
(define (substitute-name-node node call?)
|
||
|
(let ((node (name-node-substitute node)))
|
||
|
(let ((binding (node-ref node 'binding)))
|
||
|
(cond ((not binding)
|
||
|
(note-name-use! node)
|
||
|
node)
|
||
|
((primitive? (binding-static binding))
|
||
|
(make-primitive-node (binding-static binding) call?))
|
||
|
((location? (binding-place binding))
|
||
|
(let ((value (contents (binding-place binding))))
|
||
|
(if (constant? value)
|
||
|
(make-literal-node value)
|
||
|
(identity
|
||
|
(bug "name ~S has non-constant location ~S" node value)))))
|
||
|
(else
|
||
|
(note-binding-use! binding)
|
||
|
node)))))
|
||
|
|
||
|
(define (name-node-substitute node)
|
||
|
(let loop ((node node) (first? #t))
|
||
|
(cond ((node-ref node 'substitute)
|
||
|
=> (lambda (node)
|
||
|
(loop node #f)))
|
||
|
((and first? (not (node-ref node 'binding)))
|
||
|
(user-error "unbound variable ~S" (node-form node)))
|
||
|
(else
|
||
|
node))))
|
||
|
|
||
|
(define-substitution 'set!
|
||
|
(lambda (node)
|
||
|
(let* ((exp (node-form node))
|
||
|
(name (substitute-name-node (cadr exp) #f))
|
||
|
(binding (node-ref name 'binding)))
|
||
|
(if (not (binding? binding))
|
||
|
(user-error "SET! on local variable ~S" (node-form (cadr exp))))
|
||
|
((structure-ref forms note-variable-set!!)
|
||
|
(binding-place binding))
|
||
|
(note-binding-use! binding)
|
||
|
(make-similar-node node
|
||
|
(list (car exp)
|
||
|
name
|
||
|
(substitute-in-exp (caddr exp)))))))
|
||
|
|
||
|
(define-substitution 'call
|
||
|
(lambda (node)
|
||
|
(let ((proc (car (node-form node)))
|
||
|
(args (cdr (node-form node))))
|
||
|
(make-similar-node node
|
||
|
(cons (if (name-node? proc)
|
||
|
(substitute-name-node proc #t)
|
||
|
(substitute-in-exp proc))
|
||
|
(map substitute-in-exp args))))))
|
||
|
|
||
|
; Flush GOTO when it is used with a primitive.
|
||
|
|
||
|
(define-substitution 'goto
|
||
|
(lambda (node)
|
||
|
(let ((proc (cadr (node-form node)))
|
||
|
(args (cddr (node-form node))))
|
||
|
(if (and (name-node? proc)
|
||
|
(bound-to-primitive? proc))
|
||
|
(make-node (get-operator 'call)
|
||
|
(cons (substitute-name-node proc #t)
|
||
|
(map substitute-in-exp args)))
|
||
|
(make-similar-node node
|
||
|
(cons 'goto
|
||
|
(cons (if (name-node? proc)
|
||
|
(substitute-name-node proc #t)
|
||
|
(substitute-in-exp proc))
|
||
|
(map substitute-in-exp args))))))))
|
||
|
|
||
|
(define name-node? (node-predicate 'name))
|
||
|
|
||
|
(define (bound-to-primitive? node)
|
||
|
(let ((node (name-node-substitute node)))
|
||
|
(let ((binding (node-ref node 'binding)))
|
||
|
(and binding
|
||
|
(primitive? (binding-static binding))))))
|
||
|
|
||
|
(define-substitution 'begin default-substitution)
|
||
|
(define-substitution 'if default-substitution)
|
||
|
|
||
|
; drop the loophole part
|
||
|
|
||
|
(define-substitution 'loophole
|
||
|
(lambda (node)
|
||
|
(substitute-in-exp (caddr (node-form node)))))
|
||
|
|
||
|
;----------------------------------------------------------------
|
||
|
; Breaking LETREC's down to improve type inference and make compilation
|
||
|
; easier.
|
||
|
|
||
|
(define-substitution 'letrec
|
||
|
(lambda (node)
|
||
|
(let* ((exp (node-form node))
|
||
|
(vars (map car (cadr exp)))
|
||
|
(vals (map cadr (cadr exp))))
|
||
|
(receive (names datas)
|
||
|
(copy-letrec-names vars vals exp)
|
||
|
(for-each (lambda (data value)
|
||
|
(expand-letrec-value data value datas exp))
|
||
|
datas
|
||
|
vals)
|
||
|
(let ((sets (strongly-connected-components datas
|
||
|
letrec-data-uses
|
||
|
letrec-data-seen?
|
||
|
set-letrec-data-seen?!)))
|
||
|
;; so we don't keep track of which vars are referenced in the body
|
||
|
(for-each (lambda (d)
|
||
|
(set-letrec-data-seen?! d #t))
|
||
|
datas)
|
||
|
(do ((sets sets (cdr sets))
|
||
|
(exp (substitute-in-exp (caddr exp))
|
||
|
(build-letrec (car sets) exp)))
|
||
|
((null? sets)
|
||
|
(for-each (lambda (n)
|
||
|
(node-set! n 'letrec-data #f))
|
||
|
names)
|
||
|
exp)))))))
|
||
|
|
||
|
(define-record-type letrec-data
|
||
|
(name ; the name node for which this data exists
|
||
|
marker ; a unique marker for this LETREC
|
||
|
cell? ; variable is SET! or its value is not a (lambda ...). This is
|
||
|
; always #F until I can think of a reason to allow otherwise.
|
||
|
)
|
||
|
(value ; the expanded value of this variable
|
||
|
uses ; a list of variables that VALUE uses
|
||
|
seen? ; #T if this has been seen before during the current expansion
|
||
|
))
|
||
|
|
||
|
(define (copy-letrec-names names vals marker)
|
||
|
(let ((names (map (lambda (name value)
|
||
|
(let ((new (make-similar-node name (node-form name)))
|
||
|
(cell? #f)) ; we no longer allow SET! on LETREC vars.
|
||
|
(node-set! new 'letrec-data
|
||
|
(letrec-data-maker new marker cell?))
|
||
|
(node-set! name 'substitute new)
|
||
|
new))
|
||
|
names
|
||
|
vals)))
|
||
|
(values names (map (lambda (name) (node-ref name 'letrec-data)) names))))
|
||
|
|
||
|
(define lambda-node? (node-predicate 'lambda))
|
||
|
|
||
|
; List of LETREC bound variables currently in scope.
|
||
|
|
||
|
(define *letrec-datas* '())
|
||
|
|
||
|
(define (note-name-use! name)
|
||
|
(let ((data (node-ref name 'letrec-data)))
|
||
|
(cond ((and data (not (letrec-data-seen? data)))
|
||
|
(set-letrec-data-seen?! data #t)
|
||
|
(set! *letrec-datas* (cons data *letrec-datas*))))))
|
||
|
|
||
|
; Expand VALUE and determine which of DATAS it uses.
|
||
|
|
||
|
(define (expand-letrec-value data value datas mark)
|
||
|
(let ((old-letrec-vars *letrec-datas*))
|
||
|
(set! *letrec-datas* '())
|
||
|
(for-each (lambda (d) (set-letrec-data-seen?! d #f)) datas)
|
||
|
(set-letrec-data-value! data (substitute-in-exp value))
|
||
|
(receive (ours others)
|
||
|
(partition-list (lambda (data)
|
||
|
(eq? (letrec-data-marker data) mark))
|
||
|
*letrec-datas*)
|
||
|
(set! *letrec-datas* (append others old-letrec-vars))
|
||
|
(set-letrec-data-uses! data ours))))
|
||
|
|
||
|
; If there is only one variable and its value doesn't reference it, then
|
||
|
; use a LET instead of a LETREC. Variables whose value is either set! or
|
||
|
; not a lambda have explicit cells introduced.
|
||
|
|
||
|
(define (build-letrec datas body)
|
||
|
(if (and (null? (cdr datas))
|
||
|
(not (memq? (car datas)
|
||
|
(letrec-data-uses (car datas)))))
|
||
|
(make-let-node (map letrec-data-name datas)
|
||
|
(map letrec-data-value datas)
|
||
|
body)
|
||
|
(receive (cells normal)
|
||
|
(partition-list letrec-data-cell? datas)
|
||
|
(make-let-node (map letrec-data-name cells)
|
||
|
(map (lambda (ignore) (unspecific-node))
|
||
|
cells)
|
||
|
(make-letrec-node (map letrec-data-name normal)
|
||
|
(map letrec-data-value normal)
|
||
|
(make-begin-node
|
||
|
(append (map letrec-data->set! cells)
|
||
|
(list body))))))))
|
||
|
|
||
|
(define op/unspecific (get-operator 'unspecific))
|
||
|
(define op/set! (get-operator 'set!))
|
||
|
|
||
|
(define (unspecific-node)
|
||
|
(make-node op/unspecific '()))
|
||
|
|
||
|
(define (letrec-data->set! data)
|
||
|
(make-node op/set!
|
||
|
(list 'set!
|
||
|
(letrec-data-name data)
|
||
|
(letrec-data-value data))))
|
||
|
|
||
|
(define (make-let-node names values body)
|
||
|
(if (null? names)
|
||
|
body
|
||
|
(make-node op/call
|
||
|
(cons (make-node op/lambda
|
||
|
(list 'lambda names body))
|
||
|
values))))
|
||
|
|
||
|
(define (make-letrec-node names values body)
|
||
|
(if (null? names)
|
||
|
body
|
||
|
(make-node op/letrec
|
||
|
(list 'letrec
|
||
|
(map list names values)
|
||
|
body))))
|
||
|
|
||
|
(define (make-begin-node nodes)
|
||
|
(if (null? (cdr nodes))
|
||
|
(car nodes)
|
||
|
(make-node op/begin (cons 'begin nodes))))
|
||
|
|
||
|
(define op/call (get-operator 'call))
|
||
|
(define op/lambda (get-operator 'lambda))
|
||
|
(define op/letrec (get-operator 'letrec))
|
||
|
(define op/begin (get-operator 'begin))
|
||
|
|
||
|
;----------------------------------------------------------------
|
||
|
; A version of MAKE-SIMILAR-NODE that actually makes a new node.
|
||
|
; I wish this could keep the old node's list of properties.
|
||
|
|
||
|
(define (make-similar-node node form)
|
||
|
(make-node (node-operator node) form))
|