Skip to content

Instantly share code, notes, and snippets.

@kanru
Created March 4, 2012 04:03
Show Gist options
  • Save kanru/1970622 to your computer and use it in GitHub Desktop.
Save kanru/1970622 to your computer and use it in GitHub Desktop.
shake-lisp-and-die
;;; 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