 Hello, everybody, and we're back on Evolving Technology Track. I'd like to welcome Eduardo Valki, right? Did I pronounce it correct? That's correct, thank you. Hello. He will be talking today about Dr. Futamura's projection machine. Eduardo, the stage is all yours. Thank you. Hello, everyone, and thank you for joining. This is Dr. Futamura's projection machine from interpreters to compilers through a marvelous device. My name is Eduardo Valki. I'm a Senior Software Engineer at Reddit, and I work at Business Automation. This is the project I'm currently working on. It's called Cogito. It works on rules, JVPM. It's our business automation platform, but for the cloud, it's based on Quarkus to provide incredible capabilities, including native image compilation, which is supported by GraalVM. It's based on GraalVM, which is the topic for today. But today, we're not going to talk about native image compilation, native image generation, but we're going to talk about one of the projects that are part of the umbrella that GraalVM is. GraalVM includes an OpenJDKJet compiler, but it also allows you to compile your programs into native image standalone executable, but it also allows you to run multiple dynamic languages such as JavaScript, Ruby, AR, Python, and even managed native languages. Now, the way it does that through a framework is through a framework that's called Truffle. This framework allows you to define this dynamic language in a very high-level way and still get incredible performance. As you can see from this line, which is quite old but still impressive, they were able to write a Ruby implementation in a very high-level way, which was far four times faster than the traditional M Ruby implementation. Our implementation were 4.5 times faster than our and JavaScript implementation that was basically the own part with V8. This presentation was originally for a meetup that's called Papers We Love, where we started our journey by taking a look at papers. I'm not going to bore you. I'm not going to talk and read papers in this presentation, but I just want to give you a couple of information about where this projection machine comes into play for GraVM. This is a paper, a location removal by partial evaluation, where partial evaluation is first mentioned and talks about this Truffle framework, which is part, again, of the GraVM umbrella. This is where partial evaluation has been mentioned. Now, this is another paper, a more recent paper with much more details. Again, partial evaluation, as you can see here, is the keyword. In the abstract of this paper, you can read an introduction that the problem with dynamic languages is that dynamic language implementers tend to implement, duplicate, the semantics of their language in multiple places, in the interpreter, in the compiler, and even in the runtime system. So you start with an interpreter usually, but then you understand that you want more performance. Then you write a just-in-time compiler, and then you will have to duplicate the semantics over and over again. So with GraVM Truffle, you are able to write an interpreter and just use that. And by using that, you will get high-performance just-in-time compiler. And this is through the magic, so to speak, of partial evaluation. This approach was first mentioned and described in 1971 by Futamura, and the technique, one of the techniques is called first futamura projection. And that's why we're going to talk about the first, the second, the third futamura projection. The paper from which I'm taking the details is from 1983. And basically, the technique of futamura projection gives you the ability to start from an interpreter and get a compiler for free. And the reason why I'm saying compiler for free and this presentation is basically a shameless re-puff of this other presentation, I'm just kidding, but I take a lot of inspiration from RubyConf 2013, compilers for free by Tom Stewart, check it out and see how much stuff I copy from him. So this is the abstract from the 1983 paper, and we're not going to bother too much with that. Let's get into the details of what this is about. So let's get a step back and understand what a program in a programming language is. Let's just take back, really give back some definition that we probably already know. So what's a program? A problem in a sequence of instruction that can be executed by a machine. And a machine could be virtual or physical. Now, the collection of these sequence, of these instructions, it's a programming language basically. Now, when we say that a program is evaluated, we mean that there's some machine, be it virtual or physical, that is able to read these instructions and evaluate them. Now, the key here is that it is a machine. What that means is that there's no intelligence, it's not smart. It just reads an instruction and just do whatever it does. The instruction is supposed to do. So what's a program evaluation then? If you have a program written in some language, let's say a Python program, and you have some data, you feed the data into the program and you get back the result. That's what a program evaluation is. So for instance, suppose you have a program that computes the sum between two numbers, your program escape plus you and your result is seven and your data is three and four. That's it. Now, what's an interpreter? Well, an interpreter is a special kind of program. And it's a special kind of program that takes as an argument another program and then the data for that problem. So for instance, if you have an interpreter, then P would be our Python program and it takes as an argument the Python program, the data for the Python program that gives you back the results for that program P. And we can denote this with IPD. So we take the interpreter, we supply the program P and the data D and we get back the results. That's it. So we're going to see what a compiler is, but that's what an interpreter may look like in a very high level way. As we said, an interpreter has an internal representation, but it has an internal representation of the instructions for your program. And what the interpreter does is fetching, reading these instructions one by one, analyzing them against the least of instruction that it has and perform the operation that instruction implements, represents. So in this case, it will be K plus U. It will be the instruction add and this will be the way it would execute that. Now, what about compilers? Compilers are a special kind of program. Again, a compiler for a programming language takes that program, a program written for that programming language and it turns it into a compiled program. Now, the originating program, we usually call the source program and the compiled program, we sometimes call the object program. At least traditionally, it's called an object program. Now, the results of computing the compiler, or planning compiler for the program P, it's the compiled program CP. Now, the key here is that now you can evaluate the program, the compiled program with the data D and you get the result and we could denote this with CPD. Okay? And that's what a compiled program for this time would look like. This is X86, essentially. Now, there's no particular intrinsic reason why a language should be compiled or interpreted and in fact, there are many programming languages that can be both interpreted and compiled. This, for instance, is a camel. This is a simple camel program that prints a string. You could run the program or compile it and then run it. And basically, you could do this at least theoretically for any programming language. So basically, as long as the semantics is preserved, that is, as long as applying the program to the data gives you back the same results so you interpret the program over the data or you compile the program and then apply to the data the result, you should get the same results for that program. The semantics should be preserved. So what's partial evaluation? Well, partial evaluation, as an intuition, if you have a program F with two parameters K and U, suppose that you want to often call this program with a K parameter bound to the value 5. So the idea, you will define a new function F5 that only takes one parameter U and basically the implementation for the function will be substituting 5 for every occurrence of K and then simplifying the function. So basically, partial evaluation is the process of transforming F of 5 U into F5 of U. And this is how this is represented in the original Fittemura paper. This is the original function. This is the process through which you partially evaluate and you get this intermediate problem, FK. Now, this could sound to you like curing or to be more precise, it should be called partial application. So you define a function F5 in terms of F. So if you want to look at the code there, which kind of looks like JavaScript code, you're basically using the function F and you're defining a new function F5 by defining a new function that only takes the parameters U and calls F with a 5 as a key value and U as the second parameter, right? And functional programming, this is often core for misnamed curings, actually, partial application. But this is not the same as partial evaluation. And in fact, the difference is that here we're just calling another function. What's partial evaluation is is that the function is actually transformed. So you start from the originated function at the top, which is K times K times K plus 1, da, da, da, da. You set K to 5, you substitute 5, 4, K everywhere, and then you apply all the operations up until you cannot simplify any longer. Basically, you see there's only 5 times 31 plus U plus U times U. As you can see, it cannot be long, it can be not further simplified. It's a different function, but it computes the same results. So this has been partially evaluated. Another technique to do partial evaluation is verb writing. So supposedly we have this recursive definition for local power function that takes N and the K exponent. So if K is less or equal to 0, it returns 1, otherwise it N times power N K minus 1. So we substitute the definition of power 5, which is power N5 with basically the else branch here, because 5 is greater than 0 and it becomes 4 and then 3 and then 2 up until it's all expanded all in line N times N times N times N times N, 5 times. This is sometimes called inlining by compilers. And these techniques are even in the 80s they were already pretty familiar to compiler writers. This is a screenshot from the paper, a picture from the paper where PL1 program, very old programming language, were using integration simplification, the compiler was using integration, which is another name for writing and simplification to recompile and optimize the program. So what's the projection? Well, the projection is the name that we give to the function FK. So the function F with a K parameter fixed, it's called projection of F at K, just the definition. So what's the partial evaluator? Now we're ready to know what a partial evaluator is or a projection machine or also called a partial computer. It's a function, it's a program that takes another program F and a parameter for that program K and gives you back the projection of F at K. So for instance, if we have this function F, we have K and U as the parameters of our running examples and it gives you as a result of applying F to K and U we could take F at K, we could apply alpha to it and we will get back the projection of F at K. So because alpha is a program, basically what that means is that in the case of our power 5 function if we had this partial evaluator at disposal we could write alpha, power which is our power function and the binding of K to 5 and get back as a result automatically this expanded, this written implementation for free. Right? Okay. So the paper actually presents various use cases for a partial evaluator but one of its most interesting to me and the one we're going to see today is automatically generating a compiler. So let's get back to this. So uninterpreted is a program that takes another program written for that interpreter and the data for that program is an input, it evaluates the program and gives you back the result. A compiler is another program that takes another that program which is a source program and returns an object program and the object program is applied to the data and gives you back the result. Now suppose that we are in this situation it's our interpreter that takes a program and the data for that program gives you back the result. For instance, suppose I it's Python, MP it's the Python program. So because I it's a program actually we could apply the partial evaluator to the projection machine and get the projection of IFP, right? We could apply to the Python interpreter and the Python program and get back a projection of IFP projection of the Python interpreter or the Python program. What does that mean? Well let's see what this does. Basically it's a new program that by being supplied the data for the original program the original Python program gives you back the result. So basically it's a program that can be run with the data and gives you back the result. So that's basically an object program which means it's a compile program. Whoa. I don't think we're going to believe me here. I think you're going to say, oh, but that's like bundling the Python interpreter with a Python program. Well, not quite. Because partial evaluator as we've seen previously simplifies, transform your program and reprides it. And so this is our program, K plus U which is transformed into the instruction at X, Y, so to speak, right? This is our instruction. This is our interpreter which fetches instruction and matches them against the least of possible instruction. But our program only contains one instruction, so all of this stuff and only one line. So all of this stuff the wide loop, the match against different kind of instructions is useless. And so it gets simplified because it's never evaluated. It gets simplified to this one simple list of statements. But actually this is a very high level definition as a pseudo code. If we imagine that this was compiled using a compiler to native code, basically this reduces this, which is again our assembly, our representation machine code for X86, right? So this is our current situation. We took our interpreter, we took our program with a Python server, a Python program and partially evaluated for and we got the IP which is a projection IP, which is a compiled program basically, right? Let's rewrite it in a different way. So it's still the same thing, but even on the, you know, with our head tilted on the side alpha, it's the center it's the program that we evaluate. It's being evaluated over I, which is our Python interpreter and P is our Python program and it gives you back the projection IP, which is our compiler program, right? Now, suppose that now because alpha, it's a program, right? This is the beginning we say this, alpha is a program. So we can actually partially apply alpha to I and we get the projection of alpha at I, right? The projection of alpha at Python interpreter. What does that mean? Well, let's see what this does. Alpha projection of alpha at I takes P and gives you back IP, the projection of I at P. But what's the projection of I at P? Well, if you recall, that's a compiled program, right? So what's called, what's called what is a program that takes a program and gives you back a compiled program? Well, that's that's a compiler, right? So we're getting a compiler for Python by applying partial evaluation twice. That's pretty cool, but we need to go deeper, right? So this is our current situation. We have alpha, we have I, we have partially apply alpha to I and we got alpha I which is a compiler for Python, right? So let's rewrite it tilting to the side. Let's rewrite it and we have this situation, still the same situation, alpha alpha I as the parameters and we get alpha I which is a compiler, right? Well, alpha it's still a program, right? So we can actually apply partial evaluation to some of these parameters. Let's say alpha. So by applying alpha to alpha and alpha, we get alpha alpha, the projection of alpha at alpha. So what does that mean? Well, let's see what that does. Well, the projection of alpha at alpha takes I and gives you back alpha I, which is C, which is a compiler. So this is a program that takes an interpreter for some language, for instance Python and gives you back alpha I, which is the projection of alpha at I, which is a compiler. So by giving a high level specification in some way of a language in the form of an interpreter, you get a compiler. So that's alpha alpha it's a compiler compiler. So that's a compiler generator. That's pretty cool. Okay, but can we, can we refer to that? Well, kind of. So we are in this situation, right? We were in this situation, we applied alpha, we got alpha alpha, let's rewrite it. So we now have this situation where alpha alpha alpha is a program and we get as a result alpha alpha. Now, if we apply again partial evaluation to alpha and alpha, we're going to get again alpha alpha. So that's kind of a coin. It's a program you cannot go further than that. It's a fixed point. No. So let's, this is the fourth equation of partial computation. The first three were called the first, second, third equations or first, second, third projections. This is the fourth equation of partial computation. Let's see what that means. Alpha alpha alpha gives you alpha alpha. So because I is an interpreter and alpha alpha applied to I gives you a compiler for the language interpreter I. Then basically alpha alpha gives you, can be applied to any program. So what does it really do? When you apply alpha alpha to I gives you back a compiler for I. When you apply alpha alpha to I and then apply to P, it gives you compiler to P, but I could be any program, could be any interpreter. So what is exactly alpha alpha? Well alpha alpha, if you want to see it differently, if you see the comparison with the equation at the top, with the question on the second line. Alpha, it's kind of a language. If I, it's a language interpreter alpha, it's an interpreter for the alpha language. And that's what's written in the paper. What that means is that you can say that alpha alpha, it's a partial evaluation compiler. It's a compiler for the alpha language, which means that basically because alpha here can be substituted by any program. So it could be I, it could be any program. Basically you are able to generate a partial evaluator for any program. Now the older says that there's no practical derivation for this program. So that's just theoretical but the rest can be actually applied. In fact, the first and second projections are used in Growl VM, as we mentioned in the beginning. The interesting thing about this about the technique that the Growl VM team uses is that they do not just use partial evaluation because besides repwriting and substitution there's also there's also another technique that's called tabulation that could lead to an explosion in code. So instead of just compiling every possible code path, they use they use they use profiling feedback to only compile those code paths that are actually used. So you use a high level the representation of your program as a tree an ASD, abstract syntax tree based interpreter. These are rewritten using profiling feedback and when there's a hot pot they're actually rewritten using they're actually evaluated into compile coding. You can go back and forth depending on the code path that's being visited and that's how they get this amazing performance. So that was basically all I had and these are all the references to this presentation. So I hope you enjoyed this. I hope you were able to follow I hope that you were happy to stay with me.