;;; -*- Mode:LISP; Package:LAMBDA; Base:8; Readtable:CL -*- ;;; There are several kludges in this file among which are: ;;; ;;; The microtracer is run in a different stack group so as to avoid overflowing ;;; the pdl which cannot be extended because of a microcode bug. ;;; ;;; Some internal procedures have been externalized because there are only ;;; 64 locals in any procedure. ;;; ;;; Kludges may be found by looking for flaming comments. ;;; The microtracer is very recursive and tends to overflow the pdl. ;;; Since the code that extends the pdl seems to be broken, we make a stack ;;; group with a large regular pdl explicitly for the microtracer. ;;; A kludge is born. (defvar *microtracer-stack-group* (make-stack-group "Microtracer" :regular-pdl-size 1000000 :special-pdl-size 10000)) (defvar *trace-m-mem-only?* nil "If T, only consider m-mem in tracing. If NIL, consider a-mem, too.") (defvar *interactive-tracing?* '() "If T, break on trace errors. Overrides verbose tracing.") (defvar *verbose-tracing?* '() "If T, print trace errors as they occur. No effect if interactive tracing is on.") (defvar *break-on-registers-sourced-set* (make-empty-set *register-set-universe*) "The micro-tracer will break upon finding an instruction that sources a register in this set.") (defvar *current-subroutine* '() "Holds the name of the subroutine currently being traced.") (defvar *subroutine-trace-chain* '() "Holds the list of subroutines that are being traced.") (defvar *name-trace-chain* '() "Holds the list of labels seen while tracing. Useful for debugging the tracer.") ;;; Stack group stuff. ;;; Experimental use of heap closures here. (defun start-tracer (terminal-io thunk) ;; Note that the thunk here is from a different stack group and therefore ;; will not lose when we use heap closures. (using-heap-closures (pkg-goto 'lambda) (stack-group-return (catch-error-restart ((sys:abort error) "Exit the microtracer.") (funcall thunk))))) (defun within-tracer (thunk) ;; (funcall thunk) (stack-group-preset *microtracer-stack-group* #'start-tracer terminal-io thunk) (funcall *microtracer-stack-group*)) ;;; A subroutine is essentially a single instruction as ;;; far as the tracer is concerned. However, it is an error ;;; to depend on anything that the subroutine smashed ;;; except for values and if you are restoring something ;;; that was saved. Therefore, when we trace a subroutine ;;; we wish to get back sources, clobbereds, and new save info. (defun trace-the-microcode () (labels ( (trace-list (name list) (format t "~%~S" name) (dolist (routine list) (format t "~% ~S" (instruction-address routine)) (within-tracer #'(lambda () (trace-subroutine routine))))) ) (trace-list "Subroutines:" *subroutine-entries*) (trace-list "Miscellaneous:" *instructions-without-predecessors*))) (defun i-info (symbol) "Returns the trace info for instruction at " (let ((instruction (instruction-at (i-mem-symbol->address symbol)))) (values (instruction-trace-warnings instruction) (instruction-trace-info instruction)))) (defun i-trace (symbol) "Starts tracing from instruction at ." (within-tracer #'(lambda () (trace-subroutine (instruction-at (i-mem-symbol->address symbol)))))) (defun reinit-trace () (for-each-instruction #'(lambda (instruction ignore-address) ignore-address (setf (instruction-trace-info instruction) '() (instruction-trace-warnings instruction) '() (instruction-calling-subroutine instruction) '())))) (defun find-who-depends-on (&rest register-names) (find-subroutines-which-depend-on-non-args (list->set register-names))) (defun find-subroutines-which-depend-on-non-args (registers-to-look-for) (dolist (subroutine *subroutine-entries*) (let ((subroutine-sources (elt (instruction-trace-info subroutine) 0))) (when (and subroutine-sources (not (empty-set? (set-diff (set-intersection registers-to-look-for subroutine-sources) (spread-declarations subroutine 'args))))) (format t "~%~S" (instruction-address subroutine))))) (format t "~%Th-th-th-that's all folks!")) ;;; How trace warnings are made. (defsubst make-trace-warning (format-string args) (cons format-string args)) (defun output-trace-info (filename) (let ((output-stream (open filename :direction :output))) (for-each-instruction #'(lambda (instruction ignore-address) ignore-address (display-trace-warnings instruction output-stream))))) (defun display-trace-warnings (instruction &optional (stream terminal-io)) (let ((warnings (instruction-trace-warnings instruction))) (when warnings (format stream "~&~S: " (instruction-address instruction)) (dolist (warning warnings) (apply #'format stream warning))))) (defun inform (instruction format-string &rest format-args) (setq format-args (copylist format-args)) ;; &rest lossage (let ((trace-warning (make-trace-warning format-string format-args))) (pushnew trace-warning (instruction-trace-warnings instruction) :test #'alike?) (if *interactive-tracing?* (apply #'break `(,format-string ,@format-args)) (when *verbose-tracing?* (format t "~&~S: ~A" (instruction-address instruction) (apply #'format `(nil ,format-string ,@format-args))))))) (defun instruction-path-name (path) (cond ((symbol? path) path) ((instruction? path) (instruction-address path)) ('else (list (first path) (instruction-address (instruction-xfered-to path)))))) ;;; Things that are "interesting" (defun add-register-breaks (&rest registers) (setq *break-on-registers-sourced-set* (set-union *break-on-registers-sourced-set* (list->set registers)))) (defun remove-register-breaks (&rest registers) (setq *break-on-registers-sourced-set* (if (null? registers) (make-empty-set) (set-diff *break-on-registers-sourced-set* (list->set registers))))) (defun maybe-break (registers) (let ((intersection (set-intersection registers *break-on-registers-sourced-set*))) (when (not (empty-set? intersection)) (break "Souced registers ~S at ~S via ~S." intersection *current-subroutine* *name-trace-chain*)) registers)) (defun interesting-registers (register-set) (if *trace-m-mem-only?* (set-intersection register-set *m-mem-registers-set*) register-set)) (defun excluding-register-sets (&rest register-sets) (let ((total-to-exclude-set (apply #'set-union register-sets))) #'(lambda (another-set) (set-diff another-set total-to-exclude-set)))) (defun interesting-sources (register-set) (funcall (compose #'interesting-registers (excluding-register-sets *global-registers-set* *constant-registers-set*) #'maybe-break #'instruction-sources) register-set)) (defun interesting-destination (register-set) (funcall (compose #'interesting-registers (excluding-register-sets *global-registers-set*) #'instruction-destination) register-set)) ;;; Saves and restores (u-neq *loop-tag*) (u-neq *recur-tag*) (u-neq *return-tag*) (u-neq *suspend-tag*) (defstruct (saves-record (:constructor make-saves-record (from into)) (:callable-accessors t)) from into) (defsynonym saves-record? saves-record-p) (defalike #'saves-record? #'(lambda (saves-record1 saves-record2) (and (alike? (saves-record-from saves-record1) (saves-record-from saves-record2)) (alike? (saves-record-into saves-record1) (saves-record-into saves-record2))))) (defstruct (restores-record (:constructor make-restores-record (saves)) (:callable-accessors t)) saves (smashed-later? '())) (defsynonym restores-record? restores-record-p) (defalike #'restores-record? #'(lambda (restores-record1 restores-record2) (and (alike? (restores-record-saves restores-record1) (restores-record-saves restores-record2)) (alike? (restores-record-smashed-later? restores-record1) (restores-record-smashed-later? restores-record2))))) (defsynonym exit-reason-identifier car) (defsynonym entries cdr) (defsetf entries rplacd) (defun make-looping-record-maker (tag) #'(lambda (instruction) (cons (cons tag instruction) nil))) (defun make-looping-record-predicate (tag) #'(lambda (exit-reason) (and (pair? exit-reason) (pair? (car exit-reason)) (eq? (car (car exit-reason)) tag)))) (deff make-loop-record (make-looping-record-maker *loop-tag*)) (deff make-recur-record (make-looping-record-maker *recur-tag*)) (deff loop-exit-reason? (make-looping-record-predicate *loop-tag*)) (deff recur-exit-reason? (make-looping-record-predicate *recur-tag*)) (defun path-only-loops? (exit-reason) (and (null? (cdr exit-reason)) (loop-exit-reason? (car exit-reason)))) (defun possible-new-trace-chain (instruction) (let ((possible-label (instruction-address instruction))) (if (symbol? possible-label) (cons possible-label *name-trace-chain*) *name-trace-chain*))) ;(defvar *things-to-avoid* '() ; "Holds list of symbols that must not be reached.") (defun path-suspends? (exit-reasons) (assoc *suspend-tag* exit-reasons)) (defun quit-tracing (instruction exit-reason) (values instruction (make-empty-set) (make-empty-set) (make-empty-set) '() '() (list exit-reason))) (defun trace-subroutine (entry-point) "Values: arguments (non-local-sources), values, side-effects, saves, restores" (let ((*current-subroutine* entry-point) (*subroutine-trace-chain* (cons entry-point *subroutine-trace-chain*))) (multiple-value-bind (args values clobbers locals) (spread-declarations entry-point 'args 'values 'clobbers 'local) (multiple-value-bind (ignore-entry-point sources maybe-clobbered really-clobbered saves restores exit-reasons) (trace-path entry-point) ignore-entry-point (labels ( (discrepancy-check (set-to-check report-format-string) (unless (empty-set? set-to-check) (inform entry-point report-format-string set-to-check))) (check-for-undeclared-sources () (discrepancy-check (set-diff sources (set-union args locals)) "~&Sources ~S are not args or locals.")) (check-for-undeclared-clobbers () (discrepancy-check (set-diff maybe-clobbered (set-union values clobbers locals)) "~&Clobbers ~S are not values, clobbers or locals.")) (check-for-unused-args () (discrepancy-check (set-diff args sources) "~&Declares unused args ~S.")) (check-for-unreturned-values () (let ((never-clobbers (set-diff values (set-union clobbers maybe-clobbered))) (not-always-clobbers (set-diff values (set-union clobbers really-clobbered)))) (discrepancy-check not-always-clobbers "~&May not clobber return value ~S.") (discrepancy-check never-clobbers "~&Never clobbers return value ~S."))) ) (check-for-undeclared-sources) (check-for-undeclared-clobbers) (check-for-unused-args) (check-for-unreturned-values) (values (set-diff sources locals) ;; Anything but locals. values (set-diff maybe-clobbered (set-union locals values)) (set-diff really-clobbered (set-union locals values)) saves restores exit-reasons)))))) (defun trace-path (instruction) "Values: sources, maybe-clobbers, always-clobbers, saves, restores, exit-reasons" (cond ((null? instruction) (ferror nil "Missing instruction")) ((already-seen? instruction) (values-list (cons instruction (instruction-trace-info instruction)))) ((looping? instruction) (quit-tracing instruction (make-loop-record instruction))) ((recurring? instruction) (inform instruction "~&Recursively entered.") (quit-tracing instruction (make-recur-record instruction))) ('else (let ((*name-trace-chain* (possible-new-trace-chain instruction))) (append-instruction instruction #'(lambda () (trace-successors instruction))))))) (defun already-seen? (instruction) ;;(not (null? (instruction-trace-info instruction)) ;; )) (defun looping? (instruction) (eq? (instruction-calling-subroutine instruction) *current-subroutine*)) (defun recurring? (instruction) (member (instruction-calling-subroutine instruction) *subroutine-trace-chain*)) (defun combine-sequences (beginning-of-first-sequence beginning-of-later-sequence first-sources first-values first-maybe-clobbers first-clobbers first-saves first-restores later-sources later-maybe-clobbers later-clobbers later-saves later-restores) (let ((path-name (if beginning-of-later-sequence (instruction-path-name beginning-of-later-sequence) 'UNSPECIFIED))) (labels ( (maybe-complain (string set) (when (not (empty-set? set)) (inform beginning-of-first-sequence (string-append "~&Path ~S " string) path-name set))) (check-smashed-needed-value () (let ((needed-but-smashed (set-intersection later-sources (apply #'set-union (map 'list #'set-later-may-restore (set->list-of-subsets (set-diff first-maybe-clobbers first-values))))))) (maybe-complain "depends on clobbered values ~S" needed-but-smashed))) (check-loses-result () (let ((always-lost (set-intersection first-values (set-diff later-clobbers later-sources))) (maybe-lost (set-intersection first-values (set-diff later-maybe-clobbers later-clobbers later-sources)))) (maybe-complain "may clobber unseen results ~S" maybe-lost) (maybe-complain "always clobbers unseen results ~S" always-lost))) (combine-sequence-sources () (set-union first-sources (set-diff later-sources first-values))) (see-if-first-saves (register) (not (null? (car (member register first-saves :test #'alike? :key #'saves-record-from))))) (set-later-may-restore (register) (let ((restore-record (car (member register later-restores :test #'alike? :key (compose #'saves-record-into #'restores-record-saves))))) (if (or (null? restore-record) (restores-record-smashed-later? restore-record)) register (make-empty-set)))) (combine-clobbers-set (first-set later-set) (let ((new-clobbers-set (make-empty-set))) (for-single-element-subsets first-set #'(lambda (clobber) (setq new-clobbers-set (set-union new-clobbers-set (set-later-may-restore clobber))))) (for-single-element-subsets later-set #'(lambda (clobber) (when (not (see-if-first-saves clobber)) (setq new-clobbers-set (set-union new-clobbers-set clobber))))) new-clobbers-set)) (combine-saves () (union later-saves (set-difference first-saves later-restores :test #'(lambda (save-record restore-record) (alike? save-record (restores-record-saves restore-record)))) :test #'alike?)) (combine-restores () (dolist (first-restore first-restores) (when (not (null? (set-intersection later-maybe-clobbers (saves-record-from (restores-record-saves first-restore))))) (setf (restores-record-smashed-later? first-restore) t))) (union first-restores (set-difference later-restores first-saves :test #'(lambda (restore-record save-record) (alike? save-record (restores-record-saves restore-record)))) :test #'alike? :key #'restores-record-saves)) ) (check-smashed-needed-value) (check-loses-result) (values (combine-sequence-sources) (combine-clobbers-set first-maybe-clobbers later-maybe-clobbers) (combine-clobbers-set first-clobbers later-clobbers) (combine-saves) (combine-restores))))) (defun append-instruction (instruction delayed-tail) (if (suspend-flow-tracing? instruction) (quit-tracing instruction (list *suspend-tag*)) (setf (instruction-calling-subroutine instruction) *current-subroutine*) (multiple-value-bind (tail-begin after-me-sources after-me-maybe-clobbers after-me-clobbers after-me-saves after-me-restores exit-reasons) (funcall delayed-tail) (if (path-suspends? exit-reasons) (quit-tracing instruction (list *suspend-tag*)) (let* ((my-sources (interesting-sources instruction)) (my-clobbers (interesting-destination instruction)) (do-i-save? (instruction-saves? instruction)) (do-i-restore? (instruction-restores? instruction)) (my-depends (if do-i-save? (make-empty-set) my-sources)) (my-values (if do-i-restore? (make-empty-set) my-clobbers)) (saves-record (make-saves-record my-sources my-clobbers)) (multiple-entry? (not (null? (cdr (instruction-predecessors instruction))))) (new-exit-reasons '())) (multiple-value-bind (combined-sources combined-maybe-clobbers combined-clobbers combined-saves combined-restores) (combine-sequences instruction tail-begin my-depends ;sources if not saving. my-values ;values my-values ;maybe-clobbers my-values ;always-clobbers (when do-i-save? (list saves-record)) (when do-i-restore? (list (make-restores-record saves-record))) after-me-sources after-me-maybe-clobbers after-me-clobbers after-me-saves after-me-restores) (labels ( (close-loops () (let ((l-ident (exit-reason-identifier (make-loop-record instruction))) (r-ident (exit-reason-identifier (make-recur-record instruction)))) (dolist (exit-reason exit-reasons) (cond ((loop-exit-reason? exit-reason) (close-test l-ident exit-reason #'merge-loop-entry)) ((recur-exit-reason? exit-reason) (close-test r-ident exit-reason #'merge-loop-entry)) ;; like loop for now. ('else (push exit-reason new-exit-reasons)))) new-exit-reasons)) (close-test (ident exit-reason merger) (if (not (alike? ident (exit-reason-identifier exit-reason))) (progn (when multiple-entry? (push instruction (entries exit-reason))) (push exit-reason new-exit-reasons)) (dolist (entry (entries exit-reason)) (funcall merger entry)))) (merge-loop-entry (loop-entry) (multiple-value-bind (entry-sources entry-maybe-clobbers entry-clobbers entry-saves entry-restores ignore-exit-reasons) (values-list (instruction-trace-info loop-entry)) ignore-exit-reasons (multiple-value-bind (loop-sources loop-maybe-clobbers loop-clobbers loop-saves loop-restores) (combine-sequences loop-entry instruction entry-sources entry-clobbers ;;values of the loop entry-maybe-clobbers entry-clobbers entry-saves entry-restores combined-sources combined-maybe-clobbers combined-clobbers combined-saves combined-restores) (setf (instruction-trace-info loop-entry) (list loop-sources loop-maybe-clobbers loop-clobbers loop-saves loop-restores))))) ) (let ((trace-info (list combined-sources combined-maybe-clobbers combined-clobbers combined-saves combined-restores (close-loops)))) (setf (instruction-trace-info instruction) trace-info) (values-list (cons instruction trace-info)))))))))) ;;; Moby compiler lossage: only 64 locals! ;;; We kludge by bringing internal procs outside. (defun kludge-merge-sources (receiver sources new-sources) (funcall receiver (set-union sources new-sources))) (defun kludge-merge-maybe-clobbers (receiver maybe-clobbers new-maybe-clobbers) (funcall receiver (set-union maybe-clobbers new-maybe-clobbers))) (defun kludge-handle-xct-next (merge-parallel-path successor tail-computer) (let ((xct-next (instruction-xct-along-the-way successor))) (multiple-value-call merge-parallel-path (if (null? xct-next) (funcall tail-computer) (append-instruction xct-next tail-computer))))) (defun trace-successors (instruction) (let ((successor 'none) (sources (make-set)) (maybe-clobbers (make-set)) (clobbers (make-set)) (saves '()) (restores '()) (exit-reasons '())) (labels ( ; (merge-sources (new-sources) ; (setq sources (set-union new-sources sources))) ; (merge-maybe-clobbers (new-maybe-clobbers) ; (setq maybe-clobbers (set-union new-maybe-clobbers maybe-clobbers))) (merge-clobbers (new-clobbers) (let ((always-clobbers (set-intersection new-clobbers clobbers)) (might-clobber (set-xor new-clobbers clobbers))) (setq maybe-clobbers (set-union might-clobber maybe-clobbers)) (setq clobbers always-clobbers))) (merge-saves (new-saves) (when (not (null? (set-exclusive-or new-saves saves :test #'alike?))) (inform instruction "~&Saves do not match on successor paths.") (setq saves (intersection saves new-saves)))) (merge-restores (new-restores) (when (not (null? (set-exclusive-or new-restores restores :test #'alike?))) (inform instruction "~&Restores do not match on successor paths.") (setq restores (intersection restores new-restores)))) (merge-parallel-path (a-successor more-sources more-maybe-clobbers more-clobbers more-saves more-restores exit-reason) (unless (path-suspends? exit-reason) (if (or (eq? successor 'none) (path-only-loops? exit-reasons)) (setq successor a-successor sources more-sources maybe-clobbers more-maybe-clobbers clobbers more-clobbers saves more-saves restores more-restores exit-reasons exit-reason) (setq successor '()) (unless (path-only-loops? exit-reason) (kludge-merge-sources #'(lambda (merged-sources) (setq sources merged-sources)) sources more-sources) (kludge-merge-maybe-clobbers #'(lambda (merged-maybe-clobbers) (setq maybe-clobbers merged-maybe-clobbers)) maybe-clobbers more-maybe-clobbers) ; (merge-sources more-sources) ; (merge-maybe-clobbers more-maybe-clobbers) (merge-clobbers more-clobbers) (merge-saves more-saves) (merge-restores more-restores) (setq exit-reasons (union exit-reason exit-reasons :test #'alike?)))))) ; (handle-xct-next (successor tail-computer) ; (let ((xct-next (instruction-xct-along-the-way successor))) ; (multiple-value-call ; #'merge-parallel-path ; (if (null? xct-next) ; (funcall tail-computer) ; (append-instruction xct-next tail-computer))))) ) (let ((successors (instruction-successors instruction))) (dolist (successor successors) (if (instruction? successor) (multiple-value-call #'merge-parallel-path (trace-path successor)) (kludge-handle-xct-next #'merge-parallel-path successor #'(lambda () (funcall (case (first successor) (calls #'trace-call-successor) (jump-xct-next #'trace-jump-xct-next-successor) (returns #'trace-returns-successor)) successor))))))) (if (null? exit-reasons) (quit-tracing instruction (list *suspend-tag*)) (values successor sources maybe-clobbers clobbers saves restores exit-reasons)))) (defun trace-call-successor (successor) (let ((subroutine (instruction-xfered-to successor)) (rest-of-path (instruction-returned-to successor))) (multiple-value-bind (sources values maybe-clobbers clobbers saves restores exit-reasons) (trace-subroutine subroutine) (if (path-suspends? exit-reasons) (quit-tracing successor (list *suspend-tag*)) (multiple-value-bind (ignore-tail-head tail-sources tail-maybe-clobbers tail-clobbers tail-saves tail-restores tail-exit-reasons) (trace-path rest-of-path) ignore-tail-head (values-values successor (combine-sequences subroutine rest-of-path sources values maybe-clobbers clobbers saves restores tail-sources tail-maybe-clobbers tail-clobbers tail-saves tail-restores) tail-exit-reasons)))))) (defun trace-jump-xct-next-successor (successor) (trace-path (instruction-xfered-to successor))) (defun trace-returns-successor (ignore-successor) ignore-successor (quit-tracing '() (list *return-tag*)))