Add `-Wunused-toplevel' compiler warning.
[bpt/guile.git] / module / language / tree-il / analyze.scm
1 ;;; TREE-IL -> GLIL compiler
2
3 ;; Copyright (C) 2001,2008,2009,2010 Free Software Foundation, Inc.
4
5 ;;;; This library is free software; you can redistribute it and/or
6 ;;;; modify it under the terms of the GNU Lesser General Public
7 ;;;; License as published by the Free Software Foundation; either
8 ;;;; version 3 of the License, or (at your option) any later version.
9 ;;;;
10 ;;;; This library is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 ;;;; Lesser General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU Lesser General Public
16 ;;;; License along with this library; if not, write to the Free Software
17 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
19 ;;; Code:
20
21 (define-module (language tree-il analyze)
22 #:use-module (srfi srfi-1)
23 #:use-module (srfi srfi-9)
24 #:use-module (srfi srfi-11)
25 #:use-module (system base syntax)
26 #:use-module (system base message)
27 #:use-module (system vm program)
28 #:use-module (language tree-il)
29 #:use-module (system base pmatch)
30 #:export (analyze-lexicals
31 analyze-tree
32 unused-variable-analysis
33 unused-toplevel-analysis
34 unbound-variable-analysis
35 arity-analysis))
36
37 ;; Allocation is the process of assigning storage locations for lexical
38 ;; variables. A lexical variable has a distinct "address", or storage
39 ;; location, for each procedure in which it is referenced.
40 ;;
41 ;; A variable is "local", i.e., allocated on the stack, if it is
42 ;; referenced from within the procedure that defined it. Otherwise it is
43 ;; a "closure" variable. For example:
44 ;;
45 ;; (lambda (a) a) ; a will be local
46 ;; `a' is local to the procedure.
47 ;;
48 ;; (lambda (a) (lambda () a))
49 ;; `a' is local to the outer procedure, but a closure variable with
50 ;; respect to the inner procedure.
51 ;;
52 ;; If a variable is ever assigned, it needs to be heap-allocated
53 ;; ("boxed"). This is so that closures and continuations capture the
54 ;; variable's identity, not just one of the values it may have over the
55 ;; course of program execution. If the variable is never assigned, there
56 ;; is no distinction between value and identity, so closing over its
57 ;; identity (whether through closures or continuations) can make a copy
58 ;; of its value instead.
59 ;;
60 ;; Local variables are stored on the stack within a procedure's call
61 ;; frame. Their index into the stack is determined from their linear
62 ;; postion within a procedure's binding path:
63 ;; (let (0 1)
64 ;; (let (2 3) ...)
65 ;; (let (2) ...))
66 ;; (let (2 3 4) ...))
67 ;; etc.
68 ;;
69 ;; This algorithm has the problem that variables are only allocated
70 ;; indices at the end of the binding path. If variables bound early in
71 ;; the path are not used in later portions of the path, their indices
72 ;; will not be recycled. This problem is particularly egregious in the
73 ;; expansion of `or':
74 ;;
75 ;; (or x y z)
76 ;; -> (let ((a x)) (if a a (let ((b y)) (if b b z))))
77 ;;
78 ;; As you can see, the `a' binding is only used in the ephemeral
79 ;; `consequent' clause of the first `if', but its index would be
80 ;; reserved for the whole of the `or' expansion. So we have a hack for
81 ;; this specific case. A proper solution would be some sort of liveness
82 ;; analysis, and not our linear allocation algorithm.
83 ;;
84 ;; Closure variables are captured when a closure is created, and stored
85 ;; in a vector. Each closure variable has a unique index into that
86 ;; vector.
87 ;;
88 ;; There is one more complication. Procedures bound by <fix> may, in
89 ;; some cases, be rendered inline to their parent procedure. That is to
90 ;; say,
91 ;;
92 ;; (letrec ((lp (lambda () (lp)))) (lp))
93 ;; => (fix ((lp (lambda () (lp)))) (lp))
94 ;; => goto FIX-BODY; LP: goto LP; FIX-BODY: goto LP;
95 ;; ^ jump over the loop ^ the fixpoint lp ^ starting off the loop
96 ;;
97 ;; The upshot is that we don't have to allocate any space for the `lp'
98 ;; closure at all, as it can be rendered inline as a loop. So there is
99 ;; another kind of allocation, "label allocation", in which the
100 ;; procedure is simply a label, placed at the start of the lambda body.
101 ;; The label is the gensym under which the lambda expression is bound.
102 ;;
103 ;; The analyzer checks to see that the label is called with the correct
104 ;; number of arguments. Calls to labels compile to rename + goto.
105 ;; Lambda, the ultimate goto!
106 ;;
107 ;;
108 ;; The return value of `analyze-lexicals' is a hash table, the
109 ;; "allocation".
110 ;;
111 ;; The allocation maps gensyms -- recall that each lexically bound
112 ;; variable has a unique gensym -- to storage locations ("addresses").
113 ;; Since one gensym may have many storage locations, if it is referenced
114 ;; in many procedures, it is a two-level map.
115 ;;
116 ;; The allocation also stored information on how many local variables
117 ;; need to be allocated for each procedure, lexicals that have been
118 ;; translated into labels, and information on what free variables to
119 ;; capture from its lexical parent procedure.
120 ;;
121 ;; In addition, we have a conflation: while we're traversing the code,
122 ;; recording information to pass to the compiler, we take the
123 ;; opportunity to generate labels for each lambda-case clause, so that
124 ;; generated code can skip argument checks at runtime if they match at
125 ;; compile-time.
126 ;;
127 ;; That is:
128 ;;
129 ;; sym -> {lambda -> address}
130 ;; lambda -> (labels . free-locs)
131 ;; lambda-case -> (gensym . nlocs)
132 ;;
133 ;; address ::= (local? boxed? . index)
134 ;; labels ::= ((sym . lambda) ...)
135 ;; free-locs ::= ((sym0 . address0) (sym1 . address1) ...)
136 ;; free variable addresses are relative to parent proc.
137
138 (define (make-hashq k v)
139 (let ((res (make-hash-table)))
140 (hashq-set! res k v)
141 res))
142
143 (define (analyze-lexicals x)
144 ;; bound-vars: lambda -> (sym ...)
145 ;; all identifiers bound within a lambda
146 (define bound-vars (make-hash-table))
147 ;; free-vars: lambda -> (sym ...)
148 ;; all identifiers referenced in a lambda, but not bound
149 ;; NB, this includes identifiers referenced by contained lambdas
150 (define free-vars (make-hash-table))
151 ;; assigned: sym -> #t
152 ;; variables that are assigned
153 (define assigned (make-hash-table))
154 ;; refcounts: sym -> count
155 ;; allows us to detect the or-expansion in O(1) time
156 (define refcounts (make-hash-table))
157 ;; labels: sym -> lambda
158 ;; for determining if fixed-point procedures can be rendered as
159 ;; labels.
160 (define labels (make-hash-table))
161
162 ;; returns variables referenced in expr
163 (define (analyze! x proc labels-in-proc tail? tail-call-args)
164 (define (step y) (analyze! y proc labels-in-proc #f #f))
165 (define (step-tail y) (analyze! y proc labels-in-proc tail? #f))
166 (define (step-tail-call y args) (analyze! y proc labels-in-proc #f
167 (and tail? args)))
168 (define (recur/labels x new-proc labels)
169 (analyze! x new-proc (append labels labels-in-proc) #t #f))
170 (define (recur x new-proc) (analyze! x new-proc '() tail? #f))
171 (record-case x
172 ((<application> proc args)
173 (apply lset-union eq? (step-tail-call proc args)
174 (map step args)))
175
176 ((<conditional> test consequent alternate)
177 (lset-union eq? (step test) (step-tail consequent) (step-tail alternate)))
178
179 ((<lexical-ref> gensym)
180 (hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0)))
181 (if (not (and tail-call-args
182 (memq gensym labels-in-proc)
183 (let ((p (hashq-ref labels gensym)))
184 (and p
185 (let lp ((c (lambda-body p)))
186 (and c (lambda-case? c)
187 (or
188 ;; for now prohibit optional &
189 ;; keyword arguments; can relax this
190 ;; restriction later
191 (and (= (length (lambda-case-req c))
192 (length tail-call-args))
193 (not (lambda-case-opt c))
194 (not (lambda-case-kw c))
195 (not (lambda-case-rest c)))
196 (lp (lambda-case-alternate c)))))))))
197 (hashq-set! labels gensym #f))
198 (list gensym))
199
200 ((<lexical-set> gensym exp)
201 (hashq-set! assigned gensym #t)
202 (hashq-set! labels gensym #f)
203 (lset-adjoin eq? (step exp) gensym))
204
205 ((<module-set> exp)
206 (step exp))
207
208 ((<toplevel-set> exp)
209 (step exp))
210
211 ((<toplevel-define> exp)
212 (step exp))
213
214 ((<sequence> exps)
215 (let lp ((exps exps) (ret '()))
216 (cond ((null? exps) '())
217 ((null? (cdr exps))
218 (lset-union eq? ret (step-tail (car exps))))
219 (else
220 (lp (cdr exps) (lset-union eq? ret (step (car exps))))))))
221
222 ((<lambda> body)
223 ;; order is important here
224 (hashq-set! bound-vars x '())
225 (let ((free (recur body x)))
226 (hashq-set! bound-vars x (reverse! (hashq-ref bound-vars x)))
227 (hashq-set! free-vars x free)
228 free))
229
230 ((<lambda-case> opt kw inits vars body alternate)
231 (hashq-set! bound-vars proc
232 (append (reverse vars) (hashq-ref bound-vars proc)))
233 (lset-union
234 eq?
235 (lset-difference eq?
236 (lset-union eq?
237 (apply lset-union eq? (map step inits))
238 (step-tail body))
239 vars)
240 (if alternate (step-tail alternate) '())))
241
242 ((<let> vars vals body)
243 (hashq-set! bound-vars proc
244 (append (reverse vars) (hashq-ref bound-vars proc)))
245 (lset-difference eq?
246 (apply lset-union eq? (step-tail body) (map step vals))
247 vars))
248
249 ((<letrec> vars vals body)
250 (hashq-set! bound-vars proc
251 (append (reverse vars) (hashq-ref bound-vars proc)))
252 (for-each (lambda (sym) (hashq-set! assigned sym #t)) vars)
253 (lset-difference eq?
254 (apply lset-union eq? (step-tail body) (map step vals))
255 vars))
256
257 ((<fix> vars vals body)
258 ;; Try to allocate these procedures as labels.
259 (for-each (lambda (sym val) (hashq-set! labels sym val))
260 vars vals)
261 (hashq-set! bound-vars proc
262 (append (reverse vars) (hashq-ref bound-vars proc)))
263 ;; Step into subexpressions.
264 (let* ((var-refs
265 (map
266 ;; Since we're trying to label-allocate the lambda,
267 ;; pretend it's not a closure, and just recurse into its
268 ;; body directly. (Otherwise, recursing on a closure
269 ;; that references one of the fix's bound vars would
270 ;; prevent label allocation.)
271 (lambda (x)
272 (record-case x
273 ((<lambda> body)
274 ;; just like the closure case, except here we use
275 ;; recur/labels instead of recur
276 (hashq-set! bound-vars x '())
277 (let ((free (recur/labels body x vars)))
278 (hashq-set! bound-vars x (reverse! (hashq-ref bound-vars x)))
279 (hashq-set! free-vars x free)
280 free))))
281 vals))
282 (vars-with-refs (map cons vars var-refs))
283 (body-refs (recur/labels body proc vars)))
284 (define (delabel-dependents! sym)
285 (let ((refs (assq-ref vars-with-refs sym)))
286 (if refs
287 (for-each (lambda (sym)
288 (if (hashq-ref labels sym)
289 (begin
290 (hashq-set! labels sym #f)
291 (delabel-dependents! sym))))
292 refs))))
293 ;; Stepping into the lambdas and the body might have made some
294 ;; procedures not label-allocatable -- which might have
295 ;; knock-on effects. For example:
296 ;; (fix ((a (lambda () (b)))
297 ;; (b (lambda () a)))
298 ;; (a))
299 ;; As far as `a' is concerned, both `a' and `b' are
300 ;; label-allocatable. But `b' references `a' not in a proc-tail
301 ;; position, which makes `a' not label-allocatable. The
302 ;; knock-on effect is that, when back-propagating this
303 ;; information to `a', `b' will also become not
304 ;; label-allocatable, as it is referenced within `a', which is
305 ;; allocated as a closure. This is a transitive relationship.
306 (for-each (lambda (sym)
307 (if (not (hashq-ref labels sym))
308 (delabel-dependents! sym)))
309 vars)
310 ;; Now lift bound variables with label-allocated lambdas to the
311 ;; parent procedure.
312 (for-each
313 (lambda (sym val)
314 (if (hashq-ref labels sym)
315 ;; Remove traces of the label-bound lambda. The free
316 ;; vars will propagate up via the return val.
317 (begin
318 (hashq-set! bound-vars proc
319 (append (hashq-ref bound-vars val)
320 (hashq-ref bound-vars proc)))
321 (hashq-remove! bound-vars val)
322 (hashq-remove! free-vars val))))
323 vars vals)
324 (lset-difference eq?
325 (apply lset-union eq? body-refs var-refs)
326 vars)))
327
328 ((<let-values> exp body)
329 (lset-union eq? (step exp) (step body)))
330
331 (else '())))
332
333 ;; allocation: sym -> {lambda -> address}
334 ;; lambda -> (nlocs labels . free-locs)
335 (define allocation (make-hash-table))
336
337 (define (allocate! x proc n)
338 (define (recur y) (allocate! y proc n))
339 (record-case x
340 ((<application> proc args)
341 (apply max (recur proc) (map recur args)))
342
343 ((<conditional> test consequent alternate)
344 (max (recur test) (recur consequent) (recur alternate)))
345
346 ((<lexical-set> exp)
347 (recur exp))
348
349 ((<module-set> exp)
350 (recur exp))
351
352 ((<toplevel-set> exp)
353 (recur exp))
354
355 ((<toplevel-define> exp)
356 (recur exp))
357
358 ((<sequence> exps)
359 (apply max (map recur exps)))
360
361 ((<lambda> body)
362 ;; allocate closure vars in order
363 (let lp ((c (hashq-ref free-vars x)) (n 0))
364 (if (pair? c)
365 (begin
366 (hashq-set! (hashq-ref allocation (car c))
367 x
368 `(#f ,(hashq-ref assigned (car c)) . ,n))
369 (lp (cdr c) (1+ n)))))
370
371 (let ((nlocs (allocate! body x 0))
372 (free-addresses
373 (map (lambda (v)
374 (hashq-ref (hashq-ref allocation v) proc))
375 (hashq-ref free-vars x)))
376 (labels (filter cdr
377 (map (lambda (sym)
378 (cons sym (hashq-ref labels sym)))
379 (hashq-ref bound-vars x)))))
380 ;; set procedure allocations
381 (hashq-set! allocation x (cons labels free-addresses)))
382 n)
383
384 ((<lambda-case> opt kw inits vars body alternate)
385 (max
386 (let lp ((vars vars) (n n))
387 (if (null? vars)
388 (let ((nlocs (apply
389 max
390 (allocate! body proc n)
391 ;; inits not logically at the end, but they
392 ;; are the list...
393 (map (lambda (x) (allocate! x proc n)) inits))))
394 ;; label and nlocs for the case
395 (hashq-set! allocation x (cons (gensym ":LCASE") nlocs))
396 nlocs)
397 (begin
398 (hashq-set! allocation (car vars)
399 (make-hashq
400 proc `(#t ,(hashq-ref assigned (car vars)) . ,n)))
401 (lp (cdr vars) (1+ n)))))
402 (if alternate (allocate! alternate proc n) n)))
403
404 ((<let> vars vals body)
405 (let ((nmax (apply max (map recur vals))))
406 (cond
407 ;; the `or' hack
408 ((and (conditional? body)
409 (= (length vars) 1)
410 (let ((v (car vars)))
411 (and (not (hashq-ref assigned v))
412 (= (hashq-ref refcounts v 0) 2)
413 (lexical-ref? (conditional-test body))
414 (eq? (lexical-ref-gensym (conditional-test body)) v)
415 (lexical-ref? (conditional-consequent body))
416 (eq? (lexical-ref-gensym (conditional-consequent body)) v))))
417 (hashq-set! allocation (car vars)
418 (make-hashq proc `(#t #f . ,n)))
419 ;; the 1+ for this var
420 (max nmax (1+ n) (allocate! (conditional-alternate body) proc n)))
421 (else
422 (let lp ((vars vars) (n n))
423 (if (null? vars)
424 (max nmax (allocate! body proc n))
425 (let ((v (car vars)))
426 (hashq-set!
427 allocation v
428 (make-hashq proc
429 `(#t ,(hashq-ref assigned v) . ,n)))
430 (lp (cdr vars) (1+ n)))))))))
431
432 ((<letrec> vars vals body)
433 (let lp ((vars vars) (n n))
434 (if (null? vars)
435 (let ((nmax (apply max
436 (map (lambda (x)
437 (allocate! x proc n))
438 vals))))
439 (max nmax (allocate! body proc n)))
440 (let ((v (car vars)))
441 (hashq-set!
442 allocation v
443 (make-hashq proc
444 `(#t ,(hashq-ref assigned v) . ,n)))
445 (lp (cdr vars) (1+ n))))))
446
447 ((<fix> vars vals body)
448 (let lp ((in vars) (n n))
449 (if (null? in)
450 (let lp ((vars vars) (vals vals) (nmax n))
451 (cond
452 ((null? vars)
453 (max nmax (allocate! body proc n)))
454 ((hashq-ref labels (car vars))
455 ;; allocate lambda body inline to proc
456 (lp (cdr vars)
457 (cdr vals)
458 (record-case (car vals)
459 ((<lambda> body)
460 (max nmax (allocate! body proc n))))))
461 (else
462 ;; allocate closure
463 (lp (cdr vars)
464 (cdr vals)
465 (max nmax (allocate! (car vals) proc n))))))
466
467 (let ((v (car in)))
468 (cond
469 ((hashq-ref assigned v)
470 (error "fixpoint procedures may not be assigned" x))
471 ((hashq-ref labels v)
472 ;; no binding, it's a label
473 (lp (cdr in) n))
474 (else
475 ;; allocate closure binding
476 (hashq-set! allocation v (make-hashq proc `(#t #f . ,n)))
477 (lp (cdr in) (1+ n))))))))
478
479 ((<let-values> exp body)
480 (max (recur exp) (recur body)))
481
482 (else n)))
483
484 (analyze! x #f '() #t #f)
485 (allocate! x #f 0)
486
487 allocation)
488
489 \f
490 ;;;
491 ;;; Tree analyses for warnings.
492 ;;;
493
494 (define-record-type <tree-analysis>
495 (make-tree-analysis leaf down up post init)
496 tree-analysis?
497 (leaf tree-analysis-leaf) ;; (lambda (x result env locs) ...)
498 (down tree-analysis-down) ;; (lambda (x result env locs) ...)
499 (up tree-analysis-up) ;; (lambda (x result env locs) ...)
500 (post tree-analysis-post) ;; (lambda (result env) ...)
501 (init tree-analysis-init)) ;; arbitrary value
502
503 (define (analyze-tree analyses tree env)
504 "Run all tree analyses listed in ANALYSES on TREE for ENV, using
505 `tree-il-fold'. Return TREE. The leaf/down/up procedures of each analysis are
506 passed a ``location stack', which is the stack of `tree-il-src' values for each
507 parent tree (a list); it can be used to approximate source location when
508 accurate information is missing from a given `tree-il' element."
509
510 (define (traverse proc update-locs)
511 ;; Return a tree traversing procedure that returns a list of analysis
512 ;; results prepended by the location stack.
513 (lambda (x results)
514 (let ((locs (update-locs x (car results))))
515 (cons locs ;; the location stack
516 (map (lambda (analysis result)
517 ((proc analysis) x result env locs))
518 analyses
519 (cdr results))))))
520
521 ;; Keeping/extending/shrinking the location stack.
522 (define (keep-locs x locs) locs)
523 (define (extend-locs x locs) (cons (tree-il-src x) locs))
524 (define (shrink-locs x locs) (cdr locs))
525
526 (let ((results
527 (tree-il-fold (traverse tree-analysis-leaf keep-locs)
528 (traverse tree-analysis-down extend-locs)
529 (traverse tree-analysis-up shrink-locs)
530 (cons '() ;; empty location stack
531 (map tree-analysis-init analyses))
532 tree)))
533
534 (for-each (lambda (analysis result)
535 ((tree-analysis-post analysis) result env))
536 analyses
537 (cdr results)))
538
539 tree)
540
541 \f
542 ;;;
543 ;;; Unused variable analysis.
544 ;;;
545
546 ;; <binding-info> records are used during tree traversals in
547 ;; `unused-variable-analysis'. They contain a list of the local vars
548 ;; currently in scope, and a list of locals vars that have been referenced.
549 (define-record-type <binding-info>
550 (make-binding-info vars refs)
551 binding-info?
552 (vars binding-info-vars) ;; ((GENSYM NAME LOCATION) ...)
553 (refs binding-info-refs)) ;; (GENSYM ...)
554
555 (define unused-variable-analysis
556 ;; Report unused variables in the given tree.
557 (make-tree-analysis
558 (lambda (x info env locs)
559 ;; X is a leaf: extend INFO's refs accordingly.
560 (let ((refs (binding-info-refs info))
561 (vars (binding-info-vars info)))
562 (record-case x
563 ((<lexical-ref> gensym)
564 (make-binding-info vars (cons gensym refs)))
565 (else info))))
566
567 (lambda (x info env locs)
568 ;; Going down into X: extend INFO's variable list
569 ;; accordingly.
570 (let ((refs (binding-info-refs info))
571 (vars (binding-info-vars info))
572 (src (tree-il-src x)))
573 (define (extend inner-vars inner-names)
574 (append (map (lambda (var name)
575 (list var name src))
576 inner-vars
577 inner-names)
578 vars))
579 (record-case x
580 ((<lexical-set> gensym)
581 (make-binding-info vars (cons gensym refs)))
582 ((<lambda-case> req opt inits rest kw vars)
583 (let ((names `(,@req
584 ,@(or opt '())
585 ,@(if rest (list rest) '())
586 ,@(if kw (map cadr (cdr kw)) '()))))
587 (make-binding-info (extend vars names) refs)))
588 ((<let> vars names)
589 (make-binding-info (extend vars names) refs))
590 ((<letrec> vars names)
591 (make-binding-info (extend vars names) refs))
592 ((<fix> vars names)
593 (make-binding-info (extend vars names) refs))
594 (else info))))
595
596 (lambda (x info env locs)
597 ;; Leaving X's scope: shrink INFO's variable list
598 ;; accordingly and reported unused nested variables.
599 (let ((refs (binding-info-refs info))
600 (vars (binding-info-vars info)))
601 (define (shrink inner-vars refs)
602 (for-each (lambda (var)
603 (let ((gensym (car var)))
604 ;; Don't report lambda parameters as
605 ;; unused.
606 (if (and (not (memq gensym refs))
607 (not (and (lambda-case? x)
608 (memq gensym
609 inner-vars))))
610 (let ((name (cadr var))
611 ;; We can get approximate
612 ;; source location by going up
613 ;; the LOCS location stack.
614 (loc (or (caddr var)
615 (find pair? locs))))
616 (warning 'unused-variable loc name)))))
617 (filter (lambda (var)
618 (memq (car var) inner-vars))
619 vars))
620 (fold alist-delete vars inner-vars))
621
622 ;; For simplicity, we leave REFS untouched, i.e., with
623 ;; names of variables that are now going out of scope.
624 ;; It doesn't hurt as these are unique names, it just
625 ;; makes REFS unnecessarily fat.
626 (record-case x
627 ((<lambda-case> vars)
628 (make-binding-info (shrink vars refs) refs))
629 ((<let> vars)
630 (make-binding-info (shrink vars refs) refs))
631 ((<letrec> vars)
632 (make-binding-info (shrink vars refs) refs))
633 ((<fix> vars)
634 (make-binding-info (shrink vars refs) refs))
635 (else info))))
636
637 (lambda (result env) #t)
638 (make-binding-info '() '())))
639
640 \f
641 ;;;
642 ;;; Unused top-level variable analysis.
643 ;;;
644
645 ;; <reference-dag> record top-level definitions that are made, references to
646 ;; top-level definitions and their context (the top-level definition in which
647 ;; the reference appears), as well as the current context (the top-level
648 ;; definition we're currently in). The second part (`refs' below) is
649 ;; effectively a DAG from which we can determine unused top-level definitions.
650 (define-record-type <reference-dag>
651 (make-reference-dag refs defs toplevel-context)
652 reference-dag?
653 (defs reference-dag-defs) ;; ((NAME . LOC) ...)
654 (refs reference-dag-refs) ;; ((REF-CONTEXT REF ...) ...)
655 (toplevel-context reference-dag-toplevel-context)) ;; NAME | #f
656
657 (define (dag-reachable-nodes root refs)
658 ;; Return the list of nodes reachable from ROOT in DAG REFS. REFS is an alist
659 ;; representing edges: ((A B C) (B A) (C)) corresponds to
660 ;;
661 ;; ,-------.
662 ;; v |
663 ;; A ----> B
664 ;; |
665 ;; v
666 ;; C
667
668 (let loop ((root root)
669 (path '())
670 (result '()))
671 (if (or (memq root path)
672 (memq root result))
673 result
674 (let ((children (assoc-ref refs root)))
675 (if (not children)
676 result
677 (let ((path (cons root path)))
678 (append children
679 (fold (lambda (child result)
680 (loop child path result))
681 result
682 children))))))))
683
684 (define (dag-reachable-nodes* roots refs)
685 ;; Return the list of nodes in REFS reachable from the nodes listed in ROOTS.
686 ;; FIXME: Choose a more efficient algorithm.
687 (apply lset-union eq?
688 (map (lambda (node)
689 (cons node (dag-reachable-nodes node refs)))
690 roots)))
691
692 (define unused-toplevel-analysis
693 ;; Report unused top-level definitions that are not exported.
694 (let ((add-ref-from-context
695 (lambda (dag name)
696 ;; Add an edge CTX -> NAME in DAG.
697 (let* ((refs (reference-dag-refs dag))
698 (defs (reference-dag-defs dag))
699 (ctx (reference-dag-toplevel-context dag))
700 (ctx-refs (or (assoc-ref refs ctx) '())))
701 (make-reference-dag (alist-cons ctx (cons name ctx-refs)
702 (alist-delete ctx refs eq?))
703 defs ctx)))))
704 (define (macro-variable? name env)
705 (and (module? env)
706 (let ((var (module-variable env name)))
707 (and var (variable-bound? var)
708 (macro? (variable-ref var))))))
709
710 (make-tree-analysis
711 (lambda (x dag env locs)
712 ;; X is a leaf.
713 (let ((ctx (reference-dag-toplevel-context dag)))
714 (record-case x
715 ((<toplevel-ref> name src)
716 (add-ref-from-context dag name))
717 (else dag))))
718
719 (lambda (x dag env locs)
720 ;; Going down into X.
721 (let ((ctx (reference-dag-toplevel-context dag))
722 (refs (reference-dag-refs dag))
723 (defs (reference-dag-defs dag)))
724 (record-case x
725 ((<toplevel-define> name src)
726 (let ((refs refs)
727 (defs (alist-cons name (or src (find pair? locs))
728 defs)))
729 (make-reference-dag refs defs name)))
730 ((<toplevel-set> name src)
731 (add-ref-from-context dag name))
732 (else dag))))
733
734 (lambda (x dag env locs)
735 ;; Leaving X's scope.
736 (record-case x
737 ((<toplevel-define>)
738 (let ((refs (reference-dag-refs dag))
739 (defs (reference-dag-defs dag)))
740 (make-reference-dag refs defs #f)))
741 (else dag)))
742
743 (lambda (dag env)
744 ;; Process the resulting reference DAG: determine all private definitions
745 ;; not reachable from any public definition. Macros
746 ;; (syntax-transformers), which are globally bound, never considered
747 ;; unused since we can't tell whether a macro is actually used; in
748 ;; addition, macros are considered roots of the DAG since they may use
749 ;; private bindings. FIXME: The `make-syntax-transformer' calls don't
750 ;; contain any literal `toplevel-ref' of the global bindings they use so
751 ;; this strategy fails.
752 (define (exported? name)
753 (if (module? env)
754 (module-variable (module-public-interface env) name)
755 #t))
756
757 (let-values (((public-defs private-defs)
758 (partition (lambda (name+src)
759 (let ((name (car name+src)))
760 (or (exported? name)
761 (macro-variable? name env))))
762 (reference-dag-defs dag))))
763 (let* ((roots (cons #f (map car public-defs)))
764 (refs (reference-dag-refs dag))
765 (reachable (dag-reachable-nodes* roots refs))
766 (unused (filter (lambda (name+src)
767 ;; FIXME: This is inefficient when
768 ;; REACHABLE is large (e.g., boot-9.scm);
769 ;; use a vhash or equivalent.
770 (not (memq (car name+src) reachable)))
771 private-defs)))
772 (for-each (lambda (name+loc)
773 (let ((name (car name+loc))
774 (loc (cdr name+loc)))
775 (warning 'unused-toplevel loc name)))
776 (reverse unused)))))
777
778 (make-reference-dag '() '() #f))))
779
780 \f
781 ;;;
782 ;;; Unbound variable analysis.
783 ;;;
784
785 ;; <toplevel-info> records are used during tree traversal in search of
786 ;; possibly unbound variable. They contain a list of references to
787 ;; potentially unbound top-level variables, and a list of the top-level
788 ;; defines that have been encountered.
789 (define-record-type <toplevel-info>
790 (make-toplevel-info refs defs)
791 toplevel-info?
792 (refs toplevel-info-refs) ;; ((VARIABLE-NAME . LOCATION) ...)
793 (defs toplevel-info-defs)) ;; (VARIABLE-NAME ...)
794
795 (define (goops-toplevel-definition proc args env)
796 ;; If application of PROC to ARGS is a GOOPS top-level definition, return
797 ;; the name of the variable being defined; otherwise return #f. This
798 ;; assumes knowledge of the current implementation of `define-class' et al.
799 (define (toplevel-define-arg args)
800 (and (pair? args) (pair? (cdr args)) (null? (cddr args))
801 (record-case (car args)
802 ((<const> exp)
803 (and (symbol? exp) exp))
804 (else #f))))
805
806 (record-case proc
807 ((<module-ref> mod public? name)
808 (and (equal? mod '(oop goops))
809 (not public?)
810 (eq? name 'toplevel-define!)
811 (toplevel-define-arg args)))
812 ((<toplevel-ref> name)
813 ;; This may be the result of expanding one of the GOOPS macros within
814 ;; `oop/goops.scm'.
815 (and (eq? name 'toplevel-define!)
816 (eq? env (resolve-module '(oop goops)))
817 (toplevel-define-arg args)))
818 (else #f)))
819
820 (define unbound-variable-analysis
821 ;; Report possibly unbound variables in the given tree.
822 (make-tree-analysis
823 (lambda (x info env locs)
824 ;; X is a leaf: extend INFO's refs accordingly.
825 (let ((refs (toplevel-info-refs info))
826 (defs (toplevel-info-defs info)))
827 (define (bound? name)
828 (or (and (module? env)
829 (module-variable env name))
830 (memq name defs)))
831
832 (record-case x
833 ((<toplevel-ref> name src)
834 (if (bound? name)
835 info
836 (let ((src (or src (find pair? locs))))
837 (make-toplevel-info (alist-cons name src refs)
838 defs))))
839 (else info))))
840
841 (lambda (x info env locs)
842 ;; Going down into X.
843 (let* ((refs (toplevel-info-refs info))
844 (defs (toplevel-info-defs info))
845 (src (tree-il-src x)))
846 (define (bound? name)
847 (or (and (module? env)
848 (module-variable env name))
849 (memq name defs)))
850
851 (record-case x
852 ((<toplevel-set> name src)
853 (if (bound? name)
854 (make-toplevel-info refs defs)
855 (let ((src (find pair? locs)))
856 (make-toplevel-info (alist-cons name src refs)
857 defs))))
858 ((<toplevel-define> name)
859 (make-toplevel-info (alist-delete name refs eq?)
860 (cons name defs)))
861
862 ((<application> proc args)
863 ;; Check for a dynamic top-level definition, as is
864 ;; done by code expanded from GOOPS macros.
865 (let ((name (goops-toplevel-definition proc args
866 env)))
867 (if (symbol? name)
868 (make-toplevel-info (alist-delete name refs
869 eq?)
870 (cons name defs))
871 (make-toplevel-info refs defs))))
872 (else
873 (make-toplevel-info refs defs)))))
874
875 (lambda (x info env locs)
876 ;; Leaving X's scope.
877 info)
878
879 (lambda (toplevel env)
880 ;; Post-process the result.
881 (for-each (lambda (name+loc)
882 (let ((name (car name+loc))
883 (loc (cdr name+loc)))
884 (warning 'unbound-variable loc name)))
885 (reverse (toplevel-info-refs toplevel))))
886
887 (make-toplevel-info '() '())))
888
889 \f
890 ;;;
891 ;;; Arity analysis.
892 ;;;
893
894 ;; <arity-info> records contain information about lexical definitions of
895 ;; procedures currently in scope, top-level procedure definitions that have
896 ;; been encountered, and calls to top-level procedures that have been
897 ;; encountered.
898 (define-record-type <arity-info>
899 (make-arity-info toplevel-calls lexical-lambdas toplevel-lambdas)
900 arity-info?
901 (toplevel-calls toplevel-procedure-calls) ;; ((NAME . APPLICATION) ...)
902 (lexical-lambdas lexical-lambdas) ;; ((GENSYM . DEFINITION) ...)
903 (toplevel-lambdas toplevel-lambdas)) ;; ((NAME . DEFINITION) ...)
904
905 (define (validate-arity proc application lexical?)
906 ;; Validate the argument count of APPLICATION, a tree-il application of
907 ;; PROC, emitting a warning in case of argument count mismatch.
908
909 (define (filter-keyword-args keywords allow-other-keys? args)
910 ;; Filter keyword arguments from ARGS and return the resulting list.
911 ;; KEYWORDS is the list of allowed keywords, and ALLOW-OTHER-KEYS?
912 ;; specified whethere keywords not listed in KEYWORDS are allowed.
913 (let loop ((args args)
914 (result '()))
915 (if (null? args)
916 (reverse result)
917 (let ((arg (car args)))
918 (if (and (const? arg)
919 (or (memq (const-exp arg) keywords)
920 (and allow-other-keys?
921 (keyword? (const-exp arg)))))
922 (loop (if (pair? (cdr args))
923 (cddr args)
924 '())
925 result)
926 (loop (cdr args)
927 (cons arg result)))))))
928
929 (define (arities proc)
930 ;; Return the arities of PROC, which can be either a tree-il or a
931 ;; procedure.
932 (define (len x)
933 (or (and (or (null? x) (pair? x))
934 (length x))
935 0))
936 (cond ((program? proc)
937 (values (program-name proc)
938 (map (lambda (a)
939 (list (arity:nreq a) (arity:nopt a) (arity:rest? a)
940 (map car (arity:kw a))
941 (arity:allow-other-keys? a)))
942 (program-arities proc))))
943 ((procedure? proc)
944 (let ((arity (procedure-property proc 'arity)))
945 (values (procedure-name proc)
946 (list (list (car arity) (cadr arity) (caddr arity)
947 #f #f)))))
948 (else
949 (let loop ((name #f)
950 (proc proc)
951 (arities '()))
952 (if (not proc)
953 (values name (reverse arities))
954 (record-case proc
955 ((<lambda-case> req opt rest kw alternate)
956 (loop name alternate
957 (cons (list (len req) (len opt) rest
958 (and (pair? kw) (map car (cdr kw)))
959 (and (pair? kw) (car kw)))
960 arities)))
961 ((<lambda> meta body)
962 (loop (assoc-ref meta 'name) body arities))
963 (else
964 (values #f #f))))))))
965
966 (let ((args (application-args application))
967 (src (tree-il-src application)))
968 (call-with-values (lambda () (arities proc))
969 (lambda (name arities)
970 (define matches?
971 (find (lambda (arity)
972 (pmatch arity
973 ((,req ,opt ,rest? ,kw ,aok?)
974 (let ((args (if (pair? kw)
975 (filter-keyword-args kw aok? args)
976 args)))
977 (if (and req opt)
978 (let ((count (length args)))
979 (and (>= count req)
980 (or rest?
981 (<= count (+ req opt)))))
982 #t)))
983 (else #t)))
984 arities))
985
986 (if (not matches?)
987 (warning 'arity-mismatch src
988 (or name (with-output-to-string (lambda () (write proc))))
989 lexical?)))))
990 #t)
991
992 (define arity-analysis
993 ;; Report arity mismatches in the given tree.
994 (make-tree-analysis
995 (lambda (x info env locs)
996 ;; X is a leaf.
997 info)
998 (lambda (x info env locs)
999 ;; Down into X.
1000 (define (extend lexical-name val info)
1001 ;; If VAL is a lambda, add NAME to the lexical-lambdas of INFO.
1002 (let ((toplevel-calls (toplevel-procedure-calls info))
1003 (lexical-lambdas (lexical-lambdas info))
1004 (toplevel-lambdas (toplevel-lambdas info)))
1005 (record-case val
1006 ((<lambda> body)
1007 (make-arity-info toplevel-calls
1008 (alist-cons lexical-name val
1009 lexical-lambdas)
1010 toplevel-lambdas))
1011 ((<lexical-ref> gensym)
1012 ;; lexical alias
1013 (let ((val* (assq gensym lexical-lambdas)))
1014 (if (pair? val*)
1015 (extend lexical-name (cdr val*) info)
1016 info)))
1017 ((<toplevel-ref> name)
1018 ;; top-level alias
1019 (make-arity-info toplevel-calls
1020 (alist-cons lexical-name val
1021 lexical-lambdas)
1022 toplevel-lambdas))
1023 (else info))))
1024
1025 (let ((toplevel-calls (toplevel-procedure-calls info))
1026 (lexical-lambdas (lexical-lambdas info))
1027 (toplevel-lambdas (toplevel-lambdas info)))
1028
1029 (record-case x
1030 ((<toplevel-define> name exp)
1031 (record-case exp
1032 ((<lambda> body)
1033 (make-arity-info toplevel-calls
1034 lexical-lambdas
1035 (alist-cons name exp toplevel-lambdas)))
1036 ((<toplevel-ref> name)
1037 ;; alias for another toplevel
1038 (let ((proc (assq name toplevel-lambdas)))
1039 (make-arity-info toplevel-calls
1040 lexical-lambdas
1041 (alist-cons (toplevel-define-name x)
1042 (if (pair? proc)
1043 (cdr proc)
1044 exp)
1045 toplevel-lambdas))))
1046 (else info)))
1047 ((<let> vars vals)
1048 (fold extend info vars vals))
1049 ((<letrec> vars vals)
1050 (fold extend info vars vals))
1051 ((<fix> vars vals)
1052 (fold extend info vars vals))
1053
1054 ((<application> proc args src)
1055 (record-case proc
1056 ((<lambda> body)
1057 (validate-arity proc x #t)
1058 info)
1059 ((<toplevel-ref> name)
1060 (make-arity-info (alist-cons name x toplevel-calls)
1061 lexical-lambdas
1062 toplevel-lambdas))
1063 ((<lexical-ref> gensym)
1064 (let ((proc (assq gensym lexical-lambdas)))
1065 (if (pair? proc)
1066 (record-case (cdr proc)
1067 ((<toplevel-ref> name)
1068 ;; alias to toplevel
1069 (make-arity-info (alist-cons name x toplevel-calls)
1070 lexical-lambdas
1071 toplevel-lambdas))
1072 (else
1073 (validate-arity (cdr proc) x #t)
1074 info))
1075
1076 ;; If GENSYM wasn't found, it may be because it's an
1077 ;; argument of the procedure being compiled.
1078 info)))
1079 (else info)))
1080 (else info))))
1081
1082 (lambda (x info env locs)
1083 ;; Up from X.
1084 (define (shrink name val info)
1085 ;; Remove NAME from the lexical-lambdas of INFO.
1086 (let ((toplevel-calls (toplevel-procedure-calls info))
1087 (lexical-lambdas (lexical-lambdas info))
1088 (toplevel-lambdas (toplevel-lambdas info)))
1089 (make-arity-info toplevel-calls
1090 (alist-delete name lexical-lambdas eq?)
1091 toplevel-lambdas)))
1092
1093 (let ((toplevel-calls (toplevel-procedure-calls info))
1094 (lexical-lambdas (lexical-lambdas info))
1095 (toplevel-lambdas (toplevel-lambdas info)))
1096 (record-case x
1097 ((<let> vars vals)
1098 (fold shrink info vars vals))
1099 ((<letrec> vars vals)
1100 (fold shrink info vars vals))
1101 ((<fix> vars vals)
1102 (fold shrink info vars vals))
1103
1104 (else info))))
1105
1106 (lambda (result env)
1107 ;; Post-processing: check all top-level procedure calls that have been
1108 ;; encountered.
1109 (let ((toplevel-calls (toplevel-procedure-calls result))
1110 (toplevel-lambdas (toplevel-lambdas result)))
1111 (for-each (lambda (name+application)
1112 (let* ((name (car name+application))
1113 (application (cdr name+application))
1114 (proc
1115 (or (assoc-ref toplevel-lambdas name)
1116 (and (module? env)
1117 (false-if-exception
1118 (module-ref env name)))))
1119 (proc*
1120 ;; handle toplevel aliases
1121 (if (toplevel-ref? proc)
1122 (let ((name (toplevel-ref-name proc)))
1123 (and (module? env)
1124 (false-if-exception
1125 (module-ref env name))))
1126 proc)))
1127 ;; (format #t "toplevel-call to ~A (~A) from ~A~%"
1128 ;; name proc* application)
1129 (if (or (lambda? proc*) (procedure? proc*))
1130 (validate-arity proc* application (lambda? proc*)))))
1131 toplevel-calls)))
1132
1133 (make-arity-info '() '() '())))