source: trunk/source/compiler/PPC/ppc-disassemble.lisp @ 14382

Last change on this file since 14382 was 14382, checked in by rme, 9 years ago

Update insert-ppc-label for pickier defstruct.

(cl-test::disassemble.9 was failing)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.6 KB
RevLine 
[6]1;;;-*- Mode: Lisp; Package: CCL -*-
2;;;
[13067]3;;;   Copyright (C) 2009 Clozure Associates
[6]4;;;   Copyright (C) 1994-2001 Digitool, Inc
[13066]5;;;   This file is part of Clozure CL. 
[6]6;;;
[13066]7;;;   Clozure CL is licensed under the terms of the Lisp Lesser GNU Public
8;;;   License , known as the LLGPL and distributed with Clozure CL as the
[6]9;;;   file "LICENSE".  The LLGPL consists of a preamble and the LGPL,
[13066]10;;;   which is distributed with Clozure CL as the file "LGPL".  Where these
[6]11;;;   conflict, the preamble takes precedence. 
12;;;
[13066]13;;;   Clozure CL is referenced in the preamble as the "LIBRARY."
[6]14;;;
15;;;   The LLGPL is also available online at
16;;;   http://opensource.franz.com/preamble.html
17
18(eval-when (:compile-toplevel :load-toplevel :execute)
[1335]19  (require "NXENV")
[6]20  (require "DLL-NODE")
21  (require "PPC-ASM")
22  (require "PPC-LAP"))
23
[1120]24(defparameter *ppc-disassembly-backend* *host-backend*)
[1750]25(defparameter *ppc-disassemble-raw-instructions* nil)
[1120]26
[6]27(eval-when (:compile-toplevel :execute)
28  (require "PPCENV"))
29
30(defun ppc-gpr (r)
[1750]31  (or
32   (case (backend-target-arch-name *ppc-disassembly-backend*)
33     (:ppc32 (and (eql r ppc32::rcontext) 'ppc32::rcontext))
34     (:ppc64 (and (eql r ppc64::rcontext) 'ppc64::rcontext)))
35   (svref ppc::*gpr-register-names* r)))
[6]36
37(defun ppc-fpr (r)
[203]38  (svref ppc::*fpr-register-names* r))
[6]39
40(defun ppc-vr (r)
[203]41    (svref ppc::*vector-register-names* r))
[6]42
[1750]43;;; To "unmacroexpand" something is to undo the effects of
44;;; some sort of macroexpansion, returning some presumably
45;;; more meaningful equivalent form.  Some cases of this
46;;; are trivial (e.g., turning (stwu rX -4 vsp) into (vpush rX);
47;;; some would depend on surrounding context and are still
48;;; heuristic.  A few cases can probably benefit from state
49;;; maintained by preceding instructions, e.g., (twnei rX 1)
50;;; is presumably looking at the low 2 or three bits of rX; we
51;;; have to know what set rX to know which.
[6]52
[1750]53;;; For now, just try to handle a few simple cases.
54;;; Return a new form (new-opcode-name &rest new-operands) or NIL.
55;;;
[6]56
57(defparameter *ppc-unmacroexpanders* (make-hash-table :test #'equalp))
58
59(defun ppc-unmacroexpand-function (name)
60  (let* ((pname (string name))
[203]61         (opnum (gethash pname ppc::*ppc-opcode-numbers*)))
[6]62    (unless opnum (error "Unknown ppc opcode name ~s." name))
63    (values (gethash pname *ppc-unmacroexpanders*))))
64
65(defun (setf ppc-unmacroexpand-function) (def name)
66  (let* ((pname (string name))
[203]67         (opnum (gethash pname ppc::*ppc-opcode-numbers*)))
[6]68    (unless opnum (error "Unknown ppc opcode name ~s." name))
69    (setf (gethash pname *ppc-unmacroexpanders*) def)))
70
71(defmacro def-ppc-unmacroexpand (name insn-var lambda-list &body body)
72  `(setf (ppc-unmacroexpand-function ',name)
73         #'(lambda (,insn-var)
74             (destructuring-bind ,lambda-list (lap-instruction-parsed-operands ,insn-var)
75               ,@body))))
76
77(def-ppc-unmacroexpand stwu insn (rs d ra)
[1120]78  (case (backend-target-arch-name *ppc-disassembly-backend*)
79    (:ppc32
80     (if (and (= ra ppc::vsp) (= d -4))
81       `(vpush ,(ppc-gpr rs))))))
[6]82
[1120]83(def-ppc-unmacroexpand stdu insn (rs d ra)
84  (case (backend-target-arch-name *ppc-disassembly-backend*)
85    (:ppc64
86     (if (and (= ra ppc::vsp) (= d -8))
87       `(vpush ,(ppc-gpr rs))))))
88
[6]89(def-ppc-unmacroexpand rlwinm insn (rt ra b mb &optional (me mb me-p))
90  (if (not me-p)
91    (setq mb 0))                        ; That's what's happening now to fake operands.
92  (if (and (= me 31) (= (+ b mb) 32))
93    `(srwi ,(ppc-gpr rt) ,(ppc-gpr ra) ,mb)
94    (if (and (= mb 0) (= (+ b me) 31))
[1428]95      (if (and (case (backend-target-arch-name *ppc-disassembly-backend*)
96                 (:ppc32 t))
97             (logbitp rt ppc-node-regs)
98             (not (logbitp ra ppc-node-regs))
99             (= b (arch::target-fixnum-shift (backend-target-arch
100                                               *ppc-disassembly-backend*))))
101        `(box-fixnum ,(ppc-gpr rt) ,(ppc-gpr ra))
102        `(slwi ,(ppc-gpr rt) ,(ppc-gpr ra) ,b)))))
[6]103
[1428]104(def-ppc-unmacroexpand rldicr insn (rt ra sh me)
105  (if (= (+ sh me) 63)
106    (if (and (case (backend-target-arch-name *ppc-disassembly-backend*)
107               (:ppc64 t))
108             (logbitp rt ppc-node-regs)
109             (not (logbitp ra ppc-node-regs))
110             (= sh (arch::target-fixnum-shift (backend-target-arch
111                                               *ppc-disassembly-backend*))))
112      `(box-fixnum ,(ppc-gpr rt) ,(ppc-gpr ra))
113      `(sldi ,(ppc-gpr rt) ,(ppc-gpr ra) ,sh))))
114
[1843]115(def-ppc-unmacroexpand rldicl insn (rt ra sh mb)
116  (if (= (+ sh mb) 64)
117    `(srdi ,(ppc-gpr rt) ,(ppc-gpr ra) ,mb)))
118
[1428]119(def-ppc-unmacroexpand srawi insn (rt ra sh)
120  (if (and (case (backend-target-arch-name *ppc-disassembly-backend*)
121             (:ppc32 t))
122           (not (logbitp rt ppc-node-regs))
123           (logbitp ra ppc-node-regs)
124           (= sh (arch::target-fixnum-shift (backend-target-arch
125                                             *ppc-disassembly-backend*))))
126    `(unbox-fixnum ,(ppc-gpr rt) ,(ppc-gpr ra))))
127
128(def-ppc-unmacroexpand sradi insn (rt ra sh)
129  (if (and (case (backend-target-arch-name *ppc-disassembly-backend*)
130             (:ppc64 t))
131           (not (logbitp rt ppc-node-regs))
132           (logbitp ra ppc-node-regs)
133           (= sh (arch::target-fixnum-shift (backend-target-arch
134                                             *ppc-disassembly-backend*))))
135    `(unbox-fixnum ,(ppc-gpr rt) ,(ppc-gpr ra))))
136
[6]137(def-ppc-unmacroexpand li insn (rt imm)
[1120]138  (let* ((fixnumshift (arch::target-fixnum-shift (backend-target-arch *ppc-disassembly-backend*))))
139    (if (not (logtest (1- (ash 1 fixnumshift)) imm))
140      (if (logbitp rt ppc-node-regs)
141        `(li ,(ppc-gpr rt) ',(ash imm (- fixnumshift)))
142        (if (eql rt ppc::nargs)
143          `(set-nargs ,(ash imm (- fixnumshift))))))))
[6]144
145
146
147(def-ppc-unmacroexpand cmpwi insn (crf ra simm)
[1120]148  (let* ((fixnumshift (arch::target-fixnum-shift (backend-target-arch *ppc-disassembly-backend*))))
149    (if (and (not (logtest (1- (ash 1 fixnumshift)) simm))
150             (logbitp ra ppc-node-regs))
[6]151      `(cmpwi ,@(unless (eql 0 crf) `(,(aref *ppc-cr-names* (ash crf -2))))
152        ,(ppc-gpr ra)
[1120]153        ',(ash simm (- fixnumshift))))))
[6]154
[1120]155(def-ppc-unmacroexpand cmpdi insn (crf ra simm)
156  (let* ((fixnumshift (arch::target-fixnum-shift (backend-target-arch *ppc-disassembly-backend*))))
157    (if (and (not (logtest (1- (ash 1 fixnumshift)) simm))
158             (logbitp ra ppc-node-regs))
159      `(cmpdi ,@(unless (eql 0 crf) `(,(aref *ppc-cr-names* (ash crf -2))))
160        ,(ppc-gpr ra)
161        ',(ash simm (- fixnumshift))))))
162
[6]163(def-ppc-unmacroexpand addi insn (rd ra simm)
[1335]164  (let* ((fixnumshift (arch::target-fixnum-shift (backend-target-arch *ppc-disassembly-backend*)))
165         (disp-d (ppc-gpr rd))
[6]166         (disp-a (ppc-gpr ra)))
[203]167    (if (or (eql ra ppc::sp)
[1476]168            (eql ra ppc::tsp)
[203]169            (eql ra ppc::vsp))
[6]170        `(la ,disp-d ,simm ,disp-a)
171        (let* ((opcode 'addi)
172               (val (abs simm)))
173          (if (< simm 0)
174              (setq opcode 'subi))
[1335]175          (if (and (not (logtest (1- (ash 1 fixnumshift)) simm))
[6]176                   (logbitp rd ppc-node-regs)
177                   (logbitp ra ppc-node-regs))
[1335]178            `(,opcode ,disp-d ,disp-a ',(ash val (- fixnumshift)))
179            `(,opcode ,disp-d ,disp-a ,(if (eq val
180                                               (arch::target-nil-value (backend-target-arch *ppc-disassembly-backend*))) nil val)))))))
[6]181
182(defun ppc-unmacroexpand (insn)
[1750]183  (unless *ppc-disassemble-raw-instructions*
[4103]184    (let* ((expander (ppc-unmacroexpand-function (opcode-name (lap-instruction-opcode insn))))
[1750]185           (expansion (if expander (funcall expander insn))))
186      (when expansion
187        (setf (lap-instruction-opcode insn) (car expansion)
188              (lap-instruction-parsed-operands insn) (cdr expansion))
189        expansion))))
[6]190
191
192(defun find-ppc-opcode (i)
193  (let* ((op (ldb (byte 6 26) i))
[203]194         (k (svref ppc::*ppc-opcode-indices* op)))
[6]195    (declare (type (unsigned-byte 12) k)
196             (type (unsigned-byte 6) op))
197    (unless (= k -1)
[203]198      (dotimes (j (svref ppc::*ppc-opcode-counts* op))
[6]199        (declare (type (unsigned-byte 10) j))
[203]200        (let* ((code (svref ppc::*ppc-opcodes* (+ k j))))
[4103]201          (if (= (logand (opcode-mask code) i)
202                 (opcode-opcode code))
203            (if (dolist (op (opcode-operands code) t)
204                  (let* ((xfun (operand-extract-function op)))
[6]205                    (unless (or (null xfun)
206                                (funcall xfun i))
207                      (return nil))))
208              (return code))))))))
209
210(defun ppc-disasm-1 (i pc header)
211  (let* ((opcode (find-ppc-opcode i)))
212    (if (null opcode)
213      (error "Unknown PPC instruction : #x~8,'0x" i)    ; should handle somehow
214      (let* ((vals ()))
[4103]215        (dolist (operand (opcode-operands opcode))
216          (unless (logbitp operand-fake (operand-flags operand))
217            (let* ((extract-fn (operand-extract-function operand)))
[6]218              (push (if extract-fn
219                      (funcall extract-fn i)
[222]220                      (ppc::extract-default operand i))
[6]221                    vals))))
222        (let* ((insn (%make-lap-instruction opcode)))
223          (setf (lap-instruction-parsed-operands insn)
224                (nreverse vals))
225          (setf (lap-instruction-address insn)
226                pc)
227          (append-dll-node insn header))))))
228               
229
230(defvar *disassembled-ppc-instructions* ())
231(defvar *disassembled-ppc-labels* ())
232
233
234
235(defun ppc-label-at-address (address)
236  (dolist (l *disassembled-ppc-labels* 
237             (let* ((label (%make-lap-label (intern (format nil "L~d" address)))))
238               (setf (lap-label-address label) address)
239               (push label *disassembled-ppc-labels*)
240               label))
241    (when (= address (lap-label-address l))
242      (return l))))
243
244(defun insert-ppc-label (l instructions)
245  (let* ((labaddr (lap-label-address l)))
246   (do-dll-nodes (insn instructions (append-dll-node l instructions))
[14382]247     (when (>= (instruction-element-address insn) labaddr)
248       (return (insert-dll-node-after l (instruction-element-pred insn)))))))
[6]249
250(defun ppc-disassemble-cr (val operand-spec)
251  (declare (type (mod 32) val))
[4103]252  (let* ((width (operand-width operand-spec))
[6]253         (crnum (ash val -2))
254         (ccnum (logand val 3)))
255    (declare (fixnum width crnum ccnum))
256    (if (= width 3)
257      (unless (= crnum 0) (aref *ppc-cr-names* crnum))
258      (if (= ccnum 0)
259        (unless (= crnum 0) (aref *ppc-cr-names* crnum))
260        (list (aref *ppc-cr-field-names* crnum) (aref *ppc-cc-bit-names* ccnum))))))
261
262(defun ppc-analyze-operands (instructions constants)
263  (let* ((pc 0)
[1335]264         (regsave-pseudo nil)
265         (arch (backend-target-arch *ppc-disassembly-backend*))
266         (nil-value (arch::target-nil-value arch))
267         (misc-data-offset (arch::target-misc-data-offset arch))
268         (word-shift (arch::target-word-shift arch))
269         (align-mask (1- (ash 1 word-shift))))
[6]270    (declare (fixnum pc))
271    (let* ((last (dll-header-last instructions)))
272      (when (eq (lap-instruction-opcode last) *ppc-lwz-instruction*)
273        (remove-dll-node last)
274        (setq regsave-pseudo last)))
275    (do-dll-nodes (insn instructions)
276      (unless (ppc-unmacroexpand insn)
277        (let* ((opcode (lap-instruction-opcode insn))
278               (opvalues (lap-instruction-parsed-operands insn)))
[4103]279          (do* ((operands (opcode-operands opcode) (cdr operands))
[6]280                (operand (car operands) (car operands))
281                (header (cons nil opvalues))
282                (tail header))
283               ((null operands) (setf (lap-instruction-parsed-operands insn) (cdr header)))
284            (declare (dynamic-extent header))
[4103]285            (let* ((flags (operand-flags operand))
286                   (opidx (operand-index operand))
[6]287                   (val (cadr tail)))
288              (declare (fixnum flags))
[4103]289              (if (and (logbitp operand-optional flags)
[6]290                       (eql 0 val))
291                (rplacd tail (cddr tail))
292                (progn
[203]293                  (if (and (or (eq opidx ppc::$si)
294                               (eq opidx ppc::$nsi)
295                               (eq opidx ppc::$ui))
[1335]296                           (eql val nil-value))
[6]297                    (setf (cadr tail) nil)
[203]298                    (if (logbitp ppc::$ppc-operand-relative flags)
[6]299                      (let* ((label (ppc-label-at-address (+ pc val))))
300                        (setf (cadr tail) (lap-label-name label)))
[203]301                      (if (logbitp ppc::$ppc-operand-cr flags)
[6]302                        (let* ((cr (ppc-disassemble-cr val operand)))
303                          (when cr (setf (cadr tail) cr)))
[203]304                        (if (logbitp ppc::$ppc-operand-absolute flags)
[2761]305                          (let* ((info (find val ppc::*ppc-subprims* :key #'subprimitive-info-offset)))
[6]306                            (when info (setf (cadr tail) (subprimitive-info-name info))))
[203]307                          (if (logbitp ppc::$ppc-operand-fpr flags)
[6]308                            (setf (cadr tail) (ppc-fpr val))
[203]309                            (if (logbitp ppc::$ppc-operand-vr flags) ; SVS
[6]310                              (setf (cadr tail) (ppc-vr val))
[203]311                              (when (logbitp ppc::$ppc-operand-gpr flags)
[6]312                                (setf (cadr tail) (ppc-gpr val))
[203]313                                (when (eq val ppc::fn)
[6]314                                  (let* ((disp (car tail)))
315                                    (when (and disp (typep disp 'fixnum))
[1335]316                                      (let* ((unscaled (+ (- misc-data-offset) disp)))
317                                        (unless (logtest align-mask unscaled)
318                                          (let* ((idx (ash unscaled (- word-shift))))
[6]319                                            (if (< idx (uvsize constants))
320                                              (rplaca tail (list 'quote (uvref constants idx)))))))))))))))))
321                  (setq tail (cdr tail))))))))
322      (incf pc 4))
323    (dolist (l *disassembled-ppc-labels*) (insert-ppc-label l instructions))
324    (when regsave-pseudo
325      (destructuring-bind (reg offset pc) (lap-instruction-parsed-operands regsave-pseudo)
326        (declare (fixnum reg offset pc))
327        (let* ((nregs (- 32 reg)))
328          (declare (fixnum nregs))
329          (setq pc (ash (the fixnum (dpb (ldb (byte 2 0) offset) (byte 2 5) pc)) 2)
[1750]330                offset (- (logand (lognot 3) (- offset)) (ash nregs target::word-shift))))
[6]331        (setf (lap-instruction-opcode regsave-pseudo) :regsave
332              (lap-instruction-parsed-operands regsave-pseudo)
333              (list (ppc-gpr reg) offset)
334              (lap-instruction-address regsave-pseudo) pc)
335        (do-dll-nodes (node instructions)
336          (when (>= (lap-instruction-address node) pc)
337            (insert-dll-node-after regsave-pseudo (dll-node-pred node))
338            (return)))))))
339             
340     
341; This returns a doubly-linked list of INSTRUCTION-ELEMENTs; the caller (disassemble, INSPECT)
342; can format the contents however it wants.
[1120]343(defun disassemble-ppc-function (code-vector constants-vector &optional (start-word 0))
344  (let* ((*disassembled-ppc-labels* nil)
345         (header (make-dll-header)))
[6]346    (let* ((n (uvsize code-vector)))
347      (declare (fixnum n))
[1120]348      (do* ((i start-word (1+ i))
[6]349            (pc 0 (+ pc 4)))
350           ((= i n))
351        (declare (fixnum i))
352        (let* ((opcode (uvref code-vector i)))
353          (declare (integer opcode))
354          (if (= opcode 0)
355            (return)
356            (ppc-disasm-1 opcode pc header))))
[1120]357      (ppc-analyze-operands header constants-vector))
358    header))
[6]359
360(defun print-ppc-instruction (stream tabcount opcode parsed-operands)
[4103]361  (let* ((name (if (symbolp opcode) opcode (opcode-name opcode))))
[6]362    (if (keywordp name)
363      (format stream "~&~V,t(~s" tabcount name)
364      (format stream "~&~V,t(~a" tabcount name))
365    (dolist (op parsed-operands (format stream ")"))
366      (format stream (if (and (consp op) (eq (car op) 'quote)) " ~s" " ~a") op))))
367
[11373]368(defun print-ppc-instructions (stream function instructions &optional for-lap backend)
[1120]369  (declare (ignorable backend))
[11373]370  (let* ((tab (if for-lap 6 2))
371         (previous-source-note nil))
372
373    (let ((source-note (function-source-note function)))
374      (when source-note
375        (format t ";; Source: ~S:~D-~D"
376                (source-note-filename source-note)
377                (source-note-start-pos source-note)
378                (source-note-end-pos source-note))
379        ;; Fetch text from file if don't already have it
380        (ensure-source-note-text source-note)))
381
[6]382    (when for-lap 
383      (let* ((lap-function-name (car for-lap)))
384        (format stream "~&(~S ~S ~&  (~S (~s) ~&    (~s ~s ()" 
385                'nfunction lap-function-name 'lambda '&lap 'ppc-lap-function lap-function-name)))
[11373]386
[6]387    (do-dll-nodes (i instructions)
[11373]388      (let ((source-note (find-source-note-at-pc function (instruction-element-address i))))
389        (unless (eql (source-note-file-range source-note)
390                     (source-note-file-range previous-source-note))
391          (setf previous-source-note source-note)
392          (let* ((source-text (source-note-text source-note))
393                 (text (if source-text
394                         (string-sans-most-whitespace source-text 100)
395                         "#<no source text>")))
396            (format stream "~&~%;;; ~A" text))))
[6]397      (etypecase i
398        (lap-label (format stream "~&~a " (lap-label-name i)))
399        (lap-instruction 
400         (print-ppc-instruction stream tab (lap-instruction-opcode i) (lap-instruction-parsed-operands i)))))
401    (when for-lap (format stream ")))~&"))))
402
403
[1750]404(defun ppc-Xdisassemble (fn-vector &key (for-lap nil) (stream *standard-output*) target ((:raw *ppc-disassemble-raw-instructions*) nil))
[1120]405  (let* ((backend (if target (find-backend target) *host-backend*))
406         (prefix-length (length (arch::target-code-vector-prefix (backend-target-arch backend))))
407         (*ppc-disassembly-backend* backend))
[11373]408    (print-ppc-instructions stream fn-vector
409                            (function-to-dll-header fn-vector prefix-length)
[1120]410                            (if for-lap (list (uvref fn-vector (- (uvsize fn-vector) 2)))))
411    (values)))
[6]412
[1120]413(defun function-to-dll-header (fn-vector &optional (prefix #+ppc32-target 0 #+ppc64-target 1))
[6]414  (let* ((codev (uvref fn-vector 0)))
[1120]415    (disassemble-ppc-function codev fn-vector prefix)))
[6]416
417
418(defun disassemble-list (thing)
419  (let ((dll (function-to-dll-header (function-for-disassembly thing)))
420        (address 0)
421        (label-p nil)
422        (res nil))
423    (do-dll-nodes (i dll)
424      (setq address (instruction-element-address i))
425      (etypecase i
426        (lap-label
427         (setq label-p (lap-label-name i)))
428        (lap-instruction
429         (let ((opcode (lap-instruction-opcode i))
430               (operands (lap-instruction-parsed-operands i)))
431           (push (list* (if label-p `(label ,address) address)
[4103]432                        (if (symbolp opcode) opcode (opcode-name opcode))
[6]433                        operands)
434                 res)
435           (setq label-p nil)))))
436    (nreverse res)))
437
[12846]438(defun disassemble-lines (thing)
439  (let ((dll (function-to-dll-header (function-for-disassembly thing)))
440        (address 0)
441        (label-p nil)
442        (lines (make-array 20 :adjustable t :fill-pointer 0)))
443    (do-dll-nodes (i dll)
444      (setq address (instruction-element-address i))
445      (etypecase i
446        (lap-label
447         (setq label-p (lap-label-name i)))
448        (lap-instruction
449         (let* ((opcode (lap-instruction-opcode i))
450                (operands (lap-instruction-parsed-operands i))
451                (imms (loop for op in operands
452                         when (and (consp op)
453                                   (consp (cdr op))
454                                   (null (cddr op))
455                                   (or (eq (%car op) 'quote) (eq (%car op) 'function)))
456                         collect op)))
457           (vector-push-extend (list (if (cdr imms) (coerce imms 'vector) (car imms))
458                                     (if label-p `(:label address) address)
459                                     (with-output-to-string (s)
460                                       (format s "(~a" (if (symbolp opcode) opcode (opcode-name opcode)))
461                                       (loop for op in operands
462                                          do (princ " " s)
463                                          do (disasm-prin1 op s))
464                                       (format s ")")))
465                               lines)
466           (setq label-p nil)))))
467    lines))
468
[6]469#+ppc-target
470(defun disasm-prin1 (thing stream)
471  (if (and (consp thing) (consp (cdr thing)) (null (cddr thing)))
472    (cond ((eq (%car thing) 'quote)
473           (prin1 thing stream))
474          ((eq (%car thing) 'function)
475           (format stream "#'~S" (cadr thing)))
476          ((eq (%car thing) 16)
477             (format stream "#x~X" (cadr thing)))
478          ((eq (%car thing) 'label)
479           (let ((*print-radix* nil))
480             (princ (cadr thing) stream)))
481          (t (princ thing stream)))
482    (princ thing stream)))
483
484
Note: See TracBrowser for help on using the repository browser.