dynamic-wind compilation to VM ops
[bpt/guile.git] / module / language / tree-il / analyze.scm
CommitLineData
cf10678f
AW
1;;; TREE-IL -> GLIL compiler
2
795ab688 3;; Copyright (C) 2001,2008,2009,2010 Free Software Foundation, Inc.
cf10678f 4
53befeb7
NJ
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.
bcae9a98 9;;;;
53befeb7
NJ
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.
bcae9a98 14;;;;
53befeb7
NJ
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
cf10678f
AW
18
19;;; Code:
20
21(define-module (language tree-il analyze)
66d3e9a3 22 #:use-module (srfi srfi-1)
4b856371 23 #:use-module (srfi srfi-9)
bcae9a98 24 #:use-module (srfi srfi-11)
cf10678f 25 #:use-module (system base syntax)
4b856371 26 #:use-module (system base message)
af5ed549 27 #:use-module (system vm program)
cf10678f 28 #:use-module (language tree-il)
99480e11 29 #:use-module (system base pmatch)
4b856371 30 #:export (analyze-lexicals
48b1db75
LC
31 analyze-tree
32 unused-variable-analysis
bcae9a98 33 unused-toplevel-analysis
ae03cf1f
LC
34 unbound-variable-analysis
35 arity-analysis))
cf10678f 36
66d3e9a3
AW
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:
cf10678f
AW
63;; (let (0 1)
64;; (let (2 3) ...)
65;; (let (2) ...))
66;; (let (2 3 4) ...))
67;; etc.
68;;
5af166bd
AW
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;;
b6d93b11
AW
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.
5af166bd 83;;
66d3e9a3
AW
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;;
9059993f
AW
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;;
66d3e9a3
AW
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
9059993f
AW
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.
66d3e9a3 120;;
8a4ca0ea
AW
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;;
66d3e9a3
AW
127;; That is:
128;;
129;; sym -> {lambda -> address}
8a4ca0ea
AW
130;; lambda -> (labels . free-locs)
131;; lambda-case -> (gensym . nlocs)
66d3e9a3 132;;
9059993f 133;; address ::= (local? boxed? . index)
8a4ca0ea 134;; labels ::= ((sym . lambda) ...)
66d3e9a3
AW
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))
cf10678f
AW
142
143(define (analyze-lexicals x)
66d3e9a3
AW
144 ;; bound-vars: lambda -> (sym ...)
145 ;; all identifiers bound within a lambda
9059993f 146 (define bound-vars (make-hash-table))
66d3e9a3
AW
147 ;; free-vars: lambda -> (sym ...)
148 ;; all identifiers referenced in a lambda, but not bound
149 ;; NB, this includes identifiers referenced by contained lambdas
9059993f 150 (define free-vars (make-hash-table))
66d3e9a3
AW
151 ;; assigned: sym -> #t
152 ;; variables that are assigned
d97b69d9 153 (define assigned (make-hash-table))
5af166bd 154 ;; refcounts: sym -> count
66d3e9a3 155 ;; allows us to detect the or-expansion in O(1) time
9059993f 156 (define refcounts (make-hash-table))
8a4ca0ea 157 ;; labels: sym -> lambda
9059993f 158 ;; for determining if fixed-point procedures can be rendered as
8a4ca0ea 159 ;; labels.
9059993f
AW
160 (define labels (make-hash-table))
161
66d3e9a3 162 ;; returns variables referenced in expr
d97b69d9
AW
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))
cf10678f
AW
171 (record-case x
172 ((<application> proc args)
d97b69d9
AW
173 (apply lset-union eq? (step-tail-call proc args)
174 (map step args)))
cf10678f 175
b6d93b11
AW
176 ((<conditional> test consequent alternate)
177 (lset-union eq? (step test) (step-tail consequent) (step-tail alternate)))
cf10678f 178
e5f5113c 179 ((<lexical-ref> gensym)
5af166bd 180 (hashq-set! refcounts gensym (1+ (hashq-ref refcounts gensym 0)))
d97b69d9
AW
181 (if (not (and tail-call-args
182 (memq gensym labels-in-proc)
8a4ca0ea
AW
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))
1e2a8edb 195 (not (lambda-case-rest c)))
3a88cb3b 196 (lp (lambda-case-alternate c)))))))))
d97b69d9 197 (hashq-set! labels gensym #f))
66d3e9a3 198 (list gensym))
cf10678f 199
e5f5113c 200 ((<lexical-set> gensym exp)
66d3e9a3 201 (hashq-set! assigned gensym #t)
d97b69d9 202 (hashq-set! labels gensym #f)
66d3e9a3 203 (lset-adjoin eq? (step exp) gensym))
cf10678f 204
e5f5113c 205 ((<module-set> exp)
cf10678f
AW
206 (step exp))
207
e5f5113c 208 ((<toplevel-set> exp)
cf10678f
AW
209 (step exp))
210
e5f5113c 211 ((<toplevel-define> exp)
cf10678f
AW
212 (step exp))
213
214 ((<sequence> exps)
d97b69d9
AW
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))))))))
cf10678f 221
8a4ca0ea
AW
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
3a88cb3b 230 ((<lambda-case> opt kw inits vars body alternate)
8a4ca0ea
AW
231 (hashq-set! bound-vars proc
232 (append (reverse vars) (hashq-ref bound-vars proc)))
233 (lset-union
234 eq?
235 (lset-difference eq?
b0c8c187
AW
236 (lset-union eq?
237 (apply lset-union eq? (map step inits))
8a4ca0ea
AW
238 (step-tail body))
239 vars)
3a88cb3b 240 (if alternate (step-tail alternate) '())))
66d3e9a3 241
f4aa8d53 242 ((<let> vars vals body)
66d3e9a3
AW
243 (hashq-set! bound-vars proc
244 (append (reverse vars) (hashq-ref bound-vars proc)))
245 (lset-difference eq?
d97b69d9 246 (apply lset-union eq? (step-tail body) (map step vals))
66d3e9a3 247 vars))
cf10678f 248
f4aa8d53 249 ((<letrec> vars vals body)
66d3e9a3
AW
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?
d97b69d9 254 (apply lset-union eq? (step-tail body) (map step vals))
66d3e9a3
AW
255 vars))
256
c21c89b1 257 ((<fix> vars vals body)
d97b69d9 258 ;; Try to allocate these procedures as labels.
8a4ca0ea 259 (for-each (lambda (sym val) (hashq-set! labels sym val))
d97b69d9 260 vars vals)
c21c89b1
AW
261 (hashq-set! bound-vars proc
262 (append (reverse vars) (hashq-ref bound-vars proc)))
d97b69d9
AW
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
8a4ca0ea
AW
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))))
d97b69d9
AW
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)))
c21c89b1 327
8a4ca0ea
AW
328 ((<let-values> exp body)
329 (lset-union eq? (step exp) (step body)))
66d3e9a3
AW
330
331 (else '())))
332
9059993f
AW
333 ;; allocation: sym -> {lambda -> address}
334 ;; lambda -> (nlocs labels . free-locs)
335 (define allocation (make-hash-table))
336
66d3e9a3
AW
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)))
cf10678f 342
b6d93b11
AW
343 ((<conditional> test consequent alternate)
344 (max (recur test) (recur consequent) (recur alternate)))
cf10678f 345
e5f5113c 346 ((<lexical-set> exp)
66d3e9a3
AW
347 (recur exp))
348
e5f5113c 349 ((<module-set> exp)
66d3e9a3
AW
350 (recur exp))
351
e5f5113c 352 ((<toplevel-set> exp)
66d3e9a3
AW
353 (recur exp))
354
e5f5113c 355 ((<toplevel-define> exp)
66d3e9a3
AW
356 (recur exp))
357
358 ((<sequence> exps)
359 (apply max (map recur exps)))
360
8a4ca0ea 361 ((<lambda> body)
66d3e9a3
AW
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
8a4ca0ea 371 (let ((nlocs (allocate! body x 0))
66d3e9a3
AW
372 (free-addresses
373 (map (lambda (v)
374 (hashq-ref (hashq-ref allocation v) proc))
9059993f
AW
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)))))
66d3e9a3 380 ;; set procedure allocations
8a4ca0ea 381 (hashq-set! allocation x (cons labels free-addresses)))
66d3e9a3 382 n)
cf10678f 383
3a88cb3b 384 ((<lambda-case> opt kw inits vars body alternate)
8a4ca0ea
AW
385 (max
386 (let lp ((vars vars) (n n))
387 (if (null? vars)
b0c8c187
AW
388 (let ((nlocs (apply
389 max
b0c8c187
AW
390 (allocate! body proc n)
391 ;; inits not logically at the end, but they
392 ;; are the list...
9a9d82c2 393 (map (lambda (x) (allocate! x proc n)) inits))))
8a4ca0ea
AW
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)))))
3a88cb3b 402 (if alternate (allocate! alternate proc n) n)))
8a4ca0ea 403
66d3e9a3
AW
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)
b6d93b11
AW
415 (lexical-ref? (conditional-consequent body))
416 (eq? (lexical-ref-gensym (conditional-consequent body)) v))))
66d3e9a3
AW
417 (hashq-set! allocation (car vars)
418 (make-hashq proc `(#t #f . ,n)))
419 ;; the 1+ for this var
b6d93b11 420 (max nmax (1+ n) (allocate! (conditional-alternate body) proc n)))
66d3e9a3
AW
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)))
cf10678f
AW
426 (hashq-set!
427 allocation v
66d3e9a3
AW
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))))))
cf10678f 446
c21c89b1 447 ((<fix> vars vals body)
d97b69d9
AW
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))
8a4ca0ea 455 ;; allocate lambda body inline to proc
d97b69d9
AW
456 (lp (cdr vars)
457 (cdr vals)
458 (record-case (car vals)
8a4ca0ea
AW
459 ((<lambda> body)
460 (max nmax (allocate! body proc n))))))
d97b69d9
AW
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))))))))
c21c89b1 478
8a4ca0ea
AW
479 ((<let-values> exp body)
480 (max (recur exp) (recur body)))
66d3e9a3
AW
481
482 (else n)))
cf10678f 483
d97b69d9 484 (analyze! x #f '() #t #f)
66d3e9a3 485 (allocate! x #f 0)
cf10678f
AW
486
487 allocation)
4b856371
LC
488
489\f
48b1db75
LC
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?
795ab688
LC
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) ...)
48b1db75
LC
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
795ab688
LC
505`tree-il-fold'. Return TREE. The leaf/down/up procedures of each analysis are
506passed a ``location stack', which is the stack of `tree-il-src' values for each
507parent tree (a list); it can be used to approximate source location when
508accurate 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.
48b1db75 513 (lambda (x results)
795ab688
LC
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))
48b1db75
LC
525
526 (let ((results
795ab688
LC
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))
48b1db75
LC
532 tree)))
533
534 (for-each (lambda (analysis result)
535 ((tree-analysis-post analysis) result env))
536 analyses
795ab688 537 (cdr results)))
48b1db75
LC
538
539 tree)
540
541\f
4b856371
LC
542;;;
543;;; Unused variable analysis.
544;;;
545
546;; <binding-info> records are used during tree traversals in
795ab688
LC
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.
4b856371 549(define-record-type <binding-info>
795ab688 550 (make-binding-info vars refs)
4b856371
LC
551 binding-info?
552 (vars binding-info-vars) ;; ((GENSYM NAME LOCATION) ...)
795ab688 553 (refs binding-info-refs)) ;; (GENSYM ...)
4b856371 554
48b1db75 555(define unused-variable-analysis
ae03cf1f 556 ;; Report unused variables in the given tree.
48b1db75 557 (make-tree-analysis
795ab688 558 (lambda (x info env locs)
48b1db75
LC
559 ;; X is a leaf: extend INFO's refs accordingly.
560 (let ((refs (binding-info-refs info))
795ab688 561 (vars (binding-info-vars info)))
48b1db75
LC
562 (record-case x
563 ((<lexical-ref> gensym)
795ab688 564 (make-binding-info vars (cons gensym refs)))
48b1db75
LC
565 (else info))))
566
795ab688 567 (lambda (x info env locs)
48b1db75
LC
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))
48b1db75
LC
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)
795ab688 581 (make-binding-info vars (cons gensym refs)))
48b1db75 582 ((<lambda-case> req opt inits rest kw vars)
48b1db75 583 (let ((names `(,@req
632e7c32 584 ,@(or opt '())
48b1db75
LC
585 ,@(if rest (list rest) '())
586 ,@(if kw (map cadr (cdr kw)) '()))))
795ab688 587 (make-binding-info (extend vars names) refs)))
48b1db75 588 ((<let> vars names)
795ab688 589 (make-binding-info (extend vars names) refs))
48b1db75 590 ((<letrec> vars names)
795ab688 591 (make-binding-info (extend vars names) refs))
48b1db75 592 ((<fix> vars names)
795ab688 593 (make-binding-info (extend vars names) refs))
48b1db75
LC
594 (else info))))
595
795ab688 596 (lambda (x info env locs)
48b1db75
LC
597 ;; Leaving X's scope: shrink INFO's variable list
598 ;; accordingly and reported unused nested variables.
599 (let ((refs (binding-info-refs info))
795ab688 600 (vars (binding-info-vars info)))
48b1db75
LC
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)
795ab688 628 (make-binding-info (shrink vars refs) refs))
48b1db75 629 ((<let> vars)
795ab688 630 (make-binding-info (shrink vars refs) refs))
48b1db75 631 ((<letrec> vars)
795ab688 632 (make-binding-info (shrink vars refs) refs))
48b1db75 633 ((<fix> vars)
795ab688 634 (make-binding-info (shrink vars refs) refs))
48b1db75
LC
635 (else info))))
636
637 (lambda (result env) #t)
795ab688 638 (make-binding-info '() '())))
f67ddf9d
LC
639
640\f
bcae9a98
LC
641;;;
642;;; Unused top-level variable analysis.
643;;;
644
628ddb80 645;; <reference-graph> record top-level definitions that are made, references to
bcae9a98
LC
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
628ddb80
LC
649;; effectively a graph from which we can determine unused top-level definitions.
650(define-record-type <reference-graph>
651 (make-reference-graph refs defs toplevel-context)
652 reference-graph?
653 (defs reference-graph-defs) ;; ((NAME . LOC) ...)
654 (refs reference-graph-refs) ;; ((REF-CONTEXT REF ...) ...)
655 (toplevel-context reference-graph-toplevel-context)) ;; NAME | #f
656
657(define (graph-reachable-nodes root refs)
658 ;; Return the list of nodes reachable from ROOT in graph REFS. REFS is an alist
bcae9a98
LC
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
628ddb80 684(define (graph-reachable-nodes* roots refs)
bcae9a98
LC
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)
628ddb80 689 (cons node (graph-reachable-nodes node refs)))
bcae9a98
LC
690 roots)))
691
692(define unused-toplevel-analysis
693 ;; Report unused top-level definitions that are not exported.
694 (let ((add-ref-from-context
628ddb80
LC
695 (lambda (graph name)
696 ;; Add an edge CTX -> NAME in GRAPH.
697 (let* ((refs (reference-graph-refs graph))
698 (defs (reference-graph-defs graph))
699 (ctx (reference-graph-toplevel-context graph))
bcae9a98 700 (ctx-refs (or (assoc-ref refs ctx) '())))
628ddb80
LC
701 (make-reference-graph (alist-cons ctx (cons name ctx-refs)
702 (alist-delete ctx refs eq?))
703 defs ctx)))))
bcae9a98
LC
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
628ddb80 711 (lambda (x graph env locs)
bcae9a98 712 ;; X is a leaf.
628ddb80 713 (let ((ctx (reference-graph-toplevel-context graph)))
bcae9a98
LC
714 (record-case x
715 ((<toplevel-ref> name src)
628ddb80
LC
716 (add-ref-from-context graph name))
717 (else graph))))
bcae9a98 718
628ddb80 719 (lambda (x graph env locs)
bcae9a98 720 ;; Going down into X.
628ddb80
LC
721 (let ((ctx (reference-graph-toplevel-context graph))
722 (refs (reference-graph-refs graph))
723 (defs (reference-graph-defs graph)))
bcae9a98
LC
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)))
628ddb80 729 (make-reference-graph refs defs name)))
bcae9a98 730 ((<toplevel-set> name src)
628ddb80
LC
731 (add-ref-from-context graph name))
732 (else graph))))
bcae9a98 733
628ddb80 734 (lambda (x graph env locs)
bcae9a98
LC
735 ;; Leaving X's scope.
736 (record-case x
737 ((<toplevel-define>)
628ddb80
LC
738 (let ((refs (reference-graph-refs graph))
739 (defs (reference-graph-defs graph)))
740 (make-reference-graph refs defs #f)))
741 (else graph)))
bcae9a98 742
628ddb80
LC
743 (lambda (graph env)
744 ;; Process the resulting reference graph: determine all private definitions
bcae9a98
LC
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
628ddb80 748 ;; addition, macros are considered roots of the graph since they may use
bcae9a98
LC
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))))
628ddb80 762 (reference-graph-defs graph))))
bcae9a98 763 (let* ((roots (cons #f (map car public-defs)))
628ddb80
LC
764 (refs (reference-graph-refs graph))
765 (reachable (graph-reachable-nodes* roots refs))
bcae9a98
LC
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
628ddb80 778 (make-reference-graph '() '() #f))))
bcae9a98
LC
779
780\f
f67ddf9d
LC
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
795ab688
LC
787;; potentially unbound top-level variables, and a list of the top-level
788;; defines that have been encountered.
f67ddf9d 789(define-record-type <toplevel-info>
795ab688 790 (make-toplevel-info refs defs)
f67ddf9d
LC
791 toplevel-info?
792 (refs toplevel-info-refs) ;; ((VARIABLE-NAME . LOCATION) ...)
795ab688 793 (defs toplevel-info-defs)) ;; (VARIABLE-NAME ...)
f67ddf9d 794
6bb891dc 795(define (goops-toplevel-definition proc args env)
b6d2306d
LC
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.
6bb891dc
LC
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
b6d2306d
LC
806 (record-case proc
807 ((<module-ref> mod public? name)
808 (and (equal? mod '(oop goops))
809 (not public?)
810 (eq? name 'toplevel-define!)
6bb891dc
LC
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)))
b6d2306d
LC
818 (else #f)))
819
48b1db75 820(define unbound-variable-analysis
ae03cf1f 821 ;; Report possibly unbound variables in the given tree.
48b1db75 822 (make-tree-analysis
795ab688 823 (lambda (x info env locs)
48b1db75
LC
824 ;; X is a leaf: extend INFO's refs accordingly.
825 (let ((refs (toplevel-info-refs info))
795ab688 826 (defs (toplevel-info-defs info)))
48b1db75
LC
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)
795ab688 838 defs))))
48b1db75
LC
839 (else info))))
840
795ab688 841 (lambda (x info env locs)
48b1db75
LC
842 ;; Going down into X.
843 (let* ((refs (toplevel-info-refs info))
844 (defs (toplevel-info-defs info))
795ab688 845 (src (tree-il-src x)))
48b1db75
LC
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)
795ab688 854 (make-toplevel-info refs defs)
48b1db75
LC
855 (let ((src (find pair? locs)))
856 (make-toplevel-info (alist-cons name src refs)
795ab688 857 defs))))
48b1db75
LC
858 ((<toplevel-define> name)
859 (make-toplevel-info (alist-delete name refs eq?)
795ab688 860 (cons name defs)))
48b1db75
LC
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?)
795ab688
LC
870 (cons name defs))
871 (make-toplevel-info refs defs))))
48b1db75 872 (else
795ab688 873 (make-toplevel-info refs defs)))))
48b1db75 874
795ab688 875 (lambda (x info env locs)
48b1db75 876 ;; Leaving X's scope.
bcae9a98 877 info)
48b1db75
LC
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
795ab688 887 (make-toplevel-info '() '())))
ae03cf1f
LC
888
889\f
890;;;
891;;; Arity analysis.
892;;;
893
af5ed549 894;; <arity-info> records contain information about lexical definitions of
ae03cf1f
LC
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
af5ed549
LC
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
99480e11
LC
929 (define (arities proc)
930 ;; Return the arities of PROC, which can be either a tree-il or a
ae03cf1f
LC
931 ;; procedure.
932 (define (len x)
933 (or (and (or (null? x) (pair? x))
934 (length x))
935 0))
af5ed549 936 (cond ((program? proc)
99480e11
LC
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))))
ae03cf1f
LC
943 ((procedure? proc)
944 (let ((arity (procedure-property proc 'arity)))
945 (values (procedure-name proc)
99480e11
LC
946 (list (list (car arity) (cadr arity) (caddr arity)
947 #f #f)))))
ae03cf1f 948 (else
99480e11
LC
949 (let loop ((name #f)
950 (proc proc)
951 (arities '()))
952 (if (not proc)
953 (values name (reverse arities))
954 (record-case proc
3a88cb3b
AW
955 ((<lambda-case> req opt rest kw alternate)
956 (loop name alternate
99480e11
LC
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))))))))
ae03cf1f
LC
965
966 (let ((args (application-args application))
967 (src (tree-il-src application)))
99480e11
LC
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?)))))
ae03cf1f
LC
990 #t)
991
992(define arity-analysis
993 ;; Report arity mismatches in the given tree.
994 (make-tree-analysis
795ab688 995 (lambda (x info env locs)
ae03cf1f
LC
996 ;; X is a leaf.
997 info)
795ab688 998 (lambda (x info env locs)
ae03cf1f
LC
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
795ab688 1082 (lambda (x info env locs)
ae03cf1f
LC
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 '() '() '())))