Created
March 4, 2012 04:03
-
-
Save kanru/1970622 to your computer and use it in GitHub Desktop.
shake-lisp-and-die
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;;; THIS IS A PROOF OF CONCEPT. This has been "released" in case someone | |
;;; actually wants to develop it into a usable tool. Don't even think | |
;;; about using this version for any sort of production use. | |
;;; Author: Juho Snellman | |
;;; http://jsnell.iki.fi/blog/archive/2005-07-06.html | |
(defparameter *retain* (make-hash-table :test 'eq)) | |
(defparameter *ignore* (make-hash-table :test 'eq)) | |
(defparameter *classes* nil) | |
(defparameter *generic-functions* nil) | |
(defparameter *stream* (make-broadcast-stream)) | |
(defparameter *objects* nil) | |
(defmacro ignore-functions (&rest symbols) | |
`(dolist (symbol ',symbols) | |
(setf (gethash (fdefinition symbol) *ignore*) t))) | |
(defmacro ignore-variables (&rest symbols) | |
`(dolist (symbol ',symbols) | |
(setf (gethash symbol *ignore*) t))) | |
(defun keep (object) | |
(setf (gethash object *retain*) t)) | |
(defun kept-p (object) | |
(or (gethash object *retain*) | |
(gethash object *ignore*))) | |
(defun ignored-p (object) | |
(gethash object *ignore*)) | |
;; compiler | |
(ignore-functions eval | |
sb-impl::%defun) | |
(keep 'sb-c::*cached-info-environment*) | |
(keep 'sb-c::*info-environment*) | |
(keep 'sb-c::*info-cache-vector*) | |
;; disassembler | |
(ignore-variables sb-disassem::*disassem-inst-formats* | |
sb-disassem::*disassem-arg-types* | |
sb-disassem::*disassem-insts* | |
sb-disassem::*disassem-fun-cache*) | |
;; debugger | |
(progn | |
(ignore-functions invoke-debugger cerror | |
sb-kernel:find-caller-name-and-frame) | |
(ignore-variables sb-impl::*handler-clusters* | |
sb-impl::*invoke-debugger-hook* | |
sb-debug::*debug-loop-fun* | |
sb-di::*compiled-debug-funs* | |
sb-impl::*debugger-hook*)) | |
;; tracing | |
(ignore-functions sb-debug::trace-redefined-update) | |
;; pcl | |
(ignore-functions sb-pcl::get-new-fun-generator) | |
(ignore-variables sb-pcl::*mf1* sb-pcl::*mf1p* | |
sb-pcl::*mf1cp* sb-pcl::*mf2* | |
sb-pcl::*effective-method-cache* | |
sb-pcl::*method-function-plist* | |
sb-pcl::*find-class* | |
sb-pcl::*pv-table-cache-update-info* | |
sb-pcl::*name->class->slotd-table* | |
sb-pcl::*mf2p* sb-pcl::*mf2cp*) | |
(keep #'sb-pcl::generating-lisp) | |
(keep 'sb-pcl::*pv-key-to-pv-table-table*) | |
(keep 'sb-pcl::*slot-name-lists-outer*) | |
(keep 'sb-pcl::*slot-name-lists-inner*) | |
(keep 'sb-pcl::*all-pv-table-list*) | |
(keep 'sb-pcl::*eql-specializer-table*) | |
(keep 'sb-pcl::*eql-specializer-methods*) | |
(defvar *delete-debug-info* t) | |
(defmethod delete-debug-info ((code-obj sb-vm::code-component)) | |
(when *delete-debug-info* | |
(setf (sb-c::%code-debug-info code-obj) nil))) | |
(defgeneric grovel (object)) | |
(defmethod grovel ((null null)) | |
(values)) | |
(defmethod grovel ((cons cons)) | |
(keep cons) | |
(grovel (car cons)) | |
(grovel (cdr cons))) | |
(defmethod grovel ((array array)) | |
(keep array) | |
(when (eq (array-element-type array) 't) | |
(labels ((grovel-array (dimensions indices) | |
(if dimensions | |
(dotimes (i (car dimensions)) | |
(grovel-array (cdr dimensions) | |
(cons i indices))) | |
(when indices | |
(grovel (apply #'aref array indices)))))) | |
(grovel-array (reverse (array-dimensions array)) nil)))) | |
(defmethod grovel (obj) | |
(cond ((= (sb-vm::widetag-of obj) sb-vm:unbound-marker-widetag) | |
(values)) | |
((= (sb-vm::widetag-of obj) sb-vm:value-cell-header-widetag) | |
(keep obj) | |
(grovel (sb-vm::value-cell-ref obj))) | |
(t | |
(format *stream* "Don't know how to handle ~s~%" obj)))) | |
(defmethod grovel ((num number)) | |
(values)) | |
(defmethod grovel ((char character)) | |
(values)) | |
(defmethod grovel ((package package)) | |
(values)) | |
(defmethod grovel ((pointer sb-impl::system-area-pointer)) | |
(values)) | |
(defmethod grovel ((pointer sb-impl::weak-pointer)) | |
(keep pointer) | |
(grovel (sb-impl::weak-pointer-value pointer))) | |
(defun grovel-slots (object &key skip) | |
(dolist (slot (sb-mop::class-slots (class-of object))) | |
(let ((name (sb-mop::slot-definition-name slot))) | |
(unless (member name skip) | |
(when (slot-boundp object name) | |
(grovel (slot-value object name))))))) | |
(defmethod grovel ((instance sb-pcl::slot-object)) | |
(keep instance) | |
(format *stream* "keeping instance=~s~%" (type-of instance)) | |
(grovel-slots instance)) | |
(defmethod grovel ((class sb-kernel::classoid)) | |
(keep class) | |
(grovel-slots class :skip '(sb-kernel::subclasses))) | |
(defmethod grovel ((class class)) | |
(keep class) | |
(format *stream* "keeping class ~s~%" class) | |
(push class *classes*) | |
(dolist (fun *generic-functions*) | |
(when (and (slot-boundp class 'sb-pcl::direct-methods) | |
(typep fun 'sb-pcl::standard-generic-function) | |
(slot-boundp fun 'sb-pcl::methods)) | |
(let ((methods (intersection (sb-mop::specializer-direct-methods class) | |
(sb-mop::generic-function-methods fun)))) | |
(dolist (method methods) | |
(format *stream* ".. ~s | ~s~%" class fun) | |
(grovel method))))) | |
(grovel-slots class :skip '(sb-pcl::direct-methods | |
sb-pcl::direct-subclasses))) | |
(defmethod grovel ((fun generic-function)) | |
(keep fun) | |
(push fun *generic-functions*) | |
(dolist (class *classes*) | |
(when (and (slot-boundp class 'sb-pcl::direct-methods) | |
(typep fun 'sb-pcl::standard-generic-function) | |
(slot-boundp fun 'sb-pcl::methods)) | |
(let ((methods (intersection (sb-mop::specializer-direct-methods class) | |
(sb-mop::generic-function-methods fun)))) | |
(dolist (method methods) | |
(format *stream* "// ~s | ~s~%" class fun) | |
(grovel method))))) | |
(grovel-slots fun :skip '(sb-pcl::initial-methods sb-pcl::dfun-state | |
sb-pcl::methods))) | |
(defmethod grovel ((ignore sb-c::vop-info)) | |
(values)) | |
#+nil | |
(defmethod grovel ((ignore sb-pcl::fast-method-call)) | |
(dolist (o *objects*) | |
(format t ">> ~s~%" o)) | |
(close *stream*) | |
(sb-ext:quit :unix-status 1)) | |
(defmethod grovel ((symbol symbol)) | |
(keep symbol) | |
#+nil | |
(when (fboundp symbol) | |
(grovel (symbol-function symbol))) | |
(grovel (sb-c::info :type :translator symbol)) | |
(grovel (sb-c::info :alien-type :translator symbol)) | |
(when (boundp symbol) | |
(unless (keywordp symbol) | |
(format *stream* | |
"keeping var=~s (~s)~%" symbol (type-of (symbol-value symbol)))) | |
(grovel (symbol-value symbol)))) | |
(defmethod grovel ((fun sb-vm::fdefn)) | |
(keep fun) | |
(grovel (sb-vm::fdefn-fun fun))) | |
(defmethod grovel ((fun sb-vm::funcallable-instance)) | |
(grovel (sb-vm::%funcallable-instance-fun fun))) | |
(defun code-constants (code-obj) | |
(loop for i from sb-vm::code-constants-offset | |
below (sb-vm::get-header-data code-obj) | |
collect (sb-vm::code-header-ref code-obj i))) | |
(defun code-object (fun) | |
(let ((header (sb-kernel:widetag-of fun))) | |
(ecase header | |
(#.sb-vm:simple-fun-header-widetag | |
(sb-vm::fun-code-header fun)) | |
(#.sb-vm:funcallable-instance-header-widetag | |
(sb-vm::%funcallable-instance-fun fun)) | |
(#.sb-vm:closure-header-widetag | |
(sb-vm::fun-code-header (sb-vm::%closure-fun fun)))))) | |
(defmethod grovel ((fun function)) | |
(format *stream* "keeping fun=~s~%" fun) | |
(keep fun) | |
(let ((code-obj (code-object fun))) | |
(delete-debug-info code-obj) | |
(dolist (obj (code-constants code-obj)) | |
(grovel obj))) | |
(case (sb-kernel::widetag-of fun) | |
(#.sb-vm:closure-header-widetag | |
(dotimes (i (1- (sb-kernel:get-closure-length fun))) | |
(grovel (sb-kernel:%closure-index-ref fun i)))))) | |
(defmethod grovel :around (object) | |
#+nil | |
(when (eq object #'SB-VM::|CACHED-FUN--MOV[(REG ((OP 11) (IMM NIL TYPE 'SIGNED-IMM-DATA)) '(NAME TAB REG , IMM))]-REG-PRINTER|) | |
(dolist (o *objects*) | |
(format *stream* ">> ~s~%" o)) | |
(error "foo")) | |
;; gah... | |
(when (and (eq (type-of object) 'sb-pcl::funcallable-instance) | |
(= (sb-vm::widetag-of object) 1)) | |
(return-from grovel)) | |
;; gah^2... | |
(when (and (eq (type-of object) 'function) | |
(= (sb-vm::widetag-of object) 1)) | |
(return-from grovel)) | |
(unless (kept-p object) | |
(let ((*objects* (cons object *objects*))) | |
(call-next-method)))) | |
(defvar *stub-lambda* (lambda (&rest args) | |
(format t "stub ~s~%" (car args)) | |
(force-output) | |
(format t "args ~s~%" (cdr args)) | |
(force-output) | |
(backtrace) | |
(sb-unix::unix-exit 42))) | |
#+nil | |
(defmacro make-stub (name) | |
*stub-lambda*) | |
(defmacro make-stub (name) | |
`(lambda (&rest args) | |
(declare (optimize (debug 0) (space 3))) | |
(apply *stub-lambda* ,name args))) | |
(defun clear-caches () | |
(setf sb-disassem::*disassem-inst-formats* nil | |
sb-disassem::*disassem-arg-types* nil) | |
(let ((fc sb-disassem::*disassem-fun-cache*)) | |
(setf (sb-disassem::fun-cache-labellers fc) nil | |
(sb-disassem::fun-cache-printers fc) nil | |
(sb-disassem::fun-cache-prefilters fc) nil)) | |
(sb-c::info-cache-clear) | |
(sb-kernel::type=-cache-clear) | |
(sb-kernel::values-subtypep-cache-clear) | |
(sb-kernel::values-type-intersection-cache-clear) | |
(sb-kernel::csubtypep-cache-clear) | |
(sb-kernel::values-type-union-cache-clear) | |
(sb-kernel::type-union2-cache-clear) | |
; (sb-kernel::type-intersection2-cache-clear) | |
; (sb-kernel::type-negation-cache-clear) | |
; (sb-kernel::%type-intersection-cache-clear) | |
; (sb-kernel::%type-union-cache-clear) | |
(sb-kernel::ctype-of-cache-clear) | |
(keep 'sb-kernel::*ctype-of-cache-vector*) | |
(sb-kernel::%%make-array-type-cached-cache-clear) | |
(sb-kernel::%%make-union-type-cached-cache-clear) | |
(sb-kernel::make-values-type-cached-cache-clear) | |
(sb-kernel::%coerce-to-values-cache-clear) | |
(sb-kernel::values-specifier-type-cache-clear) | |
(sb-vm::primitive-type-aux-cache-clear) | |
(sb-c::weaken-type-cache-clear)) | |
(defun clear-info-environment () | |
(sb-kernel::compact-environment-aux "Auxiliary" 200) | |
(dolist (number '(0 1 2 3 4 5 6 7 8 9 10 11 12 | |
14 15 16 17 19 | |
26 | |
27 28 | |
29 | |
36 37 38 39 | |
)) | |
(dolist (env sb-c::*info-environment*) | |
(when (typep env 'sb-c::compact-info-env) | |
(dotimes (i (length (sb-c::compact-info-env-entries env))) | |
(when (= (aref (sb-c::compact-info-env-entries-info env) i) | |
number) | |
(if (/= number 12) | |
(setf (aref (sb-c::compact-info-env-entries env) i) nil) | |
(let* ((fdefn (aref (sb-c::compact-info-env-entries env) i)) | |
(name (sb-impl::fdefn-name fdefn))) | |
(unless (kept-p (sb-impl::fdefn-fun fdefn)) | |
(setf (sb-impl::fdefn-fun fdefn) | |
(make-stub name))))))))))) | |
(defun delete-unreachable-methods () | |
(labels ((delete-for-class (class) | |
(when (slot-boundp class 'sb-pcl::direct-methods) | |
(dolist (method (sb-mop::specializer-direct-methods class)) | |
(unless (kept-p method) | |
(let ((*print-pretty* nil)) | |
(format *stream* "clearing ~s~%" method)) | |
(setf (slot-value method 'sb-pcl::function) | |
(let ((name (sb-mop:generic-function-name | |
(sb-mop:method-generic-function method)))) | |
(make-stub name)))))) | |
(dolist (subclass (sb-mop:class-direct-subclasses class)) | |
(delete-for-class subclass)))) | |
(delete-for-class (find-class t)))) | |
(in-package sb-vm) | |
(defun valid-obj (space x) | |
(or (not (eq space :dynamic)) | |
;; this test looks bogus if the allocator doesn't work linearly, | |
;; which I suspect is the case for GENCGC. -- CSR, 2004-06-29 | |
(< (get-lisp-obj-address x) | |
sb-vm::dynamic-space-end | |
#+nil (get-lisp-obj-address *ignore-after*)))) | |
(in-package cl-user) | |
(defun delete-unreachable-symbols (map) | |
(let ((*retain* map)) | |
(delete-unreachable-methods) | |
(gc :full t) | |
(do-all-symbols (symbol) | |
(without-package-locks | |
(when (or (ignored-p symbol) | |
(not (kept-p symbol))) | |
(when (boundp symbol) | |
(unless (constantp symbol) | |
(format *stream* "deleting/1 ~s (~s)~%" symbol | |
(type-of (symbol-value symbol))) | |
(setf (symbol-value symbol) nil))) | |
(unintern symbol)) | |
(when (and (fboundp symbol) | |
(or (ignored-p (symbol-function symbol)) | |
(not (kept-p (symbol-function symbol))))) | |
(format *stream* "deleting/2 ~s~%" symbol) | |
(setf (symbol-function symbol) | |
(make-stub symbol))))) | |
(clear-info-environment) | |
(format *stream* "done!~%"))) | |
(defun shake (function) | |
(gc :full t) | |
(sb-pcl::precompile-random-code-segments) | |
(clear-caches) | |
(dolist (sym sb-vm::*static-funs*) | |
(grovel (symbol-function sym))) | |
(dolist (sym sb-vm::*static-symbols*) | |
(grovel sym)) | |
(grovel function) | |
(grovel #'shake-lisp-and-die) | |
(grovel #'sb-c::%check-fast-method-fun-name) | |
(grovel #'SB-KERNEL::SIMPLE-CONDITION-PRINTER) | |
(grovel #'type-error-datum) | |
(grovel #'type-error-expected-type) | |
;; Why does this data structure contain symbols instead of fdefns anyway? | |
(dolist (r sb-impl::*output-routines*) | |
(let ((name (nth 2 r))) | |
(when (kept-p name) | |
(grovel (symbol-function name))))) | |
(clear-caches) | |
(print *retain*)) | |
(defun shake-lisp-and-die (core fun &key (delete-debug-info t)) | |
(gc :full t) | |
(with-open-file (*stream* #p"/tmp/log.shake" | |
:direction :output :if-exists :supersede) | |
(let ((*delete-debug-info* delete-debug-info)) | |
(delete-unreachable-symbols (shake fun)))) | |
(gc :full t) | |
(room) | |
(sb-vm::instance-usage :dynamic) | |
(save-lisp-and-die core :purify t :toplevel fun)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment