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