Fix reading and writing arities into DWARF.
[bpt/guile.git] / module / system / vm / program.scm
1 ;;; Guile VM program functions
2
3 ;;; Copyright (C) 2001, 2009, 2010, 2013 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 (system vm program)
22 #:use-module (ice-9 match)
23 #:use-module (system vm instruction)
24 #:use-module (system vm objcode)
25 #:use-module (system vm debug)
26 #:use-module (rnrs bytevectors)
27 #:use-module (srfi srfi-1)
28 #:use-module (srfi srfi-26)
29 #:export (make-program
30 make-rtl-program
31
32 make-binding binding:name binding:boxed? binding:index
33 binding:start binding:end
34
35 source:addr source:line source:column source:file
36 source:line-for-user
37 program-sources program-sources-pre-retire program-source
38
39 program-bindings program-bindings-by-index program-bindings-for-ip
40 program-arities program-arity arity:start arity:end
41
42 arity:nreq arity:nopt arity:rest? arity:kw arity:allow-other-keys?
43
44 program-arguments-alist program-arguments-alists
45 program-lambda-list
46
47 program-meta
48 program-objcode program? program-objects
49 rtl-program? rtl-program-code
50 program-module program-base
51 program-free-variables
52 program-num-free-variables
53 program-free-variable-ref program-free-variable-set!))
54
55 (load-extension (string-append "libguile-" (effective-version))
56 "scm_init_programs")
57
58 ;; These procedures are called by programs.c.
59 (define (rtl-program-name program)
60 (unless (rtl-program? program)
61 (error "shouldn't get here"))
62 (and=> (find-program-debug-info (rtl-program-code program))
63 program-debug-info-name))
64 (define (rtl-program-documentation program)
65 (unless (rtl-program? program)
66 (error "shouldn't get here"))
67 (find-program-docstring (rtl-program-code program)))
68 (define (rtl-program-minimum-arity program)
69 (unless (rtl-program? program)
70 (error "shouldn't get here"))
71 (program-minimum-arity (rtl-program-code program)))
72 (define (rtl-program-properties program)
73 (unless (rtl-program? program)
74 (error "shouldn't get here"))
75 (find-program-properties (rtl-program-code program)))
76
77 (define (make-binding name boxed? index start end)
78 (list name boxed? index start end))
79 (define (binding:name b) (list-ref b 0))
80 (define (binding:boxed? b) (list-ref b 1))
81 (define (binding:index b) (list-ref b 2))
82 (define (binding:start b) (list-ref b 3))
83 (define (binding:end b) (list-ref b 4))
84
85 (define (source:addr source)
86 (car source))
87 (define (source:file source)
88 (cadr source))
89 (define (source:line source)
90 (caddr source))
91 (define (source:column source)
92 (cdddr source))
93
94 ;; Lines are zero-indexed inside Guile, but users expect them to be
95 ;; one-indexed. Columns, on the other hand, are zero-indexed to both. Go
96 ;; figure.
97 (define (source:line-for-user source)
98 (1+ (source:line source)))
99
100 ;; FIXME: pull this definition from elsewhere.
101 (define *bytecode-header-len* 8)
102
103 ;; We could decompile the program to get this, but that seems like a
104 ;; waste.
105 (define (bytecode-instruction-length bytecode ip)
106 (let* ((idx (+ ip *bytecode-header-len*))
107 (inst (opcode->instruction (bytevector-u8-ref bytecode idx))))
108 ;; 1+ for the instruction itself.
109 (1+ (cond
110 ((eq? inst 'load-program)
111 (+ (bytevector-u32-native-ref bytecode (+ idx 1))
112 (bytevector-u32-native-ref bytecode (+ idx 5))))
113 ((< (instruction-length inst) 0)
114 ;; variable length instruction -- the length is encoded in the
115 ;; instruction stream.
116 (+ (ash (bytevector-u8-ref bytecode (+ idx 1)) 16)
117 (ash (bytevector-u8-ref bytecode (+ idx 2)) 8)
118 (bytevector-u8-ref bytecode (+ idx 3))))
119 (else
120 ;; fixed length
121 (instruction-length inst))))))
122
123 (define (source-for-addr addr)
124 (and=> (find-source-for-addr addr)
125 (lambda (source)
126 ;; FIXME: absolute or relative address?
127 (cons* 0
128 (source-file source)
129 (source-line source)
130 (source-column source)))))
131
132 (define (program-sources proc)
133 (cond
134 ((rtl-program? proc)
135 (map (lambda (source)
136 (cons* (- (source-post-pc source) (rtl-program-code proc))
137 (source-file source)
138 (source-line source)
139 (source-column source)))
140 (find-program-sources (rtl-program-code proc))))
141 (else
142 (%program-sources proc))))
143
144 (define* (program-source proc ip #:optional (sources (program-sources proc)))
145 (let lp ((source #f) (sources sources))
146 (match sources
147 (() source)
148 (((and s (pc . _)) . sources)
149 (if (<= pc ip)
150 (lp s sources)
151 source)))))
152
153 ;; Source information could in theory be correlated with the ip of the
154 ;; instruction, or the ip just after the instruction is retired. Guile
155 ;; does the latter, to make backtraces easy -- an error produced while
156 ;; running an opcode always happens after it has retired its arguments.
157 ;;
158 ;; But for breakpoints and such, we need the ip before the instruction
159 ;; is retired -- before it has had a chance to do anything. So here we
160 ;; change from the post-retire addresses given by program-sources to
161 ;; pre-retire addresses.
162 ;;
163 (define (program-sources-pre-retire proc)
164 (cond
165 ((rtl-program? proc)
166 (map (lambda (source)
167 (cons* (- (source-pre-pc source) (rtl-program-code proc))
168 (source-file source)
169 (source-line source)
170 (source-column source)))
171 (find-program-sources (rtl-program-code proc))))
172 (else
173 (let ((bv (objcode->bytecode (program-objcode proc))))
174 (let lp ((in (program-sources proc))
175 (out '())
176 (ip 0))
177 (cond
178 ((null? in)
179 (reverse out))
180 (else
181 (match (car in)
182 ((post-ip . source)
183 (let lp2 ((ip ip)
184 (next ip))
185 (if (< next post-ip)
186 (lp2 next (+ next (bytecode-instruction-length bv next)))
187 (lp (cdr in)
188 (acons ip source out)
189 next))))
190 (_
191 (error "unexpected"))))))))))
192
193 (define (collapse-locals locs)
194 (let lp ((ret '()) (locs locs))
195 (if (null? locs)
196 (map cdr (sort! ret
197 (lambda (x y) (< (car x) (car y)))))
198 (let ((b (car locs)))
199 (cond
200 ((assv-ref ret (binding:index b))
201 => (lambda (bindings)
202 (append! bindings (list b))
203 (lp ret (cdr locs))))
204 (else
205 (lp (acons (binding:index b) (list b) ret)
206 (cdr locs))))))))
207
208 ;; returns list of list of bindings
209 ;; (list-ref ret N) == bindings bound to the Nth local slot
210 (define (program-bindings-by-index prog)
211 (cond ((rtl-program? prog) '())
212 ((program-bindings prog) => collapse-locals)
213 (else '())))
214
215 (define (program-bindings-for-ip prog ip)
216 (let lp ((in (program-bindings-by-index prog)) (out '()))
217 (if (null? in)
218 (reverse out)
219 (lp (cdr in)
220 (let inner ((binds (car in)))
221 (cond ((null? binds) out)
222 ((<= (binding:start (car binds))
223 ip
224 (binding:end (car binds)))
225 (cons (car binds) out))
226 (else (inner (cdr binds)))))))))
227
228 (define (arity:start a)
229 (match a ((start end . _) start) (_ (error "bad arity" a))))
230 (define (arity:end a)
231 (match a ((start end . _) end) (_ (error "bad arity" a))))
232 (define (arity:nreq a)
233 (match a ((_ _ nreq . _) nreq) (_ 0)))
234 (define (arity:nopt a)
235 (match a ((_ _ nreq nopt . _) nopt) (_ 0)))
236 (define (arity:rest? a)
237 (match a ((_ _ nreq nopt rest? . _) rest?) (_ #f)))
238 (define (arity:kw a)
239 (match a ((_ _ nreq nopt rest? (_ . kw)) kw) (_ '())))
240 (define (arity:allow-other-keys? a)
241 (match a ((_ _ nreq nopt rest? (aok . kw)) aok) (_ #f)))
242
243 (define (program-arity prog ip)
244 (let ((arities (program-arities prog)))
245 (and arities
246 (let lp ((arities arities))
247 (cond ((null? arities) #f)
248 ((not ip) (car arities)) ; take the first one
249 ((and (< (arity:start (car arities)) ip)
250 (<= ip (arity:end (car arities))))
251 (car arities))
252 (else (lp (cdr arities))))))))
253
254 (define (arglist->arguments-alist arglist)
255 (match arglist
256 ((req opt keyword allow-other-keys? rest . extents)
257 `((required . ,req)
258 (optional . ,opt)
259 (keyword . ,keyword)
260 (allow-other-keys? . ,allow-other-keys?)
261 (rest . ,rest)
262 (extents . ,extents)))
263 (_ #f)))
264
265 (define* (arity->arguments-alist prog arity
266 #:optional
267 (make-placeholder
268 (lambda (i) (string->symbol "_"))))
269 (define var-by-index
270 (let ((rbinds (map (lambda (x)
271 (cons (binding:index x) (binding:name x)))
272 (program-bindings-for-ip prog
273 (arity:start arity)))))
274 (lambda (i)
275 (or (assv-ref rbinds i)
276 ;; if we don't know the name, return a placeholder
277 (make-placeholder i)))))
278
279 (let lp ((nreq (arity:nreq arity)) (req '())
280 (nopt (arity:nopt arity)) (opt '())
281 (rest? (arity:rest? arity)) (rest #f)
282 (n 0))
283 (cond
284 ((< 0 nreq)
285 (lp (1- nreq) (cons (var-by-index n) req)
286 nopt opt rest? rest (1+ n)))
287 ((< 0 nopt)
288 (lp nreq req
289 (1- nopt) (cons (var-by-index n) opt)
290 rest? rest (1+ n)))
291 (rest?
292 (lp nreq req nopt opt
293 #f (var-by-index (+ n (length (arity:kw arity))))
294 (1+ n)))
295 (else
296 `((required . ,(reverse req))
297 (optional . ,(reverse opt))
298 (keyword . ,(arity:kw arity))
299 (allow-other-keys? . ,(arity:allow-other-keys? arity))
300 (rest . ,rest))))))
301
302 ;; the name "program-arguments" is taken by features.c...
303 (define* (program-arguments-alist prog #:optional ip)
304 "Returns the signature of the given procedure in the form of an association list."
305 (cond
306 ((primitive? prog)
307 (match (procedure-minimum-arity prog)
308 (#f #f)
309 ((nreq nopt rest?)
310 (let ((start (primitive-call-ip prog)))
311 ;; Assume that there is only one IP for the call.
312 (and (or (not ip) (= start ip))
313 (arity->arguments-alist
314 prog
315 (list 0 0 nreq nopt rest? '(#f . ()))))))))
316 ((rtl-program? prog)
317 (or-map (lambda (arity)
318 (and (or (not ip)
319 (and (<= (arity-low-pc arity) ip)
320 (< ip (arity-high-pc arity))))
321 (arity-arguments-alist arity)))
322 (or (find-program-arities (rtl-program-code prog)) '())))
323 (else
324 (let ((arity (program-arity prog ip)))
325 (and arity
326 (arity->arguments-alist prog arity))))))
327
328 (define* (program-lambda-list prog #:optional ip)
329 "Returns the signature of the given procedure in the form of an argument list."
330 (and=> (program-arguments-alist prog ip) arguments-alist->lambda-list))
331
332 (define (arguments-alist->lambda-list arguments-alist)
333 (let ((req (or (assq-ref arguments-alist 'required) '()))
334 (opt (or (assq-ref arguments-alist 'optional) '()))
335 (key (map keyword->symbol
336 (map car (or (assq-ref arguments-alist 'keyword) '()))))
337 (rest (or (assq-ref arguments-alist 'rest) '())))
338 `(,@req
339 ,@(if (pair? opt) (cons #:optional opt) '())
340 ,@(if (pair? key) (cons #:key key) '())
341 . ,rest)))
342
343 (define (program-free-variables prog)
344 "Return the list of free variables of PROG."
345 (let ((count (program-num-free-variables prog)))
346 (unfold (lambda (i) (>= i count))
347 (cut program-free-variable-ref prog <>)
348 1+
349 0)))
350
351 (define (program-arguments-alists prog)
352 "Returns all arities of the given procedure, as a list of association
353 lists."
354 (define (fallback)
355 (match (procedure-minimum-arity prog)
356 (#f '())
357 ((nreq nopt rest?)
358 (list
359 (arity->arguments-alist
360 prog
361 (list 0 0 nreq nopt rest? '(#f . ())))))))
362 (cond
363 ((primitive? prog) (fallback))
364 ((rtl-program? prog)
365 (let ((arities (find-program-arities (rtl-program-code prog))))
366 (if arities
367 (map arity-arguments-alist arities)
368 (fallback))))
369 ((program? prog)
370 (map (lambda (arity) (arity->arguments-alist prog arity))
371 (or (program-arities prog) '())))
372 (else (error "expected a program" prog))))
373
374 (define (write-program prog port)
375 (define (program-identity-string)
376 (or (procedure-name prog)
377 (and=> (program-source prog 0)
378 (lambda (s)
379 (format #f "~a at ~a:~a:~a"
380 (number->string (object-address prog) 16)
381 (or (source:file s)
382 (if s "<current input>" "<unknown port>"))
383 (source:line-for-user s) (source:column s))))
384 (number->string (object-address prog) 16)))
385
386 (define (program-formals-string)
387 (let ((arguments (program-arguments-alists prog)))
388 (if (null? arguments)
389 ""
390 (string-append
391 " " (string-join (map (lambda (a)
392 (object->string
393 (arguments-alist->lambda-list a)))
394 arguments)
395 " | ")))))
396
397 (format port "#<procedure ~a~a>"
398 (program-identity-string) (program-formals-string)))