Reducing elisp bugs using the byte-code compiler
Did you know that you can compile elisp into byte-code? You did? Okay then, did you know that moderns emacs ship with the elisp compiler? Right, you knew that too? Well, in that case, do you compile your start-up configuration? If you don’t, this post could be for you.
First of all, what is the benefit of compiling? I can think of three.
- You might get a slight speed increase
- byte code loads faster than source code
- compilation picks up all sorts of errors
In my opinion, the first two points are not particularly important. The speed increase is extremely small and, even with the fairly large packages and customisation I have done, emacs starts up in a couple of sections without compiling everything to byte code. The real benefit [for me] is the error checking.
There is also a downside. Emacs always loads a byte code if it available, even if the source code is newer. This is pretty annoying and has bitten me on a few occasions when I have been wondering why emacs was not picking up my changes at start-up.
What kind of errors does the compiler find? Here are some of the errors from a recent compile.
In run-sql-command: db.el:38:30:Warning: reference to free variable `*db-cmd*' In toplevel form: ftp-file-upload.el:16:1:Warning: defcustom for `*ftp-upload-skip-prompt*' fails to specify containing group In toplevel form: htmlize.el:89:22:Warning: reference to free variable `unresolved' In end of data: htmlize.el:1770:1:Warning: the following functions are not known to be defined: byte-compiler-options, warnings, htmlize-next-change, htmlize-locate-file, specifier-spec-list, face-property, color-instance-name, face-foreground-instance, face-background-instance, color-instance-rgb-components, make-color-instance, face-font-instance, font-instance-properties, face-strikethru-p, map-extents, extent-face, extent-at, htmlize-faces-at-point, lazy-lock-fontify-region, lazy-shot-fontify-region, dired-get-marked-files In toplevel form: my-ftp.el:27:7:Warning: assignment to free variable `*ftp-proc*' my-ftp.el:29:68:Warning: reference to free variable `*ftp-proc*' In end of data: my-ftp.el:31:1:Warning: the function `ftp-mode' is not known to be defined. In toplevel form: shell-wrappers.el:87:13:Warning: reference to free variable `term-rawp-map' In end of data: shell-wrappers.el:98:1:Warning: the function `term-set-excape-char' is not known to be defined. In copy-files: wordpress-loader.el:42:45:Warning: file-readable-p called with 0 arguments, but requires 1
As you can see from the errors from htmlize, when you are writing elisp to be portable between xemacs and emacs, the compiler will emit spurious warnings. However, for someone like me, it is great. Look at those basic errors such misspelling term-set-excape-char and calling file-readable-p with no arguments. For dynamic languages, a decent lint style tool including the emacs byte code compiler, use strict in perl and pylint/pychecker/pyflakes can really help find a lot of bugs.
How do we set up the compiler? I prefer a batch-file that compiles everything that has changed.
set EMACS_FILES=c:/packages/emacs-files set EMACS=c:\packages\emacs-22.1\bin\emacs.exe %EMACS% --batch ^ --eval “(load \”%EMACS_FILES%/my-load-paths.el\”)” ^ –eval “(batch-byte-compile-if-not-done)” *.el
(That last eval is prefixed with two minuses but it looks like WordPress has changed it).
Emacs needs to know the paths where the modules live so that it can see if functions or variables are defined in files that have been required.
;; ———————————————————————- ;; (load “c:/packages/emacs-files/my-vars.el”) (add-to-list ‘load-path *elisp-dir*) (add-to-list ‘load-path (expand-file-name “icicles” *elisp-dir*)) (add-to-list ‘load-path (expand-file-name “muse-3.12″ *elisp-dir*)) (add-to-list ‘load-path (expand-file-name “org-5.19a” *elisp-dir*)) (add-to-list ‘load-path (expand-file-name “remember-1.9″ *elisp-dir*)) (provide ‘my-load-paths) ;; ———————————————————————- ;;
Finally, I like to have as few files as possible referring to the top level directory so the emacs variable pointing to the top level gets placed in its own file.
;; ———————————————————————- ;; (defvar *elisp-dir* “c:/packages/emacs-files/”) (provide ‘my-vars) ;; ———————————————————————- ;;
JG
April 18th, 2008 at 12:06 am
[...] Garcia has a post on reducing elisp bugs using the byte-code compiler. This was written by Grant. Posted on Thursday, April 17, 2008, at 6:07 pm. Filed under Link. [...]
April 19th, 2008 at 1:29 pm
I use something like that to compile as needed:
(defun byte-compile-if-newer-and-load (file)
“Byte compile file.el if newer than file.elc”
(if (file-newer-than-file-p (concat file “.el”)
(concat file “.elc”))
(byte-compile-file (concat file “.el”)))
(load file))
(byte-compile-if-newer-and-load (jh-init-file “emacs.init”))
April 19th, 2008 at 8:59 pm
Hi Jochen, do you use this instead of (require …) then?
I am thinking of something that does a compile if needed at start-up, perhaps as part of the .bat file or maybe at the start of the .emacs.
JG