Hi everybody, my name is Andrea Corallo,
and this presentation is about
the Emacs Lisp Native Compiler --
having GNU Emacs able to
compile and run Emacs Lisp as native code.
This project has been my hobby project
for the last about two years and a half
And we will see a little bit
where this project is coming from,
where we are, and where we want to go.
So essentially everything you need to know
about the Emacs Lisp Native Compiler,
and probably a little more.
Just a little bit of context on Emacs Lisp.
Well, Emacs Lisp is a programming language,
it's indeed a Lisp,
and one year ago I looked for some statistics,
and I was kind of pleased to see
-- surprised to see actually --
that it's still kind of popular
as a programming language.
It doesn't rank that bad
against other programming languages.
Also, the other important fact about Emacs Lisp
is that there is a lot of Emacs Lisp out there,
and this will have an impact [on this project.]
It's a programming language that is capable
of almost any task, so it's
almost a general-purpose programming language,
and this reflects on Emacs itself,
that it's capable of almost any task.
Indeed this "almost" is something we want to fix,
because we want to do everything,
we want [to do] all of our computing [with Emacs].
Also, an interesting aspect for me
is that it's not specified by any standard.
This implies it can evolve in a more agile way
without having to change the standard, etc.
And, in fact, it's kind of improving,
I believe relatively fast.
A little bit about Lisp in general.
First, it's the best programming language ever,
we all know it.
It has a lot of very nice properties,
like it's dynamic, it's homoiconic, etc.
But the interesting thing for implementors,
is that, in theory,
it can be implemented with very few primitives.
You build very few primitives that are like magic,
and on top of this,
you can implement the whole language.
This sounds very nice,
and very appealing for implementors
meaning to implement a new Lisp implementation,
or improving or modifying an existing one.
So the question is:
how many primitives do we have to implement
if we want to change
the GNU Emacs Lisp implementation?
Unfortunately, not really as few as we would like.
In GNU Emacs, we have about 1500 primitives,
and the main reason for that is performance.
Actually, GNU Emacs was written
when performance was a big issue,
and nowadays certain parts
are still performance-critical.
We have a lot of C code;
30% of the GNU Emacs codebase is C code,
and we have to maintain this.
But not only that, this is the main barrier
for people that tried in the past
to change the Emacs Lisp implementation.
Because not only do you have to
replace these primitives,
all of them or part of them,
but sometimes they also share (these primitives),
internal data structures.
For instance, it's very difficult to say:
Now I want to go from C
to a different programming language
for implementing these primitives.
So this has been, effectively,
the main barrier for doing this work.
Another interesting aspect
about the GNU Emacs implementation
is that Lisp can run interpreted
or byte-compiled for performance,
and the byte compiler itself
is written in Emacs Lisp.
This implies that GNU Emacs has to go through
a bootstrap procedure in order to be built.
But it's kind of interesting
for something that started as a text editor,
or something like it.
The byte-code that is Emacs Lisp
when it's been byte compiled,
it's running on a stack-based virtual machine
that is implemented in C.
OK, so I've listed a bunch of areas
where Emacs Lisp could improve:
Namespace, Extensibility, Performance,
and Debuggability, and Diagnostic, we could say.
This activity, this project in particular,
is affecting primarily Performance
and the performance area the Execution Engine.
That said,
I think it has an impact also on Extensibility,
and I hope it will have an impact also
on programming diagnostics,
so giving better warnings, [unknown].
So which are the benefits
of increasing the Emacs Lisp performance?
Indeed, we will have, if we do that,
programs that run faster.
But the main implication of that
is that we could write less C;
we could maintain and debug less C.
That is kind of a time-consuming task.
And we could also allow for
writing performance-critical extensions
directly in Lisp
without having to use systems like
[I think there's a bunch.]
These are very consistent benefits.
OK, Project Goals,
but I think the title of this slide
maybe should be Project Requirements.
So when I started this activity,
I set some requirements for the project,
with the main goal in mind: to go upstream.
So I wanted to create something,
a modified implementation of GNU Emacs,
that was compatible
as close to 100% as possible
to the current implementation.
And when I say "current implementation,"
I don't refer to what
the Emacs Lisp Programming Manual specifies
as expected behavior of the implementation,
but really the implementation itself.
This is because there are a lot of corner cases
that are not specified by the manual,
but programs do rely on that,
and given there is a ton of Emacs Lisp
already around,
compatibility was definitely a major requirement.
I wanted to produce something that had
reduced impact on the Emacs codebase,
at least as much as possible.
So I didn't want to rewrite all of GNU Emacs.
Indeed, because it would have been
too much work for one single person,
but also still thinking
to an upstream outcome all of this time.
Another requirement was to have
no, or very reduced, impact on the user,
so I didn't want to change
the way Emacs is used by you.
And last but not least,
introducing new dependencies,
those dependencies had to be Free Software,
possibly GPL,
and also an important requirement
is that these dependencies had to be
some kind of trusted software that we know
is going to be maintained in the future.
Given Emacs has been around since forever,
and will be around forever and ever,
this was another very important point.
A little bit of history of this project/
a quick timeline.
2019, in May, I did my first commit,
I think it was when I tried to write
my first primitive function ever in C,
in GNU Emacs.
And this was an attempt to try to compile
a function that, once executed, returning [?]
That was it. Six months after (about),
I had something that was kind of working,
So I was able to start up a semi-standard Emacs
and then to compile and load,
and replacing most of the functions
that I had defined floating in my Lisp universe.
Those functions are the functions
that are essentially composing Emacs,
at least the Lisp side of Emacs.
A lot of features were missing,
like I had no Garbage Collector support,
no bootstrap, I was not optimizing these functions
Because optimization [would be broken].
No image dump support, etc.
But I think this proved the design could work.
So I sent to email to emacs-devel. I said
I have this stuff I'm working on,
and I wanted some feedback from the upstream
to see if there was some interest.
I believe the outcome of this was positive
because about one month after,
I pushed my branch within the Emacs git
as a feature branch, and shortly after,
we started to use the bug tracker to track bugs.
So essentially we moved the development
on the upstream infrastructure.
I believe two years after the first commit,
the project was merged
after literally hundreds of bugs solved,
and improvements, suggestions [unknown]
and this was about six months ago.
Before discussing how the native compiler works,
I think it's worth looking at
how Lisp is implemented in GNU Emacs.
We have Lisp_Objects
floating around our Lisp universe,
and they are internally represented in this way.
We have what is called a tagged pointer,
that is just a regular pointer
that is pointing to the area of memory
where we hold the real data of the object.
But within this tagged pointer,
we reserve a few bits
to indicate the type of object we are pointing to.
This is important because
each time we access an object,
we have to typically check those bits
to check that the object we are manipulating
is of the right kind,
remove those bits, and, if we are happy,
access the object, otherwise [unknown].
All the objects are like this,
except for typically Fixnums,
that are small integers
that we manage to fit directly within the pointer.
Also for manipulating Fixnums,
we have to check the tag bits each time.
Whenever we are not sure of the type of object
we are manipulating (read: almost every time),
we have to check those bits and remove those bits
before doing any manipulation on the Fixnum.
How Emacs Lisp is byte-compiled and executed
in, let's call it, "Vanilla".
If we have a Lisp expression of this kind:
We take the variable 'a' we do plus 2,
and then we multiply the result by 3,
the byte compiler will produce this LAP code.
LAP code is essentially
the assembly for the byte-code,
so it's the "intermediate representation"
that will assembled into byte-code. (.elc files)
How is this program executed?
As I mentioned, it's executed in a virtual machine
that is stack-based,
but we start with an execution stack that's empty,
and a stack pointer pointing to its bottom.
And we execute the first instruction,
that is pushing in the stack the value of 'a',
in this case, 100. Then we push the constant 2.
Then we do the summation,
and we have the result in the stack.
Same: we push the constant 3,
we do the multiplication,
and we will be able to return.
Now, what's good and what's bad about this?
A good thing is that it's very simple
to start from Lisp
and compile this kind of LAP output.
At least it's reasonably simple.
The compiler is not that complex.
The bad thing is that all this machinery
-- push and pop, etc. --
it's very different from how a modern CPU works.
Because modern CPUs,
they are not stack-based anymore
but they have instead a fixed number of registers,
and they work with assignment and operation
within these registers
that are generally called "general-purpose."
So to execute this LAP program,
there is another program,
that is the implementation of the VM itself
that is doing conversion during runtime.
So it's interpreting the LAP program
and it's converting it into instructions
that we can execute on the CPU.
This conversion is done each time
we will run some byte-code.
And it's something that we want to avoid.
Instead of this live conversion,
we want to convert once:
our Lisp program into native code,
that is, a binary program that can be executed
directly by our CPU.
We want to save all this unnecessary conversion
that we do each time we are running a program
while we are running it.
And we want to do this process just once,
when we are compiling.
That's the main goal of this activity.
How is the byte compiler implemented?
As any compiler it's a pipeline of transformations.
We go through macro expansion, closure conversion,
we have a bunch of source level optimization.
Then we go into LAP,
that's the transformation we are interested in,
and after a few optimizations on LAP,
LAP is assembled into byte-code.
So if we [list it]
in terms of intermediate representations,
we can simplify this pipeline like this.
We start with Lisp, and at a certain point
we are manipulating the program in LAP form,
and then at the end we produce the byte-code
that is the .elc file that [you run]
What I wanted to realize was something like this.
I wanted to start from LAP, do something,
and jump into GCC using libgccjit
and in particular
the libgccjit Intermediate Representation
that we will discuss.
Now, why I wanted to do something like this?
Essentially, writing a compiler from scratch
for Emacs Lisp would have been a very big task.
So I wanted to rely on, as much as I could,
the Emacs Lisp byte compiler,
because I had to produce something
that was as compatible as possible
to the current implementation.
So this was (I believe) a very good idea
to save an enormous quantity of work
and to produce something
that was compatible in terms of semantics
with the original implementation.
Also, I didn't want to implement a code generator
for each architecture we were targeting,
nor wanted to implement all these optimizations
that are already in GCC,
so I thought it was a good idea
to rely on an existing compiler like GCC [?].
Let's talk about libgccjit.
It was added by David Malcolm in GCC 5
It allows you to describe a C-ish semantic to GCC
and have it compile.
It's good for [reading] Jitters or AoT compilers.
And if we talk about GCC:
it's a compiler, it's a very good one,
it has support for a remarkable number
of target architectures.
It's also very good at generating [fast code],
and it's been around for a long time;
I believe it's like 1 year younger than Emacs.
It's still very well maintained,
and we can assume it will be maintained
for quite a while. And, as I mentioned,
this was a very important point.
Also, it's GPL; it's Free Software,
and it's developed under the GNU umbrella,
so I thought it was a very good option.
So we can imagine a simple translation
that goes from LAP to this subset of C
that we can describe to libgccjit.
This simple translation we can see here,
it's actually pretty trivial.
Instead of doing operations
within the execution stack we have seen before,
we have just an array that is replacing it,
and it's called 'local' in this case,
and we have assignments within this array,
so that they are done in place of the original
push and pop activity of the stack.
The nice thing is that,
when you have done this translation,
GCC will be able to optimize this,
and remove all the unnecessary operations,
and generate code
for the specific CPU you are targeting,
[which will be running your code].
This sounds great; it sounds like
a very simple and effective translation,
and in fact the first iteration of my compiler
was doing just this.
It was essentially a big C function
that was taking LAP and doing this conversion
describing the output to libgccjit.
Unfortunately, if you do this,
you will discover that you have
a performance upper bound limit of about 3x.
So it was an option,
but I thought it was a good occasion
for trying to do something more.
And doing something more means
implementing a smarter compiler
that is doing some advanced analysis on the code,
and will be able to perform
Lisp-specific optimizations
-- optimizations that take advantage of
the specific Lisp semantics,
something that GCC is not aware of.
And while I was thinking about that,
I thought that having a smarter compiler
had also other advantages, like a smarter compiler
that understands the semantics
of the programming language being compiled
would be also capable of
giving feedback to the programmers,
like better warnings and errors.
So I was really fascinated about this idea,
and I wanted to change my implementation
because I was not really happy about it.
I had a lot of C code in terms of
lines that were not doing any smart job.
And I wanted to write all the interesting logic
[in Lisp].
So optimizing outside GCC
before jumping into GCC,
as I mentioned, has two main targets:
Either optimize the code before going into GCC,
or present to GCC some code
that we know GCC can optimize effectively.
And also, this will give, as I mentioned,
better options for the compiler
to provide warnings, errors
-- better diagnostics.
So this is pretty much
what the native compiler looks like nowadays,
in terms of passes.
We have a list of passes,
each of which is taking an input
and producing an output.
So it's doing either analysis on the program
that's being passed,
or it's performing a transformation.
All of these passes are implemented in Lisp,
and only the last pass is implemented in C.
That is the one that is talking to libgccjit.
To do that, I have introduced
a new intermediate representation
that I call LIMPLE, as a tribute to GCC GIMPLE,
that is the main internal representation of GCC,
at least one of the main ones.
Introducing a new intermediate representation
-- a new way of representing my program --
solved a bunch of problems.
First, it allowed me to implement
non-trivial analysis and transformations,
the ones I needed in my compiler pipeline.
But also, it solved the problem of
what was the boundary between
what I had to implement in Lisp,
and what in C.
Because once I had
my intermediate representation defined,
essentially the boundary between Lisp and C
is just a function, that is,
the one that is implementing the final pass.
That is taking, as an input,
all of my programs in LIMPLE representation
and it's doing [his bit].
So I was convinced this design at least had sense.
When we go through some of these passes,
just to give you an idea of what these are doing:
the first pass is just responsible for
spilling the LAP from the byte compiler
that effectively here we are using as a front end
for our compiler pipeline.
The second pass, called 'limplify',
will be in charge of converting LAP into LIMPLE.
LIMPLE is an intermediate representation
that is Control Flow Graph based,
and it's capable of SSA.
So we can have a look to what this means.
Let's assume we have our LAP program,
as any program,
that's a simple list of instructions
that we will execute one after the other.
Some of these instructions
are special instructions
that we call conditional branches,
where we check for a condition,
and if this is verified,
we jump to a different address within the program.
(Addresses that here we are calling 'labels'.)
So we can split our program in chunks,
and those chunks we execute without interruption,
so we always enter from the top of those,
and we exit from the bottom.
We can name those, and split them apart,
and these are what we call basic blocks.
And now we have a bunch of these basic blocks
that are floating,
and they are not any more sorted.
This is what is called
a Control Flow Graph based representation.
Now we can get into the SSA topic.
That stands for Static Single Assignment.
I don't want to get into the details,
but just give you a feeling.
I added into our basic blocks
in our Control Flow Graph a few assignments.
We will transform this into SSA
just for the variable 'x',
just for the sake of demonstrating it.
This is done through a number of phases
that are essentially some analysis,
mainly renaming.
But the outcome, the one we see here,
looks quite similar to the original one,
but we can see that the variable 'x'
has been renamed.
And now we don't have anymore just one,
but a number of these variables.
The interesting property is that
each of these variables is assigned just once.
And this allows for the compiler
to do prediction of the value of that variable,
depending on the position
within the Control Flow Graph.
This is very important. For instance,
a very simple case is 'x1'
that we see is assigned once by definition,
in particular here at the beginning.
Here it's very simple to understand
that x1 will have the value 3.
While, for instance, it's more difficult to prove
what is going to be the value of x5,
because it's calling a function,
or we don't know at the moment what x4 is.
So the compiler will gain the capability
to do prediction on all the variables,
and the more we get information on one variable,
the more we can prove about the others.
Coming back to our passes, the next one
is forward propagation.
This pass is responsible for
doing what I briefly mentioned just before:
doing proof over all the different variables
in different positions of the Control Flow Graph,
about the values, types, or ranges.
This pass is also responsible for
executing functions
when we know that the function has no side effect
and the pass managed to
prove all the values of its argument.
So the function is then executed at compile time
and it doesn't even exist anymore
in the produced code.
Then we have another pass, this is
an example of a pass that is very specific:
it's trying to remove the call to funcall
when those are not necessary.
There are a couple situations
where this is very useful.
And not only is this beneficial
because we are generating better code,
but when we manage to do that,
we allow GCC better analysis over the code,
because GCC knows nothing about funcall.
So if we are calling, from 'foo', directly, 'bar',
for GCC it's way easier to do its analysis
on top of this code.
Another interesting pass we can mention is 'tco'.
This is performing Tail Recursion Elimination.
It allows a more functional programming style,
if you want.
We can jump to the last pass
that is called 'final', and as I mentioned,
this one is responsible for
taking our program in LIMPLE representation
and describing it to libgccjit in the gccjit IR.
That's the main task. It's also
defining inline functions
for accessing fundamental data types, and so on.
This pass is also responsible for
using some of the predictions
done by previous passes to generate better code.
Things we had to add
to have all of this machinery work
and to be controllable:
The first one is an opt called 'native-comp-speed'
and it's equivalent to Common Lisp's 'speed'.
It represents the optimization level.
The default is 2 and is
the maximum optimization level
that is meant to reflect
all the original semantics of Emacs Lisp.
So it's the one that should be used by default.
The second one is 'compilation unit'
and it's a kind of new object
that has been added to Emacs.
Let's have a look to
how the Garbage Collector works in this case.
The GNU Emacs Garbage Collector
is a simple mark-and-sweep garbage collector.
It does a tree walk through all the objects
and follows references from one object to another.
All the objects reachable during the mark phase
will be kept in our Lisp universe.
All the other ones will be freed.
In this case we have a bunch of functions,
'foo1', 'foo2', 'bar1', etc., that are defined.
When a function is defined,
it's accessible through its symbol,
so we have the symbol referring to the function.
The function, in this case a native-compiled one,
is referring to the compilation unit.
The compilation unit is essentially
the ELF file that has been compiled,
and contains all those functions
that came from the original .el file,
and that we have loaded into memory.
If, for instance, 'bar1 and 'bar2 are undefined,
functions [3] and 4 will be no longer reachable,
and we will be able to free them
and unload the compilation unit.
We discussed quite a lot about Control Flow Graph,
SSA, and a lot of boring stuff,
and I promised you that we are doing
a lot of interesting proofs over variables,
So let's have some examples of them.
Let's jump into a quick demo
to see what all of this abstract theory
and this esoteric propagation engine can do for us
and how the user can interact with it.
I've defined a bunch of functions,
and I will native-compile and load it.
Alright, Emacs Lisp native compiled and loaded.
At this point, I can disassemble 'foo1'
to make sure it's native code and I'm not lying.
These are the instructions
that will be executed directly by my CPU
when I call this function.
Alright, very cool.
Now, [Lisp:] (symbol-function #'foo1)
Interestingly, this is returning a subroutine,
as it would be a primitive function.
Because this is native code,
even if it's written in Lisp,
has been converted to native code
as if it's a primitive function.
But we can do also a new thing:
asking for the type of the subroutine.
Alright, very cool. It says this is a function,
it's taking one argument of type 't'
(that means anything
because we don't have any information),
and is returning a type 't',
so also there we don't have much information.
OK, very cool, but not very useful.
Let's see #'foo2. #'foo2 is slightly different,
it doesn't take any argument, but it's returning
an integer included between 3 and 3.
Wow, amazing!
Let's get into something a little more complex:
#'foo3 takes one argument we know nothing about,
but it's returning a number.
And why it's returning a number?
Essentially because 1+ is returning a number,
and in all the other cases,
it would signal an error
if it's not happy about its input argument.
Let's have a look to #'foo4.
#'foo4 is a little bit more complex.
It will return nil
if the 'when' condition is not satisfied,
so it's type 'null' here.
It can return a floating point;
we don't do propagation of floating point so far,
or it can return any integer between 4 and 9.
Wow. Let's go on with #'foo5.
#'foo5 is even more complex
because other than
having to satisfy this condition,
we can see that the result of the propagation
of this complex condition
is propagated also across the 'plus'.
So this foo5 can return nil,
a floating point we know nothing about,
or an integer included between 12 and 24.
Let's go on with #'foo6.
#'foo6 is returning anything but an integer.
I think it should be pretty obvious why,
because if it's not an integer we return it,
otherwise we signal an error.
Let's finish with #'foo7 very quickly.
#'foo7 has another very complex condition,
at least for me, but it's also interesting to see
that we are also propagating values for symbols.
So we can return the symbol 'big,
the symbol 'small,
or an integer included between -100 and 100.
Now, the question is: why all of this is useful
other than having Andrea very happy
when he's playing with this all day?
Well, we have to come back one second
to how Lisp_Objects are represented within Emacs.
Lisp_Objects are represented as machine words,
where we reserve a few bits to indicate the type.
And every time we access the object,
when this is a Fixnum,
or a regular object where this is a pointer,
we always have to extract these bits,
make sure that they satisfy a condition,
so make sure that we are going to manipulate
the object of the type we expect,
and then we can extract the object
and do the manipulation.
If the compiler managed to prove
that the contained object is of the right type,
it will be able to not emit the type check,
and save a lot of instructions.
This is a very powerful optimization.
Let's discuss some potential future development
in this area.
First I think it would be extremely nice
to extend the propagation
to types that are not built in.
There are a lot of cases
where we could optimize effectively.
For instance when we do
a lot of accesses to structures
-- lots of stuff like that --
where we keep on checking and checking
the same object for the type
where it's obvious, where it should be trivial
to prove that it's the right type
after the first access or things like that.
So I believe this is a low-hanging fruit
in terms of performance.
Also I think it would be really nice
to extend the declare mechanism
to allow the user to declare argument types.
(Optionally. Indeed, optionally.)
Doing that would give the compiler
a lot of information to propagate value types
within the function.
But those will allow the compiler
to give the user really good diagnostics
during compile time. Like the compiler could say:
Hey, here you are calling a function
that is expecting this argument of this type,
but I'm proving that you are calling it
with an argument that is of THIS type,
and is not a subtype of the expected one.
So you are doing something not coherent.
This kind of interprocedural logic.
And I think the compiler should also take advantage
(under certain circumstances)
of this interprocedural analysis
in order to optimize even more, when possible.
Also I think we should
work on our [?] to improve the code generation
depending on the prediction
that the compiler is doing.
We already take advantage of those predictions,
but I think we could do better.
A quick look at some performance results.
These are from the elisp-benchmarks package
within GNU ELPA.
This is the performance uplift,
and we can identify about 4 "classes" of results.
The first one there is no performance uplift,
because there is not much we can do,
and the time is probably not spent
within the execution engine.
And the ones around 3x are the ones
Where probably we are not triggering
manually specific optimizations,
but just the fact
that we are converting into native code
is giving us this performance uplift.
Then there is a bunch of other benchmarks
where the Lisp optimizations are triggering,
and the uplift is way bigger,
and then we have 3 benchmarks that at the time
are completely optimized out.
That means the compiler became "so smart"
that it was able to compute the result
in the compile time and just put the result
in the generated binary.
Let's discuss a little bit the compilation model.
This is an Hybrid one;
it's both JIT-like and Ahead-of-Time-like.
Emacs is composed of what we call an Emacs image,
essentially the Emacs binary that we start.
It's including all the C code,
plus all the Lisp code that we preload.
Then we have the rest of the Emacs Lisp codebase
that can be loaded just if it's required.
Same for the external packages, if we have any.
If we build an Emacs Lisp
with native compilation enabled, by default,
only the Emacs image will be native compiled.
All the other code will be compiled
on the fly when it's loaded and executed
the first time, if it's necessary.
Same for the packages, in a transparent way
and asynchronous way.
Also worth noting
that the result of this compilation
will be stored into a cache directory
within the home directory of the user.
So it will be reused in the following sessions
if the same file is loaded,
without having to recompile multiple times
the same file in different sessions.
It works a little bit like this:
When we load the byte-code for the first time,
we spawn a native compilation process.
Meanwhile we keep using the byte-code available.
When the native compilation is finished,
we hot-swap the definition of the functions
that are contained in the file
and start using the native code transparently.
We do this asynchronously,
and for more compilation units at the same time,
so it looks a little bit like this.
Let's try a quick demo of all of this machinery.
I've started a fresh Emacs
with native compilation support,
and at this moment nothing is going on.
We will test the machinery with Tetris,
because I can't imagine anything better
to test this.
What I do expect is that when I launch Tetris
it will be loaded, it will immediately
start execution of the byte-compiled version,
so we won't see any delay in the user experience,
and in the meanwhile, a parallel process
will start to native-compile Tetris itself.
When the native compilation will be finished,
the functions of all Tetris will be hot-swapped.
So we will not see any interruption.
So Tetris started, and it's running,
we have seen no delay, and in the meanwhile,
the native compilation probably already finished,
we can have a look.
In this I see the native compilation log buffer.
So we see that Tetris has been native compiled,
and all of its dependencies.
Now Tetris is still running,
but I can do "C-h f tetris"
and we can see that 'tetris'
is an interactive native compiled
Lisp function, so it has been native-compiled.
I can even disassemble if I want.
OK, so very cool.
I guess we can say this mechanism is working.
Also worth noting that if I go back
to the *Async-native-compile-log* buffer,
we see we have compiled another bunch of files.
I think these are because of my 'C-h f',
this help function command and disassemble,
and so on.
The first time you run Emacs, you will have,
from time to time, these processes spawned.
Emacs is "compiling itself",
and it's replacing the byte-code definition
with the native one. But after a few sessions,
you will not see this anymore,
because the output of this compilation,
as I mentioned, are stored in the user directory.
To conclude: Emacs with native compilation support
is coming up in Emacs 28,
that is gonna be the next major stable release
that will be released.
So we ought to celebrate with a big party,
I believe. But before going to the party,
I'd like to list a few points
that I think have been success factors
in upstreaming this work.
It has been extremely important
to get in touch with upstream as soon as possible,
as soon as I had a proof of concept.
It's been extremely important
to involve the community as much as possible,
and this included keeping a development blog,
and posts about that on emacs-devel,
and also producing material,
participating in conferences,
and giving presentations like the one I'm doing,
to explain what I was doing and how it works.
It has been extremely important, also,
to be able to rely on the upstream infrastructure.
So, to develop the software
as a feature branch in the official git,
but even more, I would say,
to use the official bug tracker
for solving bugs of this branch.
This gave the opportunity
to stay really in close touch with maintainers,
and senior developers of Emacs.
That helped me a lot. And at the same time
they were informed about what I was doing
and what was the status of this feature branch.
Extremely important.
And also I think it played a major role
to try to design this enormous patch
in a way that the impact on the current codebase
was minimized (at least as much as possible).
And also minimizing
the impact on the user operation of the software,
in this case Emacs.
So yes, mandatory Special Thanks:
Emacs developers, and especially maintainers
and senior developers like Stefan Monnier,
that helped me a lot across this long journey.
And, well, all the community
that really got so excited about this project
and gave me the energy
to go through all of this time and development
and bugs and solving, etc. etc.
So yes, it was a really exciting time,
and I think we have to look forward
and start thinking about how to improve all this
for the following years.
And that's it.
I think I should be online for questions.
Thank you very much.
[captions by John Cummings]