The byte compiler turns Emacs Lisp source into a .elc file. A .elc file is still Lisp text for the Lisp reader, with a short binary header, but its functions are strings of byte-code for a small stack machine inside Emacs. Compiled code loads faster and runs faster than interpreted source, and the compiler checks the code and reports mistakes. This page describes the commands, the warnings, and then the .elc format from the first bytes to the last form.
What byte-compilation does
The compiler reads the source one top-level form at a time. A defun or defmacro becomes a compiled function or macro definition. Other forms are collected into batches, and each batch is compiled and written so that its code runs when the file is loaded. Macros are expanded while compiling, so they must be known to the compiler at that point (see below). All comments are dropped, which is why the .elc file cannot be read back as source.
The compiler also acts as a checker. It warns about calls with the wrong number of arguments, unknown functions and variables, obsolete names, and many other mistakes. Many Emacs Lisp projects treat a clean byte-compile as a minimum test.
Commands and functions
| Name | What it does |
|---|---|
byte-compile | compiles one function: the argument is a symbol, whose definition is replaced by the compiled one, or a lambda expression, which returns the compiled code without storing it. Returns the byte-code function object |
byte-compile-file | compiles a file. The output name is the input name with .el changed to .elc (or .elc added when there is no .el). Returns t when there were no errors |
byte-recompile-directory | recompiles every .el file in a directory and its subdirectories whose .elc is older. A flag of 0 also compiles files that have no .elc; a force argument recompiles everything; symbolic links are skipped unless asked for |
batch-byte-compile | for batch runs only: compiles the files named on the command line and then kills Emacs. An error in one file does not stop the others, but the exit status is non-zero. With the optional noforce argument, files with an up-to-date .elc are skipped |
compile-defun | compiles and evaluates the defun at point |
emacs-lisp-byte-compile | the command in Emacs Lisp mode that compiles the file of the current buffer; its definition is not in the files this page was written from (the docstring of byte-compile-file refers to its sibling emacs-lisp-byte-compile-and-load) |
disassemble | shows the byte-code of a function, a lambda expression or a byte-code object in a *Disassemble* buffer |
The usual command line for a build is:
emacs --batch -f batch-byte-compile *.el
The messages go to a buffer named *Compile-Log* in an interactive session, and to standard error in batch mode. If byte-compile-debug is non-nil, errors are signalled as Lisp errors instead.
Warnings and how to control them
The variable byte-compile-warnings selects the kinds of warning. t means all the normal kinds, all means every kind, a list names the kinds to enable, and a list that begins with not names kinds to suppress, for example (not obsolete). If byte-compile-error-on-warn is non-nil, warnings are reported with error, which makes a build fail on any warning. Both variables are safe as file-local variables.
| Kind | Reports |
|---|---|
callargs | a call whose arguments do not match the definition |
constants | binding or assigning to a constant or a non-variable |
docstrings | style problems in documentation strings; narrower kinds are docstrings-wide, docstrings-control-chars and docstrings-non-ascii-quotes (the last is only enabled in Emacs builds) |
empty-body | an empty body in a special form or macro |
free-vars | a variable that is not in the current lexical scope |
ignored-return-value | a call whose result is thrown away where that is probably a mistake |
interactive-only | a command that is not meant to be called from Lisp |
lexical, lexical-dynamic | dynamic variables without a package prefix; a lexical variable declared dynamic elsewhere |
make-local | doubtful calls of make-variable-buffer-local |
mutate-constant | changing a constant such as a quoted list |
noruntime | functions that may not exist at run time because they are defined only under eval-when-compile |
not-unused | using a variable whose name starts with _ |
obsolete | obsolete functions and variables |
redefine | redefining a function as a macro or the reverse, or with a different arity |
suspicious | constructs that usually do not do what was meant |
unresolved | calls to functions that are not known |
To silence a warning that is wrong, tell the compiler what it needs to know:
(declare-function NAME "file")says that a function exists;(defvar NAME)with no value says that a variable exists, and marks it dynamically bound within the current scope or file.- A call in the then branch of
(if (fboundp 'f) …), or a variable reference in the then branch of(if (boundp 'v) …), is not reported. (with-suppressed-warnings ((obsolete foo)) …)silences one kind for named symbols.with-no-warningssilences everything inside it and should wrap as little code as possible.- Warnings about missing functions and variables are reported at the end of the file, not where the use is.
Compile-time evaluation
The compiler does not run the code it compiles, except for require forms, which it loads, and the forms below.
| Form | Effect |
|---|---|
eval-when-compile | the body runs at compile time only; its value becomes a constant in the compiled file. When the source is loaded instead, the body runs normally |
eval-and-compile | the body runs both when compiling and when the code runs, compiled or not |
| macro calls | expanded at compile time. The macro definition must be loaded then; the usual idiom is (eval-when-compile (require 'my-macros)), so that running the compiled file does not load it |
defsubst | an inline function; a call compiled before the definition is known still works, only slower |
(defvar gauss-sum
(eval-when-compile (apply #'+ (number-sequence 1 100))))
In the compiled file this variable is set to the constant 5050; the sum is never computed at run time.
The .elc file
A .elc file is written by byte-compile-insert-header and byte-compile-output-file-form in bytecomp.el. It starts with a header of binary and comment lines, followed by the compiled forms.
| Offset | Content | Meaning |
|---|---|---|
| 0 | ;ELC | the magic number, 0x3B 0x45 0x4C 0x43. It is also a comment, so the reader skips it |
| 4 | one byte | the format version: the Emacs major version, except that a minor version of 0 gives the major version minus 1 |
| 5 | 00 00 00 0A | three NUL bytes and a line feed, ending the first line |
| then | ;;; Compiled | a comment line |
| then | ;;; in Emacs version X | the value of emacs-version |
| then | ;;; with all optimizations. | or with source-level optimization only, with byte-level optimization only, or without optimization, followed by a full stop; then two empty lines |
The binary bytes make diff report “Binary files differ” instead of printing a diff of two compiled files, and they let the file tool identify the format. The version-byte rule exists so that files compiled by a development build of the next release (minor version 0) are still accepted by the previous release; the comment in the source says this is silent loading into Emacs 27 of files compiled with 28.0.NN. The compiler asserts that the byte is greater than 13 and less than 128, so it can never be a line feed or any other byte that would end the comment, nor a non-ASCII byte. Emacs 30.1 therefore writes byte 30 (0x1E), and a file compiled by 31.0.50 also writes 30.
After the header every top-level form is printed on its own: byte-compile-output-file-form writes a newline and then the form, with print-escape-newlines on, no length or level limit, and print-circle and print-gensym on. So the rest of the file is a sequence of Lisp expressions separated by newlines. Control characters and bytes above 127 inside strings are written raw or as octal escapes, so the file is not clean text.
When Emacs loads a file it reads the first 512 bytes. It takes the byte at offset 4 as the version and checks that the first line is followed by a comment line that begins ;;;, one character, then in Emacs version or bytecomp version FSF. A file that fails this test is not considered safe, and loading it as a compiled file signals “was not compiled in Emacs”. A version of 1 is what an empty file gives, and is treated as not compiled.
Byte-code function objects
A compiled function is printed like a vector with a # before the bracket. Since Emacs 30 the manual calls this type a closure, the common parent of byte-code functions and interpreted functions. It has at least three slots, and only the first six are in normal use:
| Slot | Name | Content in a byte-code function |
|---|---|---|
| 0 | argument descriptor | an argument list, or an integer |
| 1 | code | a unibyte string of byte-code instructions |
| 2 | constants | a vector of the objects the code refers to: function names, variable names and literals |
| 3 | stack depth | the greatest stack size the function needs |
| 4 | documentation | the docstring or nil; may also be a number or a list when the string is stored in the file (see below) |
| 5 | interactive | the interactive spec (a string or an expression), or nil |
An integer argument descriptor is used by lexically bound code. Bits 0 to 6 hold the least number of arguments, bits 8 to 14 the greatest number, and bit 7 is set when the list has &rest. The caller pushes the arguments onto the stack of the byte-code interpreter. A list descriptor belongs to dynamically bound code: the arguments are bound as dynamic variables before the code runs. Because an integer loses the argument names, the compiler appends a line (fn ARG1 ARG2) to the end of the documentation string so that help can still show the signature (the manual’s example above ends its string this way).
This is the manual’s example of a compiled factorial (an integer descriptor 257 means exactly one argument; the string contains raw control bytes that the manual writes as ^H and ^@):
#[257
"\211\300U\203^H^@\300\207\211\301^BS!_\207"
[1 factorial] 4
"Compute factorial of INTEGER.\n\n(fn INTEGER)"]
The older top-level form is (byte-code "CODE" [CONSTANTS] DEPTH): a call to the byte-code function with the same three leading pieces and no argument list or documentation. The compiler uses it for batches of top-level forms that are not definitions. Trivial forms such as a single call of a constant are written as plain Lisp instead, because the interpreter is as fast on them.
Do not build these objects by hand (make-byte-code exists but inconsistent slots can crash Emacs). Interpreted functions are now objects of the same family, made by make-interpreted-closure: the code slot holds the body forms and the constants slot holds the lexical environment, nil for the old dynamic dialect. See Emacs versions.
Documentation strings in the file
The option byte-compile-dynamic-docstrings is on by default. The compiler then does not keep a documentation string inside the function object. It writes the string to the file as a comment form and leaves a reference that Emacs resolves only when the documentation is asked for. This saves memory and loading time. Set it to nil in the first line of a file (-*-byte-compile-dynamic-docstrings: nil;-*-) when the file will be replaced while Emacs runs: after the file is moved or recompiled, the lazy documentation of already loaded functions can no longer be found until the file is loaded again.
The mechanism is two reader constructs, which source files should never use:
| Construct | Meaning |
|---|---|
#@COUNT | the reader skips the next COUNT bytes after the number. The count includes the space that follows the digits, the text and the terminator below. #@00 skips to the end of the file and yields nil |
#$ | stands for the name of the file being loaded; the reference to a lazy string is the pair (#$ . POSITION), the file and the byte position of the text |
| ^_ (0x1F) | ends the skipped text. Inside the text three characters are quoted: ^A is written ^A^A, NUL is written ^A0 and ^_ is written ^A_ |
byte-compile-output-as-comment produces this: it prints the object after a space, quotes the characters above, adds the ^_, counts the bytes and inserts #@COUNT in front. The result in the file looks like the following illustration (not real output; the count and text are invented, and ^_ stands for the single byte 0x1F):
#@41 Compute factorial of INTEGER.
(fn INTEGER)^_
(defalias 'factorial #[257 "…" [1 factorial] 4 (#$ . 1234)])
The reader skips the comment when it reaches it in sequence, and the function’s documentation slot holds (#$ . 1234) instead of the string. The later fetch reads the text straight from that position. In Emacs 30 the loader reads byte-code eagerly (see below), but documentation strings remain lazy.
The byte-code interpreter
The interpreter is a stack machine. Instructions push values onto a stack, pop their operands, and push results. When the function returns, the top of the stack is its value. Variables are used through special instructions that move values between the stack and Lisp variables. The code string holds one-byte opcodes, some followed by one or two bytes of operand. The groups are:
| Group | Examples (names as disassemble prints them) |
|---|---|
| Stack | dup, stack-ref N, stack-set N, discard |
| Constants | constant N: push an element of the constants vector |
| Variables | varref, varset, varbind (dynamic binding), unbind N |
| Calls | call N (call a function with N arguments), return |
| Jumps | goto, goto-if-nil, goto-if-not-nil and similar |
| Arithmetic and comparison | sub1, mult, eqlsign, gtr |
| Lists | list2, and instructions for car, cdr, cons and similar |
| Buffers and text | instructions for point, insert, char-after and similar |
The manual shows disassemble on a recursive factorial. The listing reads: duplicate the argument, push the constant 1, eqlsign, goto-if-nil to a later label; otherwise push 1 and return. At the label it duplicates the argument, pushes the constant factorial, copies the argument again with stack-ref 2, applies sub1, does call 1, then mult and return. The full listing, with comments, is in the manual node “Disassembled Byte-Code”.
Loading: .el or .elc
load and require search load-path and try the suffixes in load-suffixes, whose standard value is (".elc" ".el"), so a compiled file is preferred. With Auto Compression mode on, the .gz variants are tried too. When load-prefer-newer is non-nil, all suffixes are tried and the newest file wins. Otherwise, if the .el is newer than the .elc, Emacs still loads the .elc and prints “Source file … newer than byte-compiled file; using older file”. That message is the usual reason to turn on load-prefer-newer during development.
Version compatibility
A .elc file is guaranteed to load only in the Emacs major version that made it. The instruction set and the object layout change between releases; the version byte exists to allow tools to see that. Compiled files that use newer features can also be incompatible with older Emacs: the Emacs 28 notes warn that an interactive form listing applicable modes produces byte-code that older versions cannot use. Treat .elc files as build products to be regenerated, never as an interchange format.
Emacs 30 changed two things. Byte-code is now always loaded eagerly: files compiled by older Emacs with the lazy byte-compile-dynamic are read in one go, fetch-bytecode is obsolete and does nothing, and byte-compile-dynamic no longer has any effect. And evaluating a lambda now returns an interpreted-function object that prints much like a byte-code function (argument list, docstring and interactive form in the same slots) instead of a list beginning with lambda or closure.
How the viewer treats .elc
The viewer names the file “Byte-compiled Emacs Lisp (elc)” and shows it as bytes only. It does not decode the header, the forms or the byte-code.
Not described
Known, not yet covered
- Reference: the byte-code instruction set. The opcodes, their numbers and operand encodings are not listed; the groups above only name the kinds.
- Reference: where Emacs compares the version byte with the running Emacs. The loader function read for this page checks the byte and the comment line, but the comparison and the error text for a mismatch were not found in the downloaded sources.
- Reference: the definition of
emacs-lisp-byte-compile(inelisp-mode.el, not among the downloaded files), and the exact output ofdisassemblefor a lexical function with an integer descriptor beyond the manual’s examples. - Reference: optimiser details (
byte-optimize,byte-compile-delete-errors) and the contents of thebyte-compile-log-buffer.
See also
- Native compilation –
.elnfiles, built on top of the byte compiler - Emacs versions – what each release changed
- File structure – first-line cookies and file-local variables such as
no-byte-compile - Macros – expansion at compile time
- Byte Compilation – the manual’s chapter
- Disassembled Byte-Code – the manual’s worked
disassembleexamples