Native compilation turns Emacs Lisp into machine code. It uses the libgccjit library from the GCC distribution, and its output is a .eln file, a shared library that Emacs loads like a plug-in. Native code runs directly on the processor; the manual says it is usually 2.5 to 5 times faster than the same code as byte-code. The .eln file is a cache that belongs to one machine and one Emacs build, not a file to distribute. This page describes the requirements, the cache, the commands and the settings.
Requirements and history
Emacs has to be built with native compilation, which needs libgccjit. To compile Lisp, the machine also needs GCC and Binutils (the assembler and the linker). A Lisp program can ask whether the running Emacs can produce and load native code with native-comp-available-p; on systems that load libgccjit dynamically it also checks that the library can be loaded.
| Emacs | What the release notes say |
|---|---|
| 28 | native compilation is optional, enabled at build time with --with-native-compilation. The notes warn that JIT compilation works on the installed .el files, so they must not be compressed. The option package-native-compile compiles packages natively when they are installed |
| 29 | new command native-compile-prune-cache; new function startup-redirect-eln-cache; symbol-file can report the .eln file that defined a symbol (a new optional third argument); a build option for ahead-of-time compilation of all Lisp files, --with-native-compilation=aot |
| 30 | enabled by default: configure turns it on when libgccjit is present and working; --with-native-compilation=no disables it. New compilation-safety option and native-comp-async-warnings-errors-kind. subr-native-elisp-p renamed native-comp-function-p (the old name is kept as obsolete). describe-function shows the inferred type of native functions |
| 31 | new function native-compile-directory and new option native-comp-async-on-battery-power |
The pipeline
Native compilation is implemented as a side effect of byte-compilation, so compiling a file natively always produces its byte-code too, and everything in Byte-compilation about warnings, macros and eval-when-compile applies. comp.el then runs a list of passes in order:
- the front end spills the byte compiler’s intermediate form (LAP) for each function;
- the code is turned into LIMPLE, the compiler’s own intermediate representation;
- optimisation passes run on it: forward propagation, call optimisation, an interprocedural purity analysis, type constraints and type checks, tail-call optimisation, and the computation of function types;
- the final pass builds the code with
libgccjit, which calls GCC to produce and link the shared library.
The last step runs in a separate subprocess that starts the same Emacs executable. Messages are written to a buffer *Native-compile-Log*, which uses native-comp-limple-mode (like Fundamental mode but with highlighting for the log). In batch mode they are printed with message. native-comp-verbose sets how much is logged: 0 nothing, 1 the final LIMPLE, 2 also the LAP and extra pass information, 3 everything.
Commands and functions
| Name | What it does |
|---|---|
native-compile | compiles a function symbol, a form, or a source file, synchronously. For a file it returns the name of the .eln it wrote; an optional second argument gives the output file name |
native-compile-async | compiles files or directories (optionally recursively) in subprocesses, in the background. It can load each result, and a selector (a regexp or a predicate) chooses the files. Several jobs run in parallel |
native-compile-directory | compiles all .el files in a directory tree for which no .eln is found in native-comp-eln-load-path (new in Emacs 31) |
batch-native-compile | for batch runs: compiles the files named on the command line and then kills Emacs. A failed file does not stop the rest, but the exit status is non-zero. With the optional for-tarball argument the results go to the last directory of the load path, used when building Emacs from a source tarball |
emacs-lisp-native-compile, emacs-lisp-native-compile-and-load | commands that compile the file of the current buffer if it changed; the second also loads the result |
native-compile-prune-cache | deletes .eln files that the running Emacs cannot use, in every directory of the load path except the last (the system one) |
startup-redirect-eln-cache | for init files: makes a given directory the first entry of the load path, so the cache is written there |
native-comp-available-p | tests for support |
emacs --batch -f batch-native-compile foo.el
The eln-cache
native-comp-eln-load-path is the list of directories where Emacs looks for .eln files, in the same role as load-path for source. A relative name is taken relative to invocation-directory. The last entry is the system directory with the files installed by the Emacs build. The default user entry is the eln-cache directory inside user-emacs-directory. Emacs writes new files into the first writable directory of the list.
Inside each directory Emacs looks only in one subdirectory. Its name, stored in comp-native-version-dir, is built from the Emacs version and an 8-character hash that depends on the native-compilation ABI, so builds that cannot share code never share a directory. The files in it are named after the source file:
~/.emacs.d/eln-cache/30.1-1a2b3c4d/foo-0123abcd-4567ef89.eln
This path is an illustration of the shape. Per the cleaning code in comp.el, a file name is the library name, a dash, a hexadecimal hash, a dash, a second hexadecimal hash and .eln. The first hash identifies the source file: when a library is recompiled, Emacs removes every .eln in the load path that has the same first hash, which deletes the stale versions. comp-lookup-eln finds the compiled file for a source file by turning its name into a relative .eln name and testing each directory of the load path. The manual notes that because of the unique names, files made by several Emacs versions can live in one directory, and a different version will normally produce a differently named file that only it can load.
When Emacs loads a .elc found on load-path, it looks for a matching .eln in this cache. The source file must still be present, because the lookup and the background compilation both use it; native-comp-warning-on-missing-source (default on) warns when it is not. If load-prefer-newer is on, load does not take the .eln.
To keep the cache out of the home directory, change native-comp-eln-load-path, call startup-redirect-eln-cache, or point HOME at a directory that does not exist. Trampolines may still produce a few files.
Asynchronous (JIT) compilation
When a .elc file is loaded and no .eln exists, Emacs can compile it in the background. This happens when native-comp-jit-compilation is non-nil. The work is done by separate batch Emacs subprocesses; when one finishes, the new .eln is loaded and its functions replace the ones from the .elc. Because the subprocess starts from a fresh Emacs, it can report warnings that a normal byte-compile does not, for example for a missing require.
| Variable | Meaning |
|---|---|
native-comp-async-jobs-number | the greatest number of subprocesses at once. 0, the default, means half the number of processor units, or 1 on a single unit |
native-comp-async-report-warnings-errors | non-nil reports warnings and errors of the subprocesses in a *Warnings* buffer; silent logs without showing the buffer |
native-comp-async-warnings-errors-kind | which kinds are reported to the main Emacs. The default reports all errors and only the important warnings (Emacs 30) |
native-comp-async-query-on-exit | non-nil asks before exiting while jobs run. When nil (default) the jobs are killed and their .eln files are never written |
native-comp-async-on-battery-power | nil stops new background jobs while the machine runs on battery (Emacs 31) |
Trampolines
A trampoline is a small piece of native code that lets natively compiled code call a primitive (a function written in C) that has been advised or redefined. Calls from compiled code to primitives are normally direct, so they would skip the advice. comp-trampoline-compile in comp.el builds one from a synthetic lambda that calls the primitive through funcall or apply, compiled with native-comp-speed 1 and without byte optimisation, so the call is not optimised away. It is stored as its own .eln file.
native-comp-enable-subr-trampolines controls this. t (default) generates them, even when JIT compilation is off. nil turns generation off, and then calls from native code to an advised primitive ignore the advice unless a trampoline already exists. A string names a directory, relative to invocation-directory, where trampolines are stored, outside the version subdirectory, and not found again in later sessions. When no writable directory is available, a trampoline goes to temporary-file-directory.
Optimisation settings
| Setting | Meaning |
|---|---|
native-comp-speed | the optimisation level, from -1 to 3, default 2. 0 to 3 correspond to GCC’s -O0 to -O3; 2 is the highest level that follows the language rules exactly, and 3 may do dangerous optimisations. -1 means no native code: the .eln file is still produced, but holds the functions as byte-code |
compilation-safety | 0 or 1. At 0 the emitted code may misbehave, even crash Emacs, if a function’s declared type is wrong. At 1 the code is safe even for wrong declarations (Emacs 30) |
native-comp-debug | 0 to 3. 1 adds debugging symbols; 2 also dumps pseudo-C; 3 also dumps the GCC passes and the libgccjit log. The pseudo-C files go next to the .eln |
native-comp-verbose | log level, above |
native-comp-compiler-options | extra options passed to the GCC compiler; not all work, and it needs libgccjit version 9 or later |
native-comp-driver-options | options for the back-end driver, mainly assembler and linker flags. The default is ("-Wl,-w") on macOS |
The same settings apply per function through a declare form:
(defun my-hot-loop (n)
(declare (speed 3) (safety 1))
(let ((sum 0))
(dotimes (i n) (setq sum (+ sum i)))
sum))
(speed -1) makes native compilation emit byte-code for that function. In Emacs 30 a declaration (ftype TYPE) states the function’s type, which the native compiler can use for better code when compilation-safety is 0.
The file variable no-native-compile stops only native compilation of a file. no-byte-compile stops both and wins if both are set. native-comp-bootstrap-deny-list lists regexps of files to skip while building Emacs.
What an .eln file is
A .eln file is a native shared library, the kind of file the system uses for .so or .dll: ELF on Linux, Mach-O on macOS and PE on Windows. It holds the machine code of one Lisp file’s functions, in the form the building Emacs expects, with entry points that depend on Emacs’s internal primitives and calling conventions. The manual states the consequences: it is as portable as any shared library, so it works on the same machine or very similar ones (same processor and run-time libraries), and an Emacs of an earlier or later version will usually not load it. Native compilation of the same code by a different Emacs gives a different file name.
At speed -1 the file is still written, but contains the code as byte-code. The byte-compiled .elc remains needed as a fallback and as the source of the lookup.
In Lisp, a native-compiled function prints as #<subr foo>, like a primitive (this is recalled, not checked against the sources). native-comp-function-p (formerly subr-native-elisp-p) tests for one, and primitive-function-p or subr-primitive-p tests for a built-in written in C. symbol-file with its third argument reports the .eln that defined a symbol (Emacs 29).
How the viewer treats .eln
The viewer names the file “Natively compiled Emacs Lisp (eln)” and shows it as bytes only. Because the file is a shared library, a general file recogniser may identify it as ELF, Mach-O or PE (a DLL) instead. That is correct for the container; the name above describes the role of the file.
Not described
Known, not yet covered
- Reference: how the two hashes in a file name are computed, and the name of a trampoline file (
comp-trampoline-filename). They are defined incomp.c, which was not among the downloaded sources. - Reference: the deny lists for JIT compilation (
native-comp-jit-compilation-deny-listand, in older releases,native-comp-deferred-compilation-deny-list) and the startup hooks that start the JIT. They are incomp-run.el, not downloaded. - Reference: the internal layout of an
.eln(symbols, relocation tables, the embedded Lisp data) and the individual compiler passes. - Reference: the printed form of a native-compiled function was recalled, not confirmed in the sources.
See also
- Byte-compilation – the front end and the
.elcformat - Emacs versions – what each release changed
- Functions – the
declareform - Compilation of Lisp to Native Code – the manual’s chapter