diff options
author | Sacha Chua <sacha@sachachua.com> | 2021-12-03 17:28:36 -0500 |
---|---|---|
committer | Sacha Chua <sacha@sachachua.com> | 2021-12-03 17:28:36 -0500 |
commit | 0dab88ba445dededd508cf1c16422cf055bd4b07 (patch) | |
tree | 30f07a580bc83df6acc696256581d26448fda9e9 /2021 | |
parent | 81873aefa52d34e3fca0253c5a47f73773e25611 (diff) | |
download | emacsconf-wiki-0dab88ba445dededd508cf1c16422cf055bd4b07.tar.xz emacsconf-wiki-0dab88ba445dededd508cf1c16422cf055bd4b07.zip |
Add native talk
Diffstat (limited to '')
-rw-r--r-- | 2021/captions/emacsconf-2021-native--emacs-lisp-native-compiler-current-status-and-future-developments--andrea-corallo--main.vtt | 2752 |
1 files changed, 2752 insertions, 0 deletions
diff --git a/2021/captions/emacsconf-2021-native--emacs-lisp-native-compiler-current-status-and-future-developments--andrea-corallo--main.vtt b/2021/captions/emacsconf-2021-native--emacs-lisp-native-compiler-current-status-and-future-developments--andrea-corallo--main.vtt new file mode 100644 index 00000000..0f7e6a3c --- /dev/null +++ b/2021/captions/emacsconf-2021-native--emacs-lisp-native-compiler-current-status-and-future-developments--andrea-corallo--main.vtt @@ -0,0 +1,2752 @@ +WEBVTT + +00:00.200 --> 00:02.340 +Hi everybody, my name is Andrea Corallo, + +00:02.440 --> 00:03.280 +and this presentation is about + +00:03.382 --> 00:05.802 +the Emacs Lisp Native Compiler -- + +00:05.904 --> 00:07.083 +having GNU Emacs able to + +00:07.184 --> 00:08.805 +compile and run Emacs Lisp as native code. + +00:08.907 --> 00:11.565 +This project has been my hobby project + +00:11.667 --> 00:14.009 +for the last about two years and a half + +00:14.111 --> 00:15.408 +And we will see a little bit + +00:15.509 --> 00:16.969 +where this project is coming from, + +00:17.070 --> 00:18.913 +where we are, and where we want to go. + +00:19.015 --> 00:20.571 +So essentially everything you need to know + +00:20.672 --> 00:22.132 +about the Emacs Lisp Native Compiler, + +00:22.232 --> 00:23.416 +and probably a little more. + +00:23.521 --> 00:26.535 +Just a little bit of context on Emacs Lisp. + +00:26.635 --> 00:30.175 +Well, Emacs Lisp is a programming language, + +00:30.278 --> 00:33.544 +it's indeed a Lisp, + +00:33.647 --> 00:36.619 +and one year ago I looked for some statistics, + +00:36.722 --> 00:38.742 +and I was kind of pleased to see + +00:38.842 --> 00:40.030 +-- surprised to see actually -- + +00:40.132 --> 00:40.972 +that it's still kind of popular + +00:41.073 --> 00:42.844 +as a programming language. + +00:42.944 --> 00:43.684 +It doesn't rank that bad + +00:43.785 --> 00:45.554 +against other programming languages. + +00:45.658 --> 00:48.387 +Also, the other important fact about Emacs Lisp + +00:48.487 --> 00:51.518 +is that there is a lot of Emacs Lisp out there, + +00:51.622 --> 00:55.922 +and this will have an impact [on this project.] + +00:56.025 --> 00:57.632 +It's a programming language that is capable + +00:57.734 --> 00:59.320 +of almost any task, so it's + +00:59.420 --> 01:01.728 +almost a general-purpose programming language, + +01:01.831 --> 01:04.116 +and this reflects on Emacs itself, + +01:04.217 --> 01:07.232 +that it's capable of almost any task. + +01:07.335 --> 01:09.679 +Indeed this "almost" is something we want to fix, + +01:09.781 --> 01:10.720 +because we want to do everything, + +01:10.820 --> 01:14.218 +we want [to do] all of our computing [with Emacs]. + +01:14.321 --> 01:16.523 +Also, an interesting aspect for me + +01:16.625 --> 01:18.903 +is that it's not specified by any standard. + +01:19.005 --> 01:22.507 +This implies it can evolve in a more agile way + +01:22.609 --> 01:25.667 +without having to change the standard, etc. + +01:25.770 --> 01:27.870 +And, in fact, it's kind of improving, + +01:27.970 --> 01:30.370 +I believe relatively fast. + +01:30.473 --> 01:32.791 +A little bit about Lisp in general. + +01:32.892 --> 01:34.952 +First, it's the best programming language ever, + +01:35.052 --> 01:35.712 +we all know it. + +01:35.813 --> 01:37.833 +It has a lot of very nice properties, + +01:37.934 --> 01:40.356 +like it's dynamic, it's homoiconic, etc. + +01:40.462 --> 01:42.394 +But the interesting thing for implementors, + +01:42.496 --> 01:43.476 +is that, in theory, + +01:43.576 --> 01:46.716 +it can be implemented with very few primitives. + +01:46.817 --> 01:49.485 +You build very few primitives that are like magic, + +01:49.590 --> 01:51.838 +and on top of this, + +01:51.938 --> 01:53.770 +you can implement the whole language. + +01:53.873 --> 01:55.759 +This sounds very nice, + +01:55.860 --> 01:57.280 +and very appealing for implementors + +01:57.381 --> 02:00.175 +meaning to implement a new Lisp implementation, + +02:00.279 --> 02:02.539 +or improving or modifying an existing one. + +02:02.641 --> 02:04.163 +So the question is: + +02:04.263 --> 02:07.663 +how many primitives do we have to implement + +02:07.764 --> 02:09.384 +if we want to change + +02:09.485 --> 02:13.203 +the GNU Emacs Lisp implementation? + +02:13.308 --> 02:20.128 +Unfortunately, not really as few as we would like. + +02:20.234 --> 02:25.294 +In GNU Emacs, we have about 1500 primitives, + +02:25.400 --> 02:27.970 +and the main reason for that is performance. + +02:28.071 --> 02:30.961 +Actually, GNU Emacs was written + +02:31.064 --> 02:34.292 +when performance was a big issue, + +02:34.393 --> 02:36.493 +and nowadays certain parts + +02:36.594 --> 02:38.486 +are still performance-critical. + +02:38.591 --> 02:40.295 +We have a lot of C code; + +02:40.395 --> 02:46.331 +30% of the GNU Emacs codebase is C code, + +02:46.435 --> 02:49.177 +and we have to maintain this. + +02:49.279 --> 02:52.859 +But not only that, this is the main barrier + +02:52.959 --> 02:55.579 +for people that tried in the past + +02:55.681 --> 02:57.663 +to change the Emacs Lisp implementation. + +02:57.765 --> 02:59.101 +Because not only do you have to + +02:59.202 --> 03:01.582 +replace these primitives, + +03:01.683 --> 03:03.907 +all of them or part of them, + +03:04.012 --> 03:06.384 +but sometimes they also share (these primitives), + +03:06.484 --> 03:07.952 +internal data structures. + +03:08.055 --> 03:09.745 +For instance, it's very difficult to say: + +03:09.846 --> 03:12.826 +Now I want to go from C + +03:12.926 --> 03:13.906 +to a different programming language + +03:14.006 --> 03:15.638 +for implementing these primitives. + +03:15.740 --> 03:17.028 +So this has been, effectively, + +03:17.128 --> 03:20.380 +the main barrier for doing this work. + +03:20.486 --> 03:22.090 +Another interesting aspect + +03:22.190 --> 03:23.870 +about the GNU Emacs implementation + +03:23.970 --> 03:26.190 +is that Lisp can run interpreted + +03:26.291 --> 03:28.647 +or byte-compiled for performance, + +03:28.752 --> 03:30.632 +and the byte compiler itself + +03:30.733 --> 03:32.673 +is written in Emacs Lisp. + +03:32.773 --> 03:35.033 +This implies that GNU Emacs has to go through + +03:35.134 --> 03:37.214 +a bootstrap procedure in order to be built. + +03:37.319 --> 03:38.715 +But it's kind of interesting + +03:38.815 --> 03:41.835 +for something that started as a text editor, + +03:41.937 --> 03:43.101 +or something like it. + +03:43.203 --> 03:47.877 +The byte-code that is Emacs Lisp + +03:47.979 --> 03:50.279 +when it's been byte compiled, + +03:50.379 --> 03:53.079 +it's running on a stack-based virtual machine + +03:53.180 --> 03:55.146 +that is implemented in C. + +03:55.253 --> 03:59.741 +OK, so I've listed a bunch of areas + +03:59.842 --> 04:02.074 +where Emacs Lisp could improve: + +04:02.178 --> 04:06.924 +Namespace, Extensibility, Performance, + +04:07.025 --> 04:11.239 +and Debuggability, and Diagnostic, we could say. + +04:11.346 --> 04:14.326 +This activity, this project in particular, + +04:14.428 --> 04:17.088 +is affecting primarily Performance + +04:17.189 --> 04:21.314 +and the performance area the Execution Engine. + +04:21.414 --> 04:22.570 +That said, + +04:22.671 --> 04:25.855 +I think it has an impact also on Extensibility, + +04:25.957 --> 04:27.811 +and I hope it will have an impact also + +04:27.913 --> 04:29.779 +on programming diagnostics, + +04:29.881 --> 04:32.501 +so giving better warnings, [unknown]. + +04:32.604 --> 04:36.694 +So which are the benefits + +04:36.795 --> 04:39.215 +of increasing the Emacs Lisp performance? + +04:39.316 --> 04:42.976 +Indeed, we will have, if we do that, + +04:43.078 --> 04:45.668 +programs that run faster. + +04:45.774 --> 04:48.738 +But the main implication of that + +04:48.840 --> 04:50.760 +is that we could write less C; + +04:50.860 --> 04:52.940 +we could maintain and debug less C. + +04:53.041 --> 04:56.437 +That is kind of a time-consuming task. + +04:56.542 --> 04:59.502 +And we could also allow for + +04:59.603 --> 05:01.723 +writing performance-critical extensions + +05:01.824 --> 05:03.804 +directly in Lisp + +05:03.909 --> 05:06.305 +without having to use systems like + +05:06.406 --> 05:09.686 +[I think there's a bunch.] + +05:09.927 --> 05:14.791 +These are very consistent benefits. + +05:14.899 --> 05:16.669 +OK, Project Goals, + +05:16.769 --> 05:18.829 +but I think the title of this slide + +05:18.930 --> 05:21.520 +maybe should be Project Requirements. + +05:21.623 --> 05:23.231 +So when I started this activity, + +05:23.331 --> 05:26.943 +I set some requirements for the project, + +05:27.049 --> 05:29.469 +with the main goal in mind: to go upstream. + +05:29.570 --> 05:31.494 +So I wanted to create something, + +05:31.594 --> 05:34.250 +a modified implementation of GNU Emacs, + +05:34.353 --> 05:36.675 +that was compatible + +05:36.776 --> 05:38.596 +as close to 100% as possible + +05:38.696 --> 05:40.514 +to the current implementation. + +05:40.620 --> 05:42.720 +And when I say "current implementation," + +05:42.820 --> 05:44.758 +I don't refer to what + +05:44.859 --> 05:46.879 +the Emacs Lisp Programming Manual specifies + +05:46.980 --> 05:49.804 +as expected behavior of the implementation, + +05:49.907 --> 05:52.207 +but really the implementation itself. + +05:52.309 --> 05:53.841 +This is because there are a lot of corner cases + +05:53.942 --> 05:56.390 +that are not specified by the manual, + +05:56.493 --> 05:58.093 +but programs do rely on that, + +05:58.193 --> 06:00.523 +and given there is a ton of Emacs Lisp + +06:00.625 --> 06:01.935 +already around, + +06:02.037 --> 06:05.437 +compatibility was definitely a major requirement. + +06:05.541 --> 06:07.727 +I wanted to produce something that had + +06:07.827 --> 06:09.587 +reduced impact on the Emacs codebase, + +06:09.687 --> 06:11.543 +at least as much as possible. + +06:11.644 --> 06:14.724 +So I didn't want to rewrite all of GNU Emacs. + +06:14.827 --> 06:17.609 +Indeed, because it would have been + +06:17.710 --> 06:21.068 +too much work for one single person, + +06:21.171 --> 06:25.371 +but also still thinking + +06:25.473 --> 06:29.153 +to an upstream outcome all of this time. + +06:29.258 --> 06:31.974 +Another requirement was to have + +06:32.076 --> 06:34.700 +no, or very reduced, impact on the user, + +06:34.804 --> 06:36.456 +so I didn't want to change + +06:36.556 --> 06:38.604 +the way Emacs is used by you. + +06:38.708 --> 06:40.618 +And last but not least, + +06:40.718 --> 06:42.848 +introducing new dependencies, + +06:42.951 --> 06:45.379 +those dependencies had to be Free Software, + +06:45.479 --> 06:47.191 +possibly GPL, + +06:47.295 --> 06:49.995 +and also an important requirement + +06:50.097 --> 06:52.941 +is that these dependencies had to be + +06:53.043 --> 06:55.143 +some kind of trusted software that we know + +06:55.243 --> 06:56.681 +is going to be maintained in the future. + +06:56.781 --> 06:59.261 +Given Emacs has been around since forever, + +06:59.363 --> 07:01.183 +and will be around forever and ever, + +07:01.285 --> 07:05.265 +this was another very important point. + +07:05.367 --> 07:08.707 +A little bit of history of this project/ + +07:08.808 --> 07:10.648 +a quick timeline. + +07:10.748 --> 07:14.388 +2019, in May, I did my first commit, + +07:14.490 --> 07:17.110 +I think it was when I tried to write + +07:17.210 --> 07:20.230 +my first primitive function ever in C, + +07:20.332 --> 07:21.752 +in GNU Emacs. + +07:21.852 --> 07:24.832 +And this was an attempt to try to compile + +07:24.934 --> 07:30.314 +a function that, once executed, returning [?] + +07:30.415 --> 07:33.755 +That was it. Six months after (about), + +07:33.857 --> 07:37.057 +I had something that was kind of working, + +07:37.157 --> 07:42.797 +So I was able to start up a semi-standard Emacs + +07:42.899 --> 07:44.879 +and then to compile and load, + +07:44.981 --> 07:47.221 +and replacing most of the functions + +07:47.321 --> 07:50.921 +that I had defined floating in my Lisp universe. + +07:51.022 --> 07:54.202 +Those functions are the functions + +07:54.304 --> 07:55.944 +that are essentially composing Emacs, + +07:56.044 --> 07:57.884 +at least the Lisp side of Emacs. + +07:57.984 --> 08:01.084 +A lot of features were missing, + +08:01.186 --> 08:03.426 +like I had no Garbage Collector support, + +08:03.526 --> 08:07.006 +no bootstrap, I was not optimizing these functions + +08:07.108 --> 08:10.068 +Because optimization [would be broken]. + +08:10.169 --> 08:12.749 +No image dump support, etc. + +08:12.850 --> 08:16.330 +But I think this proved the design could work. + +08:16.431 --> 08:19.171 +So I sent to email to emacs-devel. I said + +08:19.272 --> 08:20.852 +I have this stuff I'm working on, + +08:20.953 --> 08:24.613 +and I wanted some feedback from the upstream + +08:24.714 --> 08:27.534 +to see if there was some interest. + +08:27.635 --> 08:30.455 +I believe the outcome of this was positive + +08:30.557 --> 08:32.837 +because about one month after, + +08:32.937 --> 08:35.697 +I pushed my branch within the Emacs git + +08:35.798 --> 08:38.438 +as a feature branch, and shortly after, + +08:38.539 --> 08:42.679 +we started to use the bug tracker to track bugs. + +08:42.779 --> 08:45.479 +So essentially we moved the development + +08:45.580 --> 08:50.040 +on the upstream infrastructure. + +08:50.140 --> 08:55.620 +I believe two years after the first commit, + +08:55.721 --> 08:57.781 +the project was merged + +08:57.882 --> 09:00.922 +after literally hundreds of bugs solved, + +09:01.022 --> 09:03.842 +and improvements, suggestions [unknown] + +09:03.943 --> 09:08.623 +and this was about six months ago. + +09:08.723 --> 09:12.363 +Before discussing how the native compiler works, + +09:12.464 --> 09:14.224 +I think it's worth looking at + +09:14.324 --> 09:17.644 +how Lisp is implemented in GNU Emacs. + +09:17.745 --> 09:19.305 +We have Lisp_Objects + +09:19.405 --> 09:21.945 +floating around our Lisp universe, + +09:22.045 --> 09:23.905 +and they are internally represented in this way. + +09:24.006 --> 09:25.606 +We have what is called a tagged pointer, + +09:25.706 --> 09:27.406 +that is just a regular pointer + +09:27.506 --> 09:29.286 +that is pointing to the area of memory + +09:29.386 --> 09:31.926 +where we hold the real data of the object. + +09:32.027 --> 09:34.187 +But within this tagged pointer, + +09:34.287 --> 09:36.747 +we reserve a few bits + +09:36.848 --> 09:38.968 +to indicate the type of object we are pointing to. + +09:39.068 --> 09:40.528 +This is important because + +09:40.628 --> 09:42.288 +each time we access an object, + +09:42.388 --> 09:46.168 +we have to typically check those bits + +09:46.269 --> 09:49.029 +to check that the object we are manipulating + +09:49.129 --> 09:50.529 +is of the right kind, + +09:50.630 --> 09:52.530 +remove those bits, and, if we are happy, + +09:52.630 --> 09:55.810 +access the object, otherwise [unknown]. + +09:55.910 --> 09:57.690 +All the objects are like this, + +09:57.791 --> 09:59.731 +except for typically Fixnums, + +09:59.831 --> 10:01.031 +that are small integers + +10:01.131 --> 10:04.891 +that we manage to fit directly within the pointer. + +10:04.992 --> 10:07.292 +Also for manipulating Fixnums, + +10:07.392 --> 10:09.412 +we have to check the tag bits each time. + +10:09.513 --> 10:13.173 +Whenever we are not sure of the type of object + +10:13.273 --> 10:16.493 +we are manipulating (read: almost every time), + +10:16.594 --> 10:18.914 +we have to check those bits and remove those bits + +10:19.014 --> 10:21.754 +before doing any manipulation on the Fixnum. + +10:21.854 --> 10:26.014 +How Emacs Lisp is byte-compiled and executed + +10:26.115 --> 10:27.775 +in, let's call it, "Vanilla". + +10:27.875 --> 10:30.055 +If we have a Lisp expression of this kind: + +10:30.156 --> 10:32.776 +We take the variable 'a' we do plus 2, + +10:32.876 --> 10:34.876 +and then we multiply the result by 3, + +10:34.976 --> 10:37.376 +the byte compiler will produce this LAP code. + +10:37.477 --> 10:38.497 +LAP code is essentially + +10:38.597 --> 10:40.017 +the assembly for the byte-code, + +10:40.117 --> 10:43.697 +so it's the "intermediate representation" + +10:43.798 --> 10:48.458 +that will assembled into byte-code. (.elc files) + +10:48.558 --> 10:50.738 +How is this program executed? + +10:50.839 --> 10:53.639 +As I mentioned, it's executed in a virtual machine + +10:53.739 --> 10:55.699 +that is stack-based, + +10:55.800 --> 10:58.540 +but we start with an execution stack that's empty, + +10:58.640 --> 11:01.680 +and a stack pointer pointing to its bottom. + +11:01.780 --> 11:04.280 +And we execute the first instruction, + +11:04.380 --> 11:07.120 +that is pushing in the stack the value of 'a', + +11:07.220 --> 11:10.360 +in this case, 100. Then we push the constant 2. + +11:10.460 --> 11:12.400 +Then we do the summation, + +11:12.500 --> 11:14.040 +and we have the result in the stack. + +11:14.140 --> 11:16.860 +Same: we push the constant 3, + +11:16.960 --> 11:17.620 +we do the multiplication, + +11:17.720 --> 11:19.260 +and we will be able to return. + +11:19.360 --> 11:22.700 +Now, what's good and what's bad about this? + +11:22.800 --> 11:25.800 +A good thing is that it's very simple + +11:25.900 --> 11:27.460 +to start from Lisp + +11:27.560 --> 11:31.320 +and compile this kind of LAP output. + +11:31.420 --> 11:34.240 +At least it's reasonably simple. + +11:34.340 --> 11:36.240 +The compiler is not that complex. + +11:36.340 --> 11:39.000 +The bad thing is that all this machinery + +11:39.100 --> 11:40.660 +-- push and pop, etc. -- + +11:40.760 --> 11:44.320 +it's very different from how a modern CPU works. + +11:44.420 --> 11:45.720 +Because modern CPUs, + +11:45.820 --> 11:47.660 +they are not stack-based anymore + +11:47.760 --> 11:50.900 +but they have instead a fixed number of registers, + +11:51.000 --> 11:54.020 +and they work with assignment and operation + +11:54.120 --> 11:55.040 +within these registers + +11:55.140 --> 11:57.040 +that are generally called "general-purpose." + +11:57.140 --> 11:59.260 +So to execute this LAP program, + +11:59.360 --> 12:00.760 +there is another program, + +12:00.860 --> 12:02.600 +that is the implementation of the VM itself + +12:02.700 --> 12:06.400 +that is doing conversion during runtime. + +12:06.500 --> 12:08.320 +So it's interpreting the LAP program + +12:08.420 --> 12:11.360 +and it's converting it into instructions + +12:11.460 --> 12:13.180 +that we can execute on the CPU. + +12:13.280 --> 12:14.940 +This conversion is done each time + +12:15.040 --> 12:17.100 +we will run some byte-code. + +12:17.200 --> 12:19.760 +And it's something that we want to avoid. + +12:19.860 --> 12:21.560 +Instead of this live conversion, + +12:21.660 --> 12:26.220 +we want to convert once: + +12:26.320 --> 12:28.920 +our Lisp program into native code, + +12:29.020 --> 12:32.380 +that is, a binary program that can be executed + +12:32.480 --> 12:34.400 +directly by our CPU. + +12:34.500 --> 12:36.680 +We want to save all this unnecessary conversion + +12:36.780 --> 12:39.000 +that we do each time we are running a program + +12:39.100 --> 12:39.760 +while we are running it. + +12:39.860 --> 12:42.060 +And we want to do this process just once, + +12:42.160 --> 12:43.740 +when we are compiling. + +12:43.840 --> 12:46.240 +That's the main goal of this activity. + +12:46.340 --> 12:50.200 +How is the byte compiler implemented? + +12:50.300 --> 12:53.940 +As any compiler it's a pipeline of transformations. + +12:54.040 --> 12:58.560 +We go through macro expansion, closure conversion, + +12:58.660 --> 13:02.120 +we have a bunch of source level optimization. + +13:02.220 --> 13:03.940 +Then we go into LAP, + +13:04.040 --> 13:06.800 +that's the transformation we are interested in, + +13:06.900 --> 13:10.600 +and after a few optimizations on LAP, + +13:10.700 --> 13:14.140 +LAP is assembled into byte-code. + +13:14.240 --> 13:16.880 +So if we [list it] + +13:16.980 --> 13:19.320 +in terms of intermediate representations, + +13:19.420 --> 13:23.600 +we can simplify this pipeline like this. + +13:23.700 --> 13:26.100 +We start with Lisp, and at a certain point + +13:26.200 --> 13:29.520 +we are manipulating the program in LAP form, + +13:29.620 --> 13:31.980 +and then at the end we produce the byte-code + +13:32.080 --> 13:34.560 +that is the .elc file that [you run] + +13:34.660 --> 13:37.660 +What I wanted to realize was something like this. + +13:37.760 --> 13:41.200 +I wanted to start from LAP, do something, + +13:41.300 --> 13:44.600 +and jump into GCC using libgccjit + +13:44.700 --> 13:45.560 +and in particular + +13:45.660 --> 13:48.000 +the libgccjit Intermediate Representation + +13:48.100 --> 13:50.340 +that we will discuss. + +13:50.440 --> 13:53.120 +Now, why I wanted to do something like this? + +13:53.220 --> 13:57.300 +Essentially, writing a compiler from scratch + +13:57.400 --> 14:01.520 +for Emacs Lisp would have been a very big task. + +14:01.620 --> 14:05.120 +So I wanted to rely on, as much as I could, + +14:05.220 --> 14:07.180 +the Emacs Lisp byte compiler, + +14:07.280 --> 14:10.280 +because I had to produce something + +14:10.380 --> 14:12.920 +that was as compatible as possible + +14:13.020 --> 14:14.240 +to the current implementation. + +14:14.340 --> 14:18.340 +So this was (I believe) a very good idea + +14:18.440 --> 14:20.520 +to save an enormous quantity of work + +14:20.620 --> 14:22.340 +and to produce something + +14:22.440 --> 14:24.940 +that was compatible in terms of semantics + +14:25.040 --> 14:26.820 +with the original implementation. + +14:26.920 --> 14:30.660 +Also, I didn't want to implement a code generator + +14:30.760 --> 14:32.860 +for each architecture we were targeting, + +14:32.960 --> 14:35.900 +nor wanted to implement all these optimizations + +14:36.000 --> 14:37.840 +that are already in GCC, + +14:37.940 --> 14:40.120 +so I thought it was a good idea + +14:40.220 --> 14:44.720 +to rely on an existing compiler like GCC [?]. + +14:44.820 --> 14:47.240 +Let's talk about libgccjit. + +14:47.340 --> 14:50.180 +It was added by David Malcolm in GCC 5 + +14:50.280 --> 14:55.680 +It allows you to describe a C-ish semantic to GCC + +14:55.780 --> 14:57.380 +and have it compile. + +14:57.480 --> 15:01.400 +It's good for [reading] Jitters or AoT compilers. + +15:01.500 --> 15:04.400 +And if we talk about GCC: + +15:04.500 --> 15:07.180 +it's a compiler, it's a very good one, + +15:07.280 --> 15:09.340 +it has support for a remarkable number + +15:09.440 --> 15:11.640 +of target architectures. + +15:11.740 --> 15:15.080 +It's also very good at generating [fast code], + +15:15.180 --> 15:17.980 +and it's been around for a long time; + +15:18.080 --> 15:21.200 +I believe it's like 1 year younger than Emacs. + +15:21.300 --> 15:23.220 +It's still very well maintained, + +15:23.320 --> 15:25.840 +and we can assume it will be maintained + +15:25.940 --> 15:28.920 +for quite a while. And, as I mentioned, + +15:29.020 --> 15:31.120 +this was a very important point. + +15:31.220 --> 15:33.860 +Also, it's GPL; it's Free Software, + +15:33.960 --> 15:36.120 +and it's developed under the GNU umbrella, + +15:36.220 --> 15:40.360 +so I thought it was a very good option. + +15:40.460 --> 15:43.300 +So we can imagine a simple translation + +15:43.400 --> 15:46.400 +that goes from LAP to this subset of C + +15:46.500 --> 15:48.880 +that we can describe to libgccjit. + +15:48.980 --> 15:52.880 +This simple translation we can see here, + +15:52.980 --> 15:55.680 +it's actually pretty trivial. + +15:55.780 --> 15:58.500 +Instead of doing operations + +15:58.600 --> 16:02.060 +within the execution stack we have seen before, + +16:02.160 --> 16:05.080 +we have just an array that is replacing it, + +16:05.180 --> 16:07.380 +and it's called 'local' in this case, + +16:07.480 --> 16:12.360 +and we have assignments within this array, + +16:12.460 --> 16:15.140 +so that they are done in place of the original + +16:15.240 --> 16:17.100 +push and pop activity of the stack. + +16:17.200 --> 16:18.280 +The nice thing is that, + +16:18.380 --> 16:20.260 +when you have done this translation, + +16:20.360 --> 16:23.160 +GCC will be able to optimize this, + +16:23.260 --> 16:25.840 +and remove all the unnecessary operations, + +16:25.940 --> 16:27.180 +and generate code + +16:27.280 --> 16:29.660 +for the specific CPU you are targeting, + +16:29.760 --> 16:32.020 +[which will be running your code]. + +16:32.120 --> 16:34.920 +This sounds great; it sounds like + +16:35.020 --> 16:37.440 +a very simple and effective translation, + +16:37.540 --> 16:39.920 +and in fact the first iteration of my compiler + +16:40.020 --> 16:41.120 +was doing just this. + +16:41.220 --> 16:45.320 +It was essentially a big C function + +16:45.420 --> 16:48.240 +that was taking LAP and doing this conversion + +16:48.340 --> 16:50.120 +describing the output to libgccjit. + +16:50.220 --> 16:53.720 +Unfortunately, if you do this, + +16:53.820 --> 16:55.740 +you will discover that you have + +16:55.840 --> 17:00.080 +a performance upper bound limit of about 3x. + +17:00.180 --> 17:04.360 +So it was an option, + +17:04.460 --> 17:06.560 +but I thought it was a good occasion + +17:06.660 --> 17:08.980 +for trying to do something more. + +17:09.080 --> 17:11.640 +And doing something more means + +17:11.740 --> 17:13.680 +implementing a smarter compiler + +17:13.780 --> 17:17.180 +that is doing some advanced analysis on the code, + +17:17.280 --> 17:18.640 +and will be able to perform + +17:18.740 --> 17:20.700 +Lisp-specific optimizations + +17:20.800 --> 17:22.480 +-- optimizations that take advantage of + +17:22.580 --> 17:24.960 +the specific Lisp semantics, + +17:25.060 --> 17:27.760 +something that GCC is not aware of. + +17:27.860 --> 17:31.240 +And while I was thinking about that, + +17:31.340 --> 17:34.000 +I thought that having a smarter compiler + +17:34.100 --> 17:38.120 +had also other advantages, like a smarter compiler + +17:38.220 --> 17:40.120 +that understands the semantics + +17:40.220 --> 17:41.820 +of the programming language being compiled + +17:41.920 --> 17:43.680 +would be also capable of + +17:43.780 --> 17:45.620 +giving feedback to the programmers, + +17:45.720 --> 17:47.920 +like better warnings and errors. + +17:48.020 --> 17:51.360 +So I was really fascinated about this idea, + +17:51.460 --> 17:53.240 +and I wanted to change my implementation + +17:53.340 --> 17:55.920 +because I was not really happy about it. + +17:56.020 --> 17:58.320 +I had a lot of C code in terms of + +17:58.420 --> 18:02.100 +lines that were not doing any smart job. + +18:02.200 --> 18:07.380 +And I wanted to write all the interesting logic + +18:07.480 --> 18:09.940 +[in Lisp]. + +18:10.040 --> 18:12.560 +So optimizing outside GCC + +18:12.660 --> 18:15.840 +before jumping into GCC, + +18:15.940 --> 18:20.500 +as I mentioned, has two main targets: + +18:20.600 --> 18:23.060 +Either optimize the code before going into GCC, + +18:23.160 --> 18:25.380 +or present to GCC some code + +18:25.480 --> 18:27.740 +that we know GCC can optimize effectively. + +18:27.840 --> 18:30.800 +And also, this will give, as I mentioned, + +18:30.900 --> 18:32.660 +better options for the compiler + +18:32.760 --> 18:34.420 +to provide warnings, errors + +18:34.520 --> 18:36.340 +-- better diagnostics. + +18:36.440 --> 18:38.200 +So this is pretty much + +18:38.300 --> 18:40.640 +what the native compiler looks like nowadays, + +18:40.740 --> 18:42.720 +in terms of passes. + +18:42.820 --> 18:44.560 +We have a list of passes, + +18:44.660 --> 18:46.620 +each of which is taking an input + +18:46.720 --> 18:48.120 +and producing an output. + +18:48.220 --> 18:51.040 +So it's doing either analysis on the program + +18:51.140 --> 18:52.640 +that's being passed, + +18:52.740 --> 18:54.760 +or it's performing a transformation. + +18:54.860 --> 18:57.900 +All of these passes are implemented in Lisp, + +18:58.000 --> 19:00.440 +and only the last pass is implemented in C. + +19:00.540 --> 19:05.060 +That is the one that is talking to libgccjit. + +19:05.160 --> 19:07.760 +To do that, I have introduced + +19:07.860 --> 19:10.640 +a new intermediate representation + +19:10.740 --> 19:13.960 +that I call LIMPLE, as a tribute to GCC GIMPLE, + +19:14.060 --> 19:17.220 +that is the main internal representation of GCC, + +19:17.320 --> 19:20.600 +at least one of the main ones. + +19:20.700 --> 19:25.080 +Introducing a new intermediate representation + +19:25.180 --> 19:27.720 +-- a new way of representing my program -- + +19:27.820 --> 19:29.540 +solved a bunch of problems. + +19:29.640 --> 19:33.200 +First, it allowed me to implement + +19:33.300 --> 19:37.280 +non-trivial analysis and transformations, + +19:37.380 --> 19:40.000 +the ones I needed in my compiler pipeline. + +19:40.100 --> 19:42.160 +But also, it solved the problem of + +19:42.260 --> 19:43.840 +what was the boundary between + +19:43.940 --> 19:46.120 +what I had to implement in Lisp, + +19:46.220 --> 19:48.040 +and what in C. + +19:48.140 --> 19:49.040 +Because once I had + +19:49.140 --> 19:51.860 +my intermediate representation defined, + +19:51.960 --> 19:53.780 +essentially the boundary between Lisp and C + +19:53.880 --> 19:55.640 +is just a function, that is, + +19:55.740 --> 19:57.780 +the one that is implementing the final pass. + +19:57.880 --> 19:59.220 +That is taking, as an input, + +19:59.320 --> 20:01.620 +all of my programs in LIMPLE representation + +20:01.720 --> 20:03.880 +and it's doing [his bit]. + +20:03.980 --> 20:07.980 +So I was convinced this design at least had sense. + +20:08.080 --> 20:10.120 +When we go through some of these passes, + +20:10.220 --> 20:12.600 +just to give you an idea of what these are doing: + +20:12.700 --> 20:14.560 +the first pass is just responsible for + +20:14.660 --> 20:18.100 +spilling the LAP from the byte compiler + +20:18.200 --> 20:20.160 +that effectively here we are using as a front end + +20:20.260 --> 20:21.780 +for our compiler pipeline. + +20:21.880 --> 20:24.040 +The second pass, called 'limplify', + +20:24.140 --> 20:27.920 +will be in charge of converting LAP into LIMPLE. + +20:28.020 --> 20:31.260 +LIMPLE is an intermediate representation + +20:31.360 --> 20:32.860 +that is Control Flow Graph based, + +20:32.960 --> 20:34.700 +and it's capable of SSA. + +20:34.800 --> 20:38.640 +So we can have a look to what this means. + +20:38.740 --> 20:41.300 +Let's assume we have our LAP program, + +20:41.400 --> 20:42.520 +as any program, + +20:42.620 --> 20:43.960 +that's a simple list of instructions + +20:44.060 --> 20:45.640 +that we will execute one after the other. + +20:45.740 --> 20:47.360 +Some of these instructions + +20:47.460 --> 20:49.200 +are special instructions + +20:49.300 --> 20:51.240 +that we call conditional branches, + +20:51.340 --> 20:52.800 +where we check for a condition, + +20:52.900 --> 20:54.320 +and if this is verified, + +20:54.420 --> 20:56.940 +we jump to a different address within the program. + +20:57.040 --> 20:59.580 +(Addresses that here we are calling 'labels'.) + +20:59.680 --> 21:03.440 +So we can split our program in chunks, + +21:03.540 --> 21:08.200 +and those chunks we execute without interruption, + +21:08.300 --> 21:10.460 +so we always enter from the top of those, + +21:10.560 --> 21:12.600 +and we exit from the bottom. + +21:12.700 --> 21:15.980 +We can name those, and split them apart, + +21:16.080 --> 21:18.980 +and these are what we call basic blocks. + +21:19.080 --> 21:22.360 +And now we have a bunch of these basic blocks + +21:22.460 --> 21:23.380 +that are floating, + +21:23.480 --> 21:25.020 +and they are not any more sorted. + +21:25.120 --> 21:25.920 +This is what is called + +21:26.020 --> 21:28.680 +a Control Flow Graph based representation. + +21:28.780 --> 21:31.400 +Now we can get into the SSA topic. + +21:31.500 --> 21:33.900 +That stands for Static Single Assignment. + +21:34.000 --> 21:35.860 +I don't want to get into the details, + +21:35.960 --> 21:36.720 +but just give you a feeling. + +21:36.820 --> 21:38.480 +I added into our basic blocks + +21:38.580 --> 21:41.400 +in our Control Flow Graph a few assignments. + +21:41.500 --> 21:43.840 +We will transform this into SSA + +21:43.940 --> 21:45.040 +just for the variable 'x', + +21:45.140 --> 21:47.360 +just for the sake of demonstrating it. + +21:47.460 --> 21:49.760 +This is done through a number of phases + +21:49.860 --> 21:51.760 +that are essentially some analysis, + +21:51.860 --> 21:52.600 +mainly renaming. + +21:52.700 --> 21:55.560 +But the outcome, the one we see here, + +21:55.660 --> 21:59.120 +looks quite similar to the original one, + +21:59.220 --> 22:01.120 +but we can see that the variable 'x' + +22:01.220 --> 22:01.960 +has been renamed. + +22:02.060 --> 22:03.400 +And now we don't have anymore just one, + +22:03.500 --> 22:06.140 +but a number of these variables. + +22:06.240 --> 22:08.000 +The interesting property is that + +22:08.100 --> 22:10.880 +each of these variables is assigned just once. + +22:10.980 --> 22:13.240 +And this allows for the compiler + +22:13.340 --> 22:16.760 +to do prediction of the value of that variable, + +22:16.860 --> 22:19.040 +depending on the position + +22:19.140 --> 22:19.840 +within the Control Flow Graph. + +22:19.940 --> 22:21.980 +This is very important. For instance, + +22:22.080 --> 22:23.440 +a very simple case is 'x1' + +22:23.540 --> 22:27.440 +that we see is assigned once by definition, + +22:27.540 --> 22:29.300 +in particular here at the beginning. + +22:29.400 --> 22:31.040 +Here it's very simple to understand + +22:31.140 --> 22:33.080 +that x1 will have the value 3. + +22:33.180 --> 22:35.380 +While, for instance, it's more difficult to prove + +22:35.480 --> 22:37.060 +what is going to be the value of x5, + +22:37.160 --> 22:38.620 +because it's calling a function, + +22:38.720 --> 22:41.940 +or we don't know at the moment what x4 is. + +22:42.040 --> 22:46.240 +So the compiler will gain the capability + +22:46.340 --> 22:48.460 +to do prediction on all the variables, + +22:48.560 --> 22:50.620 +and the more we get information on one variable, + +22:50.720 --> 22:54.980 +the more we can prove about the others. + +22:55.460 --> 22:57.280 +Coming back to our passes, the next one + +22:57.380 --> 22:59.320 +is forward propagation. + +22:59.420 --> 23:00.600 +This pass is responsible for + +23:00.700 --> 23:03.240 +doing what I briefly mentioned just before: + +23:03.340 --> 23:07.160 +doing proof over all the different variables + +23:07.260 --> 23:09.620 +in different positions of the Control Flow Graph, + +23:09.720 --> 23:12.700 +about the values, types, or ranges. + +23:12.800 --> 23:15.080 +This pass is also responsible for + +23:15.180 --> 23:16.920 +executing functions + +23:17.020 --> 23:18.640 +when we know that the function has no side effect + +23:18.740 --> 23:20.520 +and the pass managed to + +23:20.620 --> 23:22.440 +prove all the values of its argument. + +23:22.540 --> 23:24.800 +So the function is then executed at compile time + +23:24.900 --> 23:26.700 +and it doesn't even exist anymore + +23:26.800 --> 23:27.880 +in the produced code. + +23:27.980 --> 23:30.300 +Then we have another pass, this is + +23:30.400 --> 23:33.420 +an example of a pass that is very specific: + +23:33.520 --> 23:36.120 +it's trying to remove the call to funcall + +23:36.220 --> 23:38.040 +when those are not necessary. + +23:38.140 --> 23:39.760 +There are a couple situations + +23:39.860 --> 23:42.200 +where this is very useful. + +23:42.300 --> 23:45.240 +And not only is this beneficial + +23:45.340 --> 23:47.560 +because we are generating better code, + +23:47.660 --> 23:49.245 +but when we manage to do that, + +23:49.345 --> 23:52.000 +we allow GCC better analysis over the code, + +23:52.100 --> 23:54.440 +because GCC knows nothing about funcall. + +23:54.540 --> 23:57.400 +So if we are calling, from 'foo', directly, 'bar', + +23:57.500 --> 24:01.280 +for GCC it's way easier to do its analysis + +24:01.380 --> 24:03.360 +on top of this code. + +24:03.460 --> 24:06.240 +Another interesting pass we can mention is 'tco'. + +24:06.340 --> 24:08.800 +This is performing Tail Recursion Elimination. + +24:08.900 --> 24:11.880 +It allows a more functional programming style, + +24:11.980 --> 24:13.220 +if you want. + +24:13.320 --> 24:14.280 +We can jump to the last pass + +24:14.380 --> 24:16.200 +that is called 'final', and as I mentioned, + +24:16.300 --> 24:17.520 +this one is responsible for + +24:17.620 --> 24:19.880 +taking our program in LIMPLE representation + +24:19.980 --> 24:24.900 +and describing it to libgccjit in the gccjit IR. + +24:25.000 --> 24:27.480 +That's the main task. It's also + +24:27.580 --> 24:29.520 +defining inline functions + +24:29.620 --> 24:32.400 +for accessing fundamental data types, and so on. + +24:32.500 --> 24:34.460 +This pass is also responsible for + +24:34.560 --> 24:36.280 +using some of the predictions + +24:36.380 --> 24:39.560 +done by previous passes to generate better code. + +24:39.660 --> 24:41.320 +Things we had to add + +24:41.420 --> 24:43.880 +to have all of this machinery work + +24:43.980 --> 24:45.240 +and to be controllable: + +24:45.340 --> 24:47.480 +The first one is an opt called 'native-comp-speed' + +24:47.580 --> 24:49.920 +and it's equivalent to Common Lisp's 'speed'. + +24:50.020 --> 24:51.920 +It represents the optimization level. + +24:52.020 --> 24:53.400 +The default is 2 and is + +24:53.500 --> 24:55.400 +the maximum optimization level + +24:55.500 --> 24:58.600 +that is meant to reflect + +24:58.700 --> 25:00.860 +all the original semantics of Emacs Lisp. + +25:00.960 --> 25:02.480 +So it's the one that should be used by default. + +25:02.580 --> 25:04.640 +The second one is 'compilation unit' + +25:04.740 --> 25:05.960 +and it's a kind of new object + +25:06.060 --> 25:11.080 +that has been added to Emacs. + +25:11.180 --> 25:12.960 +Let's have a look to + +25:13.060 --> 25:14.360 +how the Garbage Collector works in this case. + +25:14.460 --> 25:15.840 +The GNU Emacs Garbage Collector + +25:15.940 --> 25:18.660 +is a simple mark-and-sweep garbage collector. + +25:18.760 --> 25:21.420 +It does a tree walk through all the objects + +25:21.520 --> 25:25.100 +and follows references from one object to another. + +25:25.200 --> 25:27.960 +All the objects reachable during the mark phase + +25:28.060 --> 25:31.240 +will be kept in our Lisp universe. + +25:31.340 --> 25:33.680 +All the other ones will be freed. + +25:33.780 --> 25:35.080 +In this case we have a bunch of functions, + +25:35.180 --> 25:38.760 +'foo1', 'foo2', 'bar1', etc., that are defined. + +25:38.860 --> 25:40.320 +When a function is defined, + +25:40.420 --> 25:42.400 +it's accessible through its symbol, + +25:42.500 --> 25:44.360 +so we have the symbol referring to the function. + +25:44.460 --> 25:47.720 +The function, in this case a native-compiled one, + +25:47.820 --> 25:50.040 +is referring to the compilation unit. + +25:50.140 --> 25:53.020 +The compilation unit is essentially + +25:53.120 --> 25:58.600 +the ELF file that has been compiled, + +25:58.700 --> 26:01.160 +and contains all those functions + +26:01.260 --> 26:03.240 +that came from the original .el file, + +26:03.340 --> 26:05.100 +and that we have loaded into memory. + +26:05.200 --> 26:10.000 +If, for instance, 'bar1 and 'bar2 are undefined, + +26:10.100 --> 26:14.200 +functions [3] and 4 will be no longer reachable, + +26:14.300 --> 26:16.040 +and we will be able to free them + +26:16.140 --> 26:18.160 +and unload the compilation unit. + +26:18.260 --> 26:21.200 +We discussed quite a lot about Control Flow Graph, + +26:21.300 --> 26:23.560 +SSA, and a lot of boring stuff, + +26:23.660 --> 26:25.400 +and I promised you that we are doing + +26:25.500 --> 26:27.320 +a lot of interesting proofs over variables, + +26:27.420 --> 26:30.220 +So let's have some examples of them. + +26:30.320 --> 26:31.840 +Let's jump into a quick demo + +26:31.940 --> 26:34.480 +to see what all of this abstract theory + +26:34.580 --> 26:37.680 +and this esoteric propagation engine can do for us + +26:37.780 --> 26:39.240 +and how the user can interact with it. + +26:39.340 --> 26:42.100 +I've defined a bunch of functions, + +26:42.200 --> 26:45.240 +and I will native-compile and load it. + +26:47.500 --> 26:48.840 +Alright, Emacs Lisp native compiled and loaded. + +26:48.940 --> 26:52.320 +At this point, I can disassemble 'foo1' + +26:52.420 --> 26:56.320 +to make sure it's native code and I'm not lying. + +26:56.420 --> 26:58.500 +These are the instructions + +26:58.600 --> 27:01.320 +that will be executed directly by my CPU + +27:01.420 --> 27:03.620 +when I call this function. + +27:03.720 --> 27:07.520 +Alright, very cool. + +27:07.620 --> 27:16.080 +Now, [Lisp:] (symbol-function #'foo1) + +27:16.180 --> 27:19.720 +Interestingly, this is returning a subroutine, + +27:19.820 --> 27:21.820 +as it would be a primitive function. + +27:21.920 --> 27:23.700 +Because this is native code, + +27:23.800 --> 27:24.840 +even if it's written in Lisp, + +27:24.940 --> 27:26.340 +has been converted to native code + +27:26.440 --> 27:29.200 +as if it's a primitive function. + +27:29.300 --> 27:31.560 +But we can do also a new thing: + +27:31.660 --> 27:34.440 +asking for the type of the subroutine. + +27:34.540 --> 27:38.360 +Alright, very cool. It says this is a function, + +27:38.460 --> 27:40.280 +it's taking one argument of type 't' + +27:40.380 --> 27:41.560 +(that means anything + +27:41.660 --> 27:42.960 +because we don't have any information), + +27:43.060 --> 27:45.200 +and is returning a type 't', + +27:45.300 --> 27:47.380 +so also there we don't have much information. + +27:47.480 --> 27:49.700 +OK, very cool, but not very useful. + +27:49.800 --> 27:53.680 +Let's see #'foo2. #'foo2 is slightly different, + +27:53.780 --> 27:55.840 +it doesn't take any argument, but it's returning + +27:55.940 --> 27:58.040 +an integer included between 3 and 3. + +27:58.140 --> 28:01.360 +Wow, amazing! + +28:01.460 --> 28:04.040 +Let's get into something a little more complex: + +28:04.140 --> 28:09.440 +#'foo3 takes one argument we know nothing about, + +28:09.540 --> 28:11.740 +but it's returning a number. + +28:11.840 --> 28:13.280 +And why it's returning a number? + +28:13.380 --> 28:16.320 +Essentially because 1+ is returning a number, + +28:16.420 --> 28:18.880 +and in all the other cases, + +28:18.980 --> 28:20.640 +it would signal an error + +28:20.740 --> 28:23.380 +if it's not happy about its input argument. + +28:23.480 --> 28:27.760 +Let's have a look to #'foo4. + +28:27.860 --> 28:32.920 +#'foo4 is a little bit more complex. + +28:33.020 --> 28:34.680 +It will return nil + +28:34.780 --> 28:37.400 +if the 'when' condition is not satisfied, + +28:37.500 --> 28:39.720 +so it's type 'null' here. + +28:39.820 --> 28:41.200 +It can return a floating point; + +28:41.300 --> 28:43.600 +we don't do propagation of floating point so far, + +28:43.700 --> 28:47.080 +or it can return any integer between 4 and 9. + +28:47.180 --> 28:52.840 +Wow. Let's go on with #'foo5. + +28:52.940 --> 28:55.760 +#'foo5 is even more complex + +28:55.860 --> 28:57.200 +because other than + +28:57.300 --> 28:59.180 +having to satisfy this condition, + +28:59.280 --> 29:02.280 +we can see that the result of the propagation + +29:02.380 --> 29:03.800 +of this complex condition + +29:03.900 --> 29:05.460 +is propagated also across the 'plus'. + +29:05.560 --> 29:08.240 +So this foo5 can return nil, + +29:08.340 --> 29:09.720 +a floating point we know nothing about, + +29:09.820 --> 29:13.560 +or an integer included between 12 and 24. + +29:13.660 --> 29:18.080 +Let's go on with #'foo6. + +29:18.180 --> 29:23.320 +#'foo6 is returning anything but an integer. + +29:23.420 --> 29:26.520 +I think it should be pretty obvious why, + +29:26.620 --> 29:28.120 +because if it's not an integer we return it, + +29:28.220 --> 29:30.000 +otherwise we signal an error. + +29:30.100 --> 29:32.880 +Let's finish with #'foo7 very quickly. + +29:32.980 --> 29:37.920 +#'foo7 has another very complex condition, + +29:38.020 --> 29:40.320 +at least for me, but it's also interesting to see + +29:40.420 --> 29:42.200 +that we are also propagating values for symbols. + +29:42.300 --> 29:45.160 +So we can return the symbol 'big, + +29:45.260 --> 29:46.980 +the symbol 'small, + +29:47.080 --> 29:51.200 +or an integer included between -100 and 100. + +29:51.300 --> 29:54.440 +Now, the question is: why all of this is useful + +29:54.540 --> 29:56.800 +other than having Andrea very happy + +29:56.900 --> 29:59.560 +when he's playing with this all day? + +29:59.660 --> 30:01.440 +Well, we have to come back one second + +30:01.540 --> 30:04.920 +to how Lisp_Objects are represented within Emacs. + +30:05.020 --> 30:09.480 +Lisp_Objects are represented as machine words, + +30:09.580 --> 30:12.580 +where we reserve a few bits to indicate the type. + +30:12.680 --> 30:15.560 +And every time we access the object, + +30:15.660 --> 30:17.120 +when this is a Fixnum, + +30:17.220 --> 30:19.920 +or a regular object where this is a pointer, + +30:20.020 --> 30:21.600 +we always have to extract these bits, + +30:21.700 --> 30:24.520 +make sure that they satisfy a condition, + +30:24.620 --> 30:27.120 +so make sure that we are going to manipulate + +30:27.220 --> 30:28.580 +the object of the type we expect, + +30:28.680 --> 30:31.040 +and then we can extract the object + +30:31.140 --> 30:32.540 +and do the manipulation. + +30:32.640 --> 30:34.420 +If the compiler managed to prove + +30:34.620 --> 30:37.400 +that the contained object is of the right type, + +30:37.500 --> 30:42.440 +it will be able to not emit the type check, + +30:42.540 --> 30:44.500 +and save a lot of instructions. + +30:44.600 --> 30:48.560 +This is a very powerful optimization. + +30:48.660 --> 30:50.760 +Let's discuss some potential future development + +30:50.860 --> 30:52.000 +in this area. + +30:52.100 --> 30:53.360 +First I think it would be extremely nice + +30:53.460 --> 30:54.920 +to extend the propagation + +30:55.020 --> 30:56.920 +to types that are not built in. + +30:57.020 --> 30:58.160 +There are a lot of cases + +30:58.260 --> 31:00.600 +where we could optimize effectively. + +31:00.700 --> 31:02.520 +For instance when we do + +31:02.620 --> 31:05.720 +a lot of accesses to structures + +31:05.820 --> 31:06.920 +-- lots of stuff like that -- + +31:07.020 --> 31:08.880 +where we keep on checking and checking + +31:08.980 --> 31:10.640 +the same object for the type + +31:10.740 --> 31:13.080 +where it's obvious, where it should be trivial + +31:13.180 --> 31:14.720 +to prove that it's the right type + +31:14.820 --> 31:16.360 +after the first access or things like that. + +31:16.460 --> 31:18.920 +So I believe this is a low-hanging fruit + +31:19.020 --> 31:21.180 +in terms of performance. + +31:21.280 --> 31:23.080 +Also I think it would be really nice + +31:23.180 --> 31:24.720 +to extend the declare mechanism + +31:24.820 --> 31:27.600 +to allow the user to declare argument types. + +31:27.700 --> 31:30.480 +(Optionally. Indeed, optionally.) + +31:30.580 --> 31:32.880 +Doing that would give the compiler + +31:32.980 --> 31:35.120 +a lot of information to propagate value types + +31:35.220 --> 31:36.360 +within the function. + +31:36.460 --> 31:38.840 +But those will allow the compiler + +31:38.940 --> 31:43.040 +to give the user really good diagnostics + +31:43.140 --> 31:45.400 +during compile time. Like the compiler could say: + +31:45.500 --> 31:47.000 +Hey, here you are calling a function + +31:47.100 --> 31:49.200 +that is expecting this argument of this type, + +31:49.300 --> 31:52.000 +but I'm proving that you are calling it + +31:52.100 --> 31:55.200 +with an argument that is of THIS type, + +31:55.300 --> 31:57.840 +and is not a subtype of the expected one. + +31:57.940 --> 32:00.240 +So you are doing something not coherent. + +32:00.340 --> 32:02.800 +This kind of interprocedural logic. + +32:02.900 --> 32:05.000 +And I think the compiler should also take advantage + +32:05.100 --> 32:06.640 +(under certain circumstances) + +32:06.740 --> 32:08.760 +of this interprocedural analysis + +32:08.860 --> 32:12.520 +in order to optimize even more, when possible. + +32:12.620 --> 32:13.720 +Also I think we should + +32:13.820 --> 32:15.480 +work on our [?] to improve the code generation + +32:15.580 --> 32:17.100 +depending on the prediction + +32:17.200 --> 32:18.160 +that the compiler is doing. + +32:18.260 --> 32:20.120 +We already take advantage of those predictions, + +32:20.220 --> 32:22.300 +but I think we could do better. + +32:22.400 --> 32:25.120 +A quick look at some performance results. + +32:25.220 --> 32:28.720 +These are from the elisp-benchmarks package + +32:28.820 --> 32:30.760 +within GNU ELPA. + +32:30.860 --> 32:32.520 +This is the performance uplift, + +32:32.620 --> 32:38.480 +and we can identify about 4 "classes" of results. + +32:38.580 --> 32:41.440 +The first one there is no performance uplift, + +32:41.540 --> 32:42.880 +because there is not much we can do, + +32:42.980 --> 32:44.720 +and the time is probably not spent + +32:44.820 --> 32:46.280 +within the execution engine. + +32:46.380 --> 32:49.000 +And the ones around 3x are the ones + +32:49.100 --> 32:50.680 +Where probably we are not triggering + +32:50.780 --> 32:52.640 +manually specific optimizations, + +32:52.740 --> 32:53.600 +but just the fact + +32:53.700 --> 32:57.100 +that we are converting into native code + +32:57.200 --> 33:00.500 +is giving us this performance uplift. + +33:00.600 --> 33:03.280 +Then there is a bunch of other benchmarks + +33:03.380 --> 33:05.680 +where the Lisp optimizations are triggering, + +33:05.780 --> 33:09.620 +and the uplift is way bigger, + +33:09.720 --> 33:11.900 +and then we have 3 benchmarks that at the time + +33:12.000 --> 33:13.420 +are completely optimized out. + +33:13.520 --> 33:15.580 +That means the compiler became "so smart" + +33:15.680 --> 33:18.160 +that it was able to compute the result + +33:18.260 --> 33:20.160 +in the compile time and just put the result + +33:20.260 --> 33:21.400 +in the generated binary. + +33:21.500 --> 33:23.880 +Let's discuss a little bit the compilation model. + +33:23.980 --> 33:26.080 +This is an Hybrid one; + +33:26.180 --> 33:29.420 +it's both JIT-like and Ahead-of-Time-like. + +33:29.520 --> 33:33.400 +Emacs is composed of what we call an Emacs image, + +33:33.500 --> 33:36.180 +essentially the Emacs binary that we start. + +33:36.280 --> 33:37.720 +It's including all the C code, + +33:37.820 --> 33:41.760 +plus all the Lisp code that we preload. + +33:41.860 --> 33:46.480 +Then we have the rest of the Emacs Lisp codebase + +33:46.580 --> 33:49.440 +that can be loaded just if it's required. + +33:49.540 --> 33:52.760 +Same for the external packages, if we have any. + +33:52.860 --> 33:55.120 +If we build an Emacs Lisp + +33:55.220 --> 33:57.940 +with native compilation enabled, by default, + +33:58.040 --> 34:01.200 +only the Emacs image will be native compiled. + +34:01.300 --> 34:03.920 +All the other code will be compiled + +34:04.020 --> 34:06.720 +on the fly when it's loaded and executed + +34:06.820 --> 34:08.800 +the first time, if it's necessary. + +34:08.900 --> 34:10.880 +Same for the packages, in a transparent way + +34:10.980 --> 34:12.640 +and asynchronous way. + +34:12.740 --> 34:13.480 +Also worth noting + +34:13.580 --> 34:15.400 +that the result of this compilation + +34:15.500 --> 34:17.240 +will be stored into a cache directory + +34:17.340 --> 34:19.900 +within the home directory of the user. + +34:20.000 --> 34:23.600 +So it will be reused in the following sessions + +34:23.700 --> 34:25.440 +if the same file is loaded, + +34:25.540 --> 34:27.520 +without having to recompile multiple times + +34:27.620 --> 34:29.320 +the same file in different sessions. + +34:29.420 --> 34:31.520 +It works a little bit like this: + +34:31.620 --> 34:33.800 +When we load the byte-code for the first time, + +34:33.900 --> 34:35.900 +we spawn a native compilation process. + +34:36.000 --> 34:39.320 +Meanwhile we keep using the byte-code available. + +34:39.420 --> 34:41.180 +When the native compilation is finished, + +34:41.280 --> 34:43.960 +we hot-swap the definition of the functions + +34:44.060 --> 34:46.040 +that are contained in the file + +34:46.140 --> 34:47.960 +and start using the native code transparently. + +34:48.060 --> 34:49.880 +We do this asynchronously, + +34:49.980 --> 34:53.140 +and for more compilation units at the same time, + +34:53.240 --> 34:56.280 +so it looks a little bit like this. + +34:56.380 --> 34:58.560 +Let's try a quick demo of all of this machinery. + +34:58.660 --> 35:00.880 +I've started a fresh Emacs + +35:00.980 --> 35:03.280 +with native compilation support, + +35:03.380 --> 35:05.060 +and at this moment nothing is going on. + +35:05.160 --> 35:07.440 +We will test the machinery with Tetris, + +35:07.540 --> 35:10.460 +because I can't imagine anything better + +35:10.560 --> 35:12.840 +to test this. + +35:12.940 --> 35:15.080 +What I do expect is that when I launch Tetris + +35:15.180 --> 35:17.920 +it will be loaded, it will immediately + +35:18.020 --> 35:20.460 +start execution of the byte-compiled version, + +35:20.560 --> 35:23.000 +so we won't see any delay in the user experience, + +35:23.100 --> 35:25.160 +and in the meanwhile, a parallel process + +35:25.260 --> 35:28.160 +will start to native-compile Tetris itself. + +35:28.260 --> 35:30.200 +When the native compilation will be finished, + +35:30.300 --> 35:33.240 +the functions of all Tetris will be hot-swapped. + +35:33.340 --> 35:36.280 +So we will not see any interruption. + +35:36.380 --> 35:39.680 +So Tetris started, and it's running, + +35:39.780 --> 35:41.600 +we have seen no delay, and in the meanwhile, + +35:41.700 --> 35:43.520 +the native compilation probably already finished, + +35:43.620 --> 35:44.960 +we can have a look. + +35:45.060 --> 35:47.160 +In this I see the native compilation log buffer. + +35:47.260 --> 35:49.720 +So we see that Tetris has been native compiled, + +35:49.820 --> 35:51.760 +and all of its dependencies. + +35:51.860 --> 35:53.840 +Now Tetris is still running, + +35:53.940 --> 36:00.480 +but I can do "C-h f tetris" + +36:00.580 --> 36:02.540 +and we can see that 'tetris' + +36:02.640 --> 36:04.880 +is an interactive native compiled + +36:04.980 --> 36:07.940 +Lisp function, so it has been native-compiled. + +36:08.040 --> 36:13.500 +I can even disassemble if I want. + +36:13.600 --> 36:14.880 +OK, so very cool. + +36:14.980 --> 36:18.020 +I guess we can say this mechanism is working. + +36:18.120 --> 36:20.660 +Also worth noting that if I go back + +36:20.760 --> 36:24.120 +to the *Async-native-compile-log* buffer, + +36:24.220 --> 36:27.960 +we see we have compiled another bunch of files. + +36:28.060 --> 36:31.600 +I think these are because of my 'C-h f', + +36:31.700 --> 36:33.800 +this help function command and disassemble, + +36:33.900 --> 36:34.920 +and so on. + +36:35.020 --> 36:37.720 +The first time you run Emacs, you will have, + +36:37.820 --> 36:41.280 +from time to time, these processes spawned. + +36:41.380 --> 36:43.100 +Emacs is "compiling itself", + +36:43.200 --> 36:45.520 +and it's replacing the byte-code definition + +36:45.620 --> 36:47.640 +with the native one. But after a few sessions, + +36:47.740 --> 36:49.760 +you will not see this anymore, + +36:49.860 --> 36:51.360 +because the output of this compilation, + +36:51.460 --> 36:54.900 +as I mentioned, are stored in the user directory. + +36:55.000 --> 36:57.560 +To conclude: Emacs with native compilation support + +36:57.660 --> 36:59.080 +is coming up in Emacs 28, + +36:59.180 --> 37:01.040 +that is gonna be the next major stable release + +37:01.140 --> 37:02.080 +that will be released. + +37:02.180 --> 37:04.840 +So we ought to celebrate with a big party, + +37:04.940 --> 37:07.320 +I believe. But before going to the party, + +37:07.420 --> 37:09.080 +I'd like to list a few points + +37:09.180 --> 37:11.340 +that I think have been success factors + +37:11.440 --> 37:13.420 +in upstreaming this work. + +37:13.520 --> 37:15.420 +It has been extremely important + +37:15.520 --> 37:18.140 +to get in touch with upstream as soon as possible, + +37:18.240 --> 37:20.680 +as soon as I had a proof of concept. + +37:20.780 --> 37:22.600 +It's been extremely important + +37:22.700 --> 37:24.660 +to involve the community as much as possible, + +37:24.760 --> 37:28.480 +and this included keeping a development blog, + +37:28.580 --> 37:31.600 +and posts about that on emacs-devel, + +37:31.700 --> 37:33.720 +and also producing material, + +37:33.820 --> 37:36.020 +participating in conferences, + +37:36.120 --> 37:38.320 +and giving presentations like the one I'm doing, + +37:38.420 --> 37:40.680 +to explain what I was doing and how it works. + +37:40.780 --> 37:43.040 +It has been extremely important, also, + +37:43.140 --> 37:45.860 +to be able to rely on the upstream infrastructure. + +37:45.960 --> 37:47.540 +So, to develop the software + +37:47.640 --> 37:49.080 +as a feature branch in the official git, + +37:49.180 --> 37:49.960 +but even more, I would say, + +37:50.060 --> 37:51.660 +to use the official bug tracker + +37:51.760 --> 37:52.880 +for solving bugs of this branch. + +37:52.980 --> 37:54.680 +This gave the opportunity + +37:54.780 --> 37:58.160 +to stay really in close touch with maintainers, + +37:58.260 --> 37:59.920 +and senior developers of Emacs. + +38:00.020 --> 38:02.980 +That helped me a lot. And at the same time + +38:03.080 --> 38:04.820 +they were informed about what I was doing + +38:04.920 --> 38:07.360 +and what was the status of this feature branch. + +38:07.460 --> 38:08.800 +Extremely important. + +38:08.900 --> 38:11.160 +And also I think it played a major role + +38:11.260 --> 38:14.120 +to try to design this enormous patch + +38:14.220 --> 38:18.120 +in a way that the impact on the current codebase + +38:18.220 --> 38:21.120 +was minimized (at least as much as possible). + +38:21.220 --> 38:23.660 +And also minimizing + +38:23.760 --> 38:26.240 +the impact on the user operation of the software, + +38:26.340 --> 38:28.040 +in this case Emacs. + +38:28.140 --> 38:29.640 +So yes, mandatory Special Thanks: + +38:29.740 --> 38:33.360 +Emacs developers, and especially maintainers + +38:33.460 --> 38:36.680 +and senior developers like Stefan Monnier, + +38:36.780 --> 38:40.440 +that helped me a lot across this long journey. + +38:40.540 --> 38:42.800 +And, well, all the community + +38:42.900 --> 38:45.160 +that really got so excited about this project + +38:45.260 --> 38:46.240 +and gave me the energy + +38:46.340 --> 38:49.120 +to go through all of this time and development + +38:49.220 --> 38:51.980 +and bugs and solving, etc. etc. + +38:52.080 --> 38:54.920 +So yes, it was a really exciting time, + +38:55.020 --> 38:58.000 +and I think we have to look forward + +38:58.100 --> 39:01.400 +and start thinking about how to improve all this + +39:01.500 --> 39:02.920 +for the following years. + +39:03.020 --> 39:04.300 +And that's it. + +39:04.400 --> 39:06.040 +I think I should be online for questions. + +39:06.140 --> 39:07.480 +Thank you very much. + +39:07.580 --> 39:07.680 +[captions by John Cummings] |