The next version of Emacs will include an optimizing compiler from Elisp to native code, thanks to the work by Andrea Corallo et.al. This will facilitate performance increases between 2.3x and 42x¹ and a generally smoother editing experience².
In this talk, I will discuss the design and implementation of this new feature and demonstrate some of its implications for day-to-day use.
~20% of emacs written in C because of performance
Byte compiler & VM used to speed up Elisp performance
Last major improvements to byte compiler: ca. 1990
Byte code portable in theory
Demanding modern requirements:
Competition (VSCode) uses state of the Art JIT compiler (V8)
VIM users laugh at Emacs’ input latency
(defun silly-loop1 (n)
"Return the time, in seconds, to run N iterations of a loop."
(let ((t1 (float-time)))
(while (> (setq n (1- n)) 0))
(- (float-time) t1)))
(silly-loop1 50000000)
(defun silly-loop2 (n)
"Return the time, in seconds, to run N iterations of a loop."
(let ((t1 (float-time)))
(while (> (setq n (1- n)) 0))
(- (float-time) t1)))
(byte-compile 'silly-loop2)
(silly-loop2 50000000)
(with-temp-buffer
(disassemble 'silly-loop2 (current-buffer))
(buffer-string))
(defun silly-loop3 (n)
"Return the time, in seconds, to run N iterations of a loop."
(let ((t1 (float-time)))
(while (> (setq n (1- n)) 0))
(- (float-time) t1)))
(native-compile 'silly-loop3)
(silly-loop3 50000000)
(with-temp-buffer
(disassemble 'silly-loop3 (current-buffer))
(buffer-string))
Starts with existing byte code representation
Implements bespoke IR with data-flow analysis, etc
Uses libgccjit for native code generation
(defun simple1 (n)
(+ n 1))
(with-temp-buffer
(disassemble 'simple1 (current-buffer))
(buffer-string))
LIMPLE
Motivation:
Later IRs in GCC disregard Elisp’s semantics
Thus, some compiler features best implemented with Elisp in mind:
Type propagation:
Purity analysis
Reference propagation
Unboxing
Compiler hints
comp-hint-fixnum
comp-hint-cons
Better warnings and errors
GCC optimization constraints: GCC very conservative with optimization
(defun simple2 (n)
(+ n 1))
(let ((native-comp-verbose 2))
(native-compile 'simple2))
Propagation of
Byte Code: Opcodes for some C-functions, funcall-trampoline for others
Now: All primitive C functions as direct calls
(defun simple2 (n)
(+ n 1))
(native-compile 'simple2)
(with-temp-buffer
(disassemble 'simple2 (current-buffer))
(buffer-string))
Transparent and asynchronous (depending on native-comp-deferred-compilation
)
Whenever .elc
file without matching .eln
file is loaded:
.eln
file cached for future loadingAhead-of-time compilation optionally possible