 Okay, so my name's Andy Keith and I'm here to talk about using Ruby to generate faster Ruby code using partial evaluation and sorry that the title is such a mouthful. So what I want to talk about is really two things but sort of broken into four parts. So I want to talk a little bit about partial evaluation and in general and what we're doing with partial evaluation of Ruby. And then about the tools that we created in order to do these transformations and what progress we've made and where we're going with it. Before I get started I did want to say who I am. So I'm a third year PhD student at Indiana University. I was not always an academic. I actually spent eight years working as a web developer in New York. Most of that time was coding Java and when I started learning Ruby I was very, very happy with the flexibility of the language and the metaprogramming that it provided. And when I got to college I also learned that I really like compiler technology which is what sort of got me started on this process. So first just to talk about partial evaluation a little bit. So partial evaluation is sort of an old compiler optimization idea and basically the idea comes from the fact that in a lot of cases when we write programs we know things about the programs statically. That is we know things about the program before they start to execute. So you can think of your constants in your program, you can think of sort of basic operations. You know what all of those are and so you can determine those before runtime ever begins. So what we want to do is we want to take those static parts and evaluate them before we ever run the program. So this is what partial evaluation is basically, this is the basic idea behind it. And it also manifests itself in a couple of simpler optimizations namely constant propagation and constant folding which are both very, very common things to do particularly in static languages and specialization. So specialization some people would say partial evaluation is a type of specialization and some people would say that specialization is something that you do as part of partial evaluation but we don't really care what the distinction is. It just doesn't matter that much for us. So what is constant propagation? Well constant propagation is pretty simple. We have three variables defined here X, Y and Z and X and Y are both getting assigned to constants. So our operation here of adding these, the values in these two we know what those values are going to be so we can go ahead and propagate those into those positions. Very simple. And the great thing about doing this kind of stuff is we may be able to eliminate the definitions of X and Y from the program entirely which means less code to be run at runtime. We can also do constant folding and constant folding is basically just taking the two, taking the operation and taking the arguments to that operation and executing it at compile time. So here we're sort of ignoring the fact that a plus could actually be a method call that does something random and we're going to treat it as though we know what plus is. And we can of course combine these two techniques so we get, take our original expression and we do our constant propagation and then we do our constant folding and maybe we can eliminate X and Y entirely and maybe if there is a reference to Z we can replace that we can get rid of that as well. So specialization is something that's a little bit more complicated. So we might think of a simple method definition and we have here an optional value that's already been specified for Y and it may occur that in a lot of places in our program we actually only ever pass one argument and that means that we know that for those particular cases the value of Y is always going to be specified as five and so we can actually pre-compute this Y times six to make a specialized version of the method where we replace that with an already completed calculation. And so this is sort of the basic idea behind what partial evaluation is or at least what we're calling partial evaluation. So of course there's also, we now need to think about the sort of Ruby twist on partial evaluation. This is all great in static languages. Are you also including inlining? We're not including inlining. Traditionally inlining is not usually partial evaluation though it is something that I would like to do eventually for this code but it's not something that I've looked at immediately. So the Ruby twist on partial evaluation is that we can actually use the interpreter to do the evaluation. This is great because in a lot of static languages I actually have to make my compiler know what the operations that can be performed in language are and I have to make it sort of encode it in an evaluator in my compiler which is not necessarily an obvious thing to do. And so because we already have the interpreter, we don't need to implement it. It's already done. We never have to worry about trying to match Ruby semantics because we've got the evaluator and it will do the Ruby semantics for us. It is Ruby. It gives us the ability to introspect the results so we can look at what we got out of it, we can know its type, we can know what can be done with it, what methods are defined on all those types of things. It also provides for us a set of tracing facilities and so we can even put hooks on our code to say we want to know when a new method is being defined, we want to know when an object being extended and we can actually look at all those pieces. And we can use distributed Ruby in order to separate the program that's doing the partial evaluation from the program that is being partially evaluated. And this is particularly important because we don't want our definitions to conflict with each other if we're running all in one big evaluation session. So you could imagine actually trying to partially evaluate your partial evaluator and that might go very poorly if it was all in the same environment. But it's not all simple stuff. So we still need to figure out what do we know statically versus what is only known at runtime. And so it makes a big difference to try and figure that out automatically is not a non-trivial task. We need to ensure that the meaning of the program is preserved because of course we could write a very fast compiler that made all programs return zero, but that wouldn't be a very useful compiler. And so we need to determine what is and what is not safe. And this has to do with looking at side effects, what functional programmers would call side effects and has to do with making sure that the input and output that we get out of the program is going to be the same for both the original program and for our partially evaluated program. In Ruby of course expressions depend on the context. Because we can change definitions as we go along it's very important that we make sure that as the definitions change we're tracking what those are so that when we do the partial evaluation we're getting them in the correct environment. And we also need to know what the receiver class is in order to even resolve a method. So I can't know what a method is if I don't know what a method call is going to, which method is going to be called. If I don't know which receiver class it is because there's no way for me to look it up. So just to look at why context matters. So this is just three times two, very simple, right? We all know that this is just six. But what if I evaluate it in a slightly different context? So I cheated a little when I changed multiply to be plus. Well now it evaluates to five. And so the partial evaluator can't just take three times two and say well I know it's always going to be six. Likewise I could actually redefine times to always return some constant value. Here no matter what I do it's always going to return 100. In all these cases though it's been safe for us to call this because there's no side effects. And so even though the expression has changed the partial evaluator should be able to give us those values properly following the data flow of the program. But what if I have something like this? Now I've used method chaining in order to redefine, multiply so that it puts out a copy of the two things that it's multiplying out to standard out. And I might do this to a log file. You can imagine programs where we want to have logging of what's going on. We need to preserve these in order to make sure that the program is still doing the same thing that the program was supposed to do in the first place. And so here we have the output as well as the value that we get. And so determining the receiver is another challenge for us. If we have this simple method F that just takes an argument and then calls G on that argument, we need to know what G is in order to know whether or not we can partially evaluate calls to this method. That means that we need to determine every possible value of X that can flow into here. Which means that we need to find all the call sites for F before we can make a decision about whether or not this thing is safe. And of course we may find that G actually resolves to a number of different methods and we will need to make sure that all of them are safe in order to say that F is safe. Or we'll need to do some sort of call site specific optimization where we say at this call site this is safe and at this call site it's not. So to determine safety, right now we've restricted ourselves to full program analysis. And part of the reason why we stick to full program analysis is that the meaning of libraries can change. If we think about writing a factorial function that uses multiply, then the value that comes out of it when we give it five should be 120 if we have a standard multiply. And so I could go ahead and say, well, factorial five is 120. But if someone includes a library before that, before that call that redefines my multiply, now the value of that factorial changes. And so I really need to have the whole program. There are some ways to get around this. We can define a set of assumptions that are made in the library. And then if those assumptions are violated, you can go and either re-partially evaluate or just use the original version of the code. But we haven't gotten into that yet. We need to ensure that our effectful operations are preserved. So this is our reads and writes, anything that is going to change memory, where that memory may impact the run time of the program. So that happens a lot in the languages, of course. We change the value of an instance variable of an object. And the method call result changes based on that change. And so we need to make sure that anything that's going to be done at run time that's going to have those types of effects we preserve in the resulting program. And this means that we need to ensure that static values are available at run time. Regardless of whether they're in the local variable, if they're in some instance variable, some class variable, some constant, we need to make sure that those are available at run time. So our approach to this has been to use data flow based analysis, which is very common for this type of problem in writing compiler tools. And basically what it means is that we go through the abstract syntax tree from the start to the end. There are other methods of data flow based analysis that use a control flow graph. We are actually just sticking with the abstract syntax tree. And so the basic thing that we need is an environment that tracks the values or types of the static variables as they pass through the program. And we need that information so that we can determine what methods are being called, determine what is, and is not known statically in those types of things. But because this is Ruby and we can change definitions and do things like that, we need to actually track the definitions as well. And we can actually make definitions down different paths of the program. So you can think about an if statement where on one path of the if statement I define a function f and give it one meaning and if I can't determine statically which one of those two is going to be used. I can't know anything about f because it could be either one at runtime. And so those types of things we also need to keep track of in our super environment. And this idea of coming together at the end of branches or coming together in loops is what is known as a meet operation. So we need to make sure that the meet operation works not just on values and types, but also on the environment overall. We also need some sort of method safety analysis. So we need some way to make sure that no IO methods ever get called. Well, actually we need to cheat a little or we want to cheat a little because we want to make sure that we can do something with load and require. Because most Ruby programs are going to use load and require and we can't just ignore that and assume that everyone will gladly write massive, massive amounts of Ruby code in one gigantic file. We also can't do anything with methods that we don't know about statically, of course. So this gets back to having two different definitions for the same method and not being able to determine which one of them is going to be used. And we need to track argument safety through our calls. So we may have a couple of arguments that have, say, an array of them. And because we pass those arguments in, and maybe we find a method that we can't actually call because it does some sort of IO, we may not be able to use those arguments anymore either because they may have been modified and we may not know what they are anymore statically. And so we need to have some way to say, yes, this thing you can leave alone and you can pass through. For instance, when I call puts on a variable, I don't want to have to kill that variable out of my environment. I want to be able to keep it because I know that it shouldn't be doing anything that's going to side effect that value. But at the same time, if I have some other method, which I either don't know what the method is, or I know that that method calls methods on the arguments, then I have to make sure that those get killed from my environment. Yeah, so yes, puts is, yeah, that was a cheat example because yes, you're right, it does call 2S. And so that information is something that we need to make sure that we track as well. It needs to analyze the 2S on the value in order to make sure that it knows what it is. Yeah, you're absolutely right. So there are a couple of different ways we can do this method analysis. We can actually do a fully static analysis of methods. And this is great because we only have to do it once per method. But it's not so great because it's not so precise. Because it means that we have to decide everything we know about a method based on just looking at the contents of that method and not knowing about where its call sites are, what its arguments are, or anything related to that. So instead, we could do a call site specific analysis, which basically says that each call point we could then redo this analysis. And with the known arguments, and that would allow us to be much more precise. Unfortunately, this is also much more time consuming. And while partial evaluation is something that we're going to execute before we start the run time. And so maybe we don't care too much about the compile speed. We all know that that's not true, or anyone who has set and waited for a large C code base to compile knows that we do care about compile time and we want it to be fast. So, sorry, go ahead. Sorry. So we can instead use something like a hybrid approach. And this gives us a lot more ability to sort of tweak what we're getting at. And the way we do this is by gathering up all the call site arguments. This gives us something that's in between on speed and precision. So we might decide that we're going to gather up all the arguments and we're going to analyze this function once anyway. And a lot of times we're actually going to get some benefit out of that. Because in a lot of cases our methods are called with roughly the same types or at least the same super classes of things. But we might also decide that we have a couple of different classes and we want to separate that method up into a couple of different sort of F and F prime where we have one set of calls that uses a similar set of arguments and another set of call that uses a different set of arguments and so we want to split those up. So it gives us a little bit of heuristic there that we can tweak. So, that's basically our approach to partial evaluation, what we're looking at to do inside Ruby. So we're also using Ruby to do the work. We weren't originally using Ruby to do the work, but I sort of encouraged my advisor that this was a good idea, so we're doing it now. So we do source to source translation, which means that we start with Ruby code and we end with Ruby code. This does limit us in what we can do because we might discover things about types that would be useful in accelerating things. But because we can't communicate those types through Ruby, they by necessity get lost. We're using Ripper right now to parse the Ruby code into an abstract syntax tree. We're not keeping Ripper's representation since it's event based and we want to have something a little bit more flexible. We have our own abstract syntax tree. We also wrote an internal DSL called RubyWrite that we use for doing term rewriting on our abstract syntax trees. And basically we use this for all of our analysis and for our transformation of abstract syntax trees. And the virtue of it being an internal DSL is it means that it gives us all of Ruby, which allows us to use libraries like the RGL library, which is great for doing graph related things, which is also a common thing to do inside compilers. And finally we wrote a little unparsing library called Shadow Boxing, which is also part of the RubyWrite stuff. So for the parsing, I have just a very thin wrapper around Ripper. In fact, I use Ripper's events to generate RubyWrite nodes. And I just use the Ripper event names for the node names. Yay, metaprogramming. And the thing that I like about this is that, bother, that doesn't show up. Well, it uses the same parse.y as Ruby19. And I like that because of course it means that it keeps up with the changes in the parser. Unfortunately, there have been some changes in the parser that have broke Ripper, which has been a little bit of a cause of consternation. But I think most of those are fixed now. So for analysis and transformation, we use our own tool, the RubyWrite module. And we use those as a set of RubyWrite nodes. So the RubyWrite nodes are basically an annotated abstract syntax tree node. And so the idea is that there's the head of the node and it's children. But we also want to store some annotations such as the source code that that node came from, the source file, the line number, column number, and things like that. Which is useful inside of a compiler, of course, because we might want to report where an error occurred. And then we define a couple of methods. The rewrite method, which is sort of our top level method, which basically takes a normal Ruby method and wraps using an environment. And we need the environment in there because we want to use match to decompose our patterns and we want to use build to recompose our patterns. And so match takes a pattern and that pattern can have variable slots in it. And those variables then get put into the environment with whatever tree node was in that location. And then build can look those back up in order to reconstruct abstract syntax tree nodes. And then we also have a little bit easier tool to use, which gives us a way to simply do a pattern rewriting where we take a pattern, it does the deconstruction for us, and we give it a block of code, basically, to execute, to rebuild. And then we also define a few tree traversals. Tree traversals are also something that's very common inside compilers. We define a number of them bottom up, top down, all, one, others. So let's take a look at an example. So we want to take this code, which is basically using these modifiers, Ruby modifiers, where we modify the statement with an if. And we want to turn those into just basic if statements, partially because later in the code, we don't want to have to duplicate our, in the, later in the analyzer, we don't want to duplicate our code to do the same things over and over again. And we would need to do that if we preserve all of these different node types. So when we parse this, it's going to look something like this. This is actually the literal syntax for our RubyWrite nodes. We've latched on top of symbols in order to give us our nodes. And so you can see that inside of our nodes, we also have some strings. These should actually all y and 0 and 1 actually should all be quoted, but they're a little difficult to see. And this actually should give us what we need to look at in order to transform them into this. Which is basically an if statement in the first case where we have a test and we put our consequence is the original statement that was being modified. And in the second case, we really don't want to preserve unless either. So we want to turn this into an if with an empty consequent, an empty then statement. And then put in else the results of the statement that would have been called. And finally, when we unparse it, we'll get back something like this. So a more standard if and a little bit weird looking if that has no then. We could have also done if not and change this into the other location, but this lets us get rid of not as well. So we can do this by pulling in our library. We define a class. We don't do anything special. We just have a module that we have added on top of it for doing our rewrites. Right now we sort of define a standard method of main. There's actually no need to do this any longer. This was a legacy from our old code. But RubyWrite provides us a run statement that allows that pass to run and it looks for main automatically. So in a lot of our code, we've continued to do this. So here I just want to do a bottom up pass through to unsweeten this code. Because I just want to look at all the nodes and I want to transform the ones that have that particular shape into a normal if statement. And so I'll have it called simplify on each of the nodes. And so simplify can write the same way. I should just use a new method here. And inside the simplify, I can then use match to deconstruct my clauses. And you can see here where colon t, colon c, and that's it, I guess, are being used in order to be variable slots. And I can plug them in in my build and it rebuilds those variable slots. And since I'm doing this bottom up, I don't have to do any extra traversal. It's all taken care of forming by the tree traversal. And if it doesn't match one of these two cases, I just want to return the original value back. But I can also write it as a re-writer, and there's an advantage actually to writing it as a re-writer. So you can imagine that you might have a much larger unsweetener, or a much more complex analyzer, both of which I have. And it would be nice not to have to go through the process of saying, if, else if, else if, else if, else if, for the 100 or some node types that there are in Ruby. So instead we have these re-writers. And these re-writers let us write these little functions. They also have a performance benefit for us, because rather than doing an if else, if else, if else, if else style match, we actually use a data structure called a tree, t, r, i, e, in order to determine which one of these calls should actually be executed. And we only execute the one that should be. So finally we want to un-parse this. Now we've got it transformed. We want to use shadow boxing to un-parse it. It's a pretty printing library. It's part of the RubyWrite. It basically has very, very simple matching for RubyWrite nodes. And it prints using the idea of horizontal and vertical boxes. And the basic idea, while this doesn't sound like it's very flexible, it turns out it's actually quite less the ability to list a whole bunch of items that are going to be printed out horizontally. And we put spacing information in there. And vertical boxes give us a way to indent and list things on separate lines. And so we can create a pretty simple un-parcer, just like this, by starting, pulling in our boxing library, starting our un-parcing, and then generating our un-parcer basically as a set of rules, and then using it to un-parse. And it's a little bit strange here we have this 2s at the very end. Part of the reason for that is that un-parcing a node actually generates, instead of generating the string directly, it actually generates another tree, which is the set of horizontal and vertical boxes, in case you wanted to manipulate that. So we can think of a very simple rule here for our var reference. We're just going to take the var reference. It has one item in it, which is the variable. And we're just going to print the variable back, and that's really all there is to it. But we may need to do something a little bit more complicated. We may want to actually break it down our if, and print the if pieces out. So here, T, C, and A represent the test, consequent and alternative of our if statement. And the V is our vertical boxes, and here we've got indentation of 2, because I like indentation of 2, and horizontal spacing of 1, so that we get if, space, our test, space, then. And this code is all done recursively, so if these things are not strings themselves, they will continue to be executed through the un-parcer until they get to being strings. And so we get the benefit of having these rules be independent of each other, and still getting the whole thing built up as one big final piece of source code. So finally, I'd like to talk a little bit about our progress. So this is still a work in progress. The partial evaluator is definitely still a work in progress. Right now, our current approach is using the simple static method analysis, more in order to get something up and running that we can test and start to see, sort of, get some idea of where, how much we're getting just from the simple static method analysis, but we would like to go further. And right now we're ignoring some more complicated cases. So for instance, right now we are not going inside methods to do partial evaluation. We can partially evaluate a whole method call, but we can't partially evaluate pieces of things inside of it. And we also aren't dealing with lambas and closures in general. Not because they're impossible to deal with, but because they're a little bit more complicated than we want to, again, get something up and running. And so, yeah, I mentioned that we're not partially evaluating internals with methods. The internals of methods actually have somewhat the same problem in libraries, which is to say that if the definition of methods that they call changes during the course of the program, then they are partially evaluated values. Even if we know something statically about the method, it may be different in one place in the program from another. And so in order to make sure that we don't cause something bad to happen, we need to either split those, that method call around that, or we need to only partially evaluate those methods, which this doesn't occur for. Which of course in a lot of programs is most of them, I mean, this is sort of, it's a very defensive method of doing all this stuff. We've been looking at making sure that we preserve the semantics of Ruby no matter what above and beyond our optimization, so. And we're also not doing any dead code elimination right now. One of the things that happens when you start to partially evaluate things is you end up creating, having a lot of useless assignments potentially, as well as code that contributes a value that never gets used. And this basically happens because as you can imagine as we start to propagate constants, propagate literals, we may end up no longer needing to make the original assignment of those literals because they may not manifest themselves throughout the code and there may never be a reference to that variable any longer. And so some of the benefit to be gotten out of partial evaluation is having a clean up step afterwards. And right now, we can't really do it at the same time because the partial evaluation needs to do a forward data flow analysis where it starts at the beginning of the program, not knowing anything and works its way to the bottom of the program. And dead code elimination needs to do exactly the opposite. It needs to start at the bottom of the program and work its way up through the data flow to determine what is live at any given point. I guess I already talked about needing to clean up. So our next steps are to put in a more sophisticated data flow analysis. So we really would like to be able to do something more like the hybrid approach. We also would like to do a control flow based graph to do our data flow analysis instead of using just the abstract syntax tree. And part of the advantage to using something like a control flow graph, which is essentially, you can think of the nodes on the graph as being the code, little blocks of code and the edges between the nodes of the graph are the actual control flow in the graph. Right now with the abstract syntax tree, we have some control flow that's implicit in the code. Things like yield, return, break, next. Anything that causes there to be a jump in where code is being executed is not explicit in the abstract syntax tree, it's just implicit there. And so right now we have a somewhat clumsy mechanism for dealing with that to make sure that we get the correct behavior out of it. But a control flow graph gives us a much more elegant way to handle that because we just add edge where we have these and we can actually make those pieces of control flow explicit. And it also makes more obvious where we need to do our meet points for our environments. We'd also like to do much more precise method analysis. We'd like to look more at the hybrid method, maybe even the call site analysis. The call site analysis can get kind of expensive because you can imagine that, well, you have to make sure that things like recursion and things like that are going to be caught. And of course, one of our goals for this tool was to be able to run this tool on our tool. And so if we're going to be able to do that, we make heavy use of recursion and things like that in the tool because when you're dealing with trees, it's a natural way of handling that. So we want to make sure that we can get that. We'd also like to partially evaluate methods. So we'd like to be able to go in and do some partial evaluation of methods when we know that that method is safe to partially evaluate it. And we'd also like to specialize methods. In a lot of the partial evaluation literature, especially for static programs, you will find these great and complicated ways of specializing methods. In a lot of cases, they've concerned themselves with specialization, but not with finding the opportunities for specialization. So they actually rely on the programmer to specify where those things are possible and where they're going to preserve the semantics of the program. We would prefer not to do that. But if it means that we are able to get some more out of it or we have programmers that are interested in trying to do some of those types of things, then we might actually want to expose this to programmers through some sort of DSL. So either some sort of fake annotation above the code or some sort of call during definition time that would tell us, you can specialize this because these particular values are going to be constant in a lot of cases. So maybe create three versions of this particular method and put them in the correct places in the code rather than just having the one. So finally, for more information, I maintain a blog at fee.cs.indiana.edu. Fee is actually a little play off of SSA transformations for those people who are compiler people. You can also get to our source code, which is available through HTTPS on the same server. And there is a way to get to it without a login or using a guest and no password. I cannot remember off the top of my head what that username is, but I'm going to post it on my blog after I ask my advisor. You can also get to our partial evaluator. So right now our partial evaluator is not doing too much. So it's possible to get it to do simple constant propagation. The method analysis stuff is not quite in there. One of the additional problems with the method analysis is actually that there's a lot of Ruby code that's actually written in C. And analyzing that C code and trying to treat it with Ruby semantics is also a bit non-trivial because there are things that you want to treat as being side affecting and things that you don't. So in the C code, you might have references to global variables and things like that that would normally be considered side effects. But because of the Ruby semantics that they're defining, those variables are actually constant through the program. And it's difficult to determine that without a C analyzer essentially to do something similar to what we're trying to do in Ruby. And finally, you can email me or you can Twitter me. And just to have one more slide of more and more information, we could use your help. And in particular, getting an opportunity to look at more realistic programs is something that's very helpful. So the benchmarks are great. They give us a starting point that Ruby Benchmark Suite is a great place to start. But in a lot of cases, benchmark programs are meant for testing implementations. They're not so much meant for testing these types of optimizations. And the problem that you run into with them is that in a lot of cases, you end up with code that either completely partially evaluates away because it returns a simple value and there are no side effects. And so you end up replacing every instance of factorial with the actual value of the factorial, which is sort of a not very useful example. Or you get the opposite problem where they've been optimized to the point where there's not much left for the partial evaluation to do. And in real programs, there is more opportunity for this. And I also just wanted to thank my advisor and my research group. My advisor is Arun Shaohan. And my research group includes Chunyu Shea and Pushkar, Ratnaalakar, who are both actually working on MATLAB. So thanks for coming and people have questions. Will it be a real-life health complication of the template optimizations? Yeah. Very slow, primarily because of Calvars on CRL4, which can be partial and evaluative of the template simulation time. Can you repeat the question, please? Sorry, so what he was saying is that templates in Rails can be particularly slow because of evaluating some of the helpers. And this partial evaluation would be something that would be very helpful in that. So I'm going to try applying this to Rails templates, which is a life-scale project and non-trivial thing to do. So yes, I mean, so when I started working on this project, the goal right now is, for a lot of Ruby stuff, is to get things working in Rails, right? Particularly when I first started working on this, Rails was sort of the killer app that was bringing people into the Ruby community at the time. There are some challenges there. There are always challenges when C code is involved with this stuff, because the minute we start to pull C code into this, especially outside of the Ruby library, where we can start to define some of the pieces, whenever we start to pull C code into this, we need to have some way of knowing what the characteristics of the methods that are defined and exposed inside Ruby are going to do in order to make sure that we are able to do this partial evaluation. But I am very interested in doing this kind of stuff with the Rails stuff and doing it with templates and things like that, with something that I had also been thinking about a while back. But we aren't there yet, but hopefully, in the not too distant future, we will be. Other questions? Let me actually follow up with that point. We just said, if you are going to apply this work and also, thank you so much for your work, if you are going to apply it to Rails templates in particular, there was a previous effort by Stephen, Stephen Cage or Case AES, and I think it's called Template Optimizer or something. Okay. I had some experience using it a couple of years ago, and I think that what he found, and unfortunately I can't remember the specific use case, what we found was basically that although there are these kind of simple breakdown cases of, oh, I'm going to take this route and look up, and I'm going to turn it into a string with block post ID for the ID and string interpolation. It was very hard to support the simple cases without trimming up on every Rails, every significant sort of Rails helper method actually has like 60 fucking options. So it was very difficult. So anyway, I just want to give you a pointer to that. So if you start to stumble into that, maybe you can learn from his difficulties. Yeah, no, that's definitely a good point. So just in case that wasn't heard and to get it on the record, there has been previous attempts to do a Template Optimizer and in a lot of cases, a lot of the sort of useful cases it proved not to work very well because of so many, there being so many options and helpers and things like that, it made it very difficult to do the analysis. A couple other questions. How far from a minimum viable application where you can, for example, not optimize the whole application just like few parts of it where the coder itself can say, hey, just work this part out as if you're doing like same side workings and optimize just like few parts of the code that are not that difficult and don't have closure. Right, right. So the question is sort of exactly our way, I guess, to restate it. To minimal useful thing. So right now we don't have the method analyzer completely finished and so the method analyzer being finished is the first part that's actually going to as my advisor keeps joking with me be able to handle 2 plus 2. So right now we're doing constant propagation and we are only handling things that have a literal expression. Part of the reason why we're only handling things that have literal expressions fortunately this is pretty wide in Ruby actually but part of the reason why we're only handling literal expressions is because we don't have a good way to take something that has been defined and let it go through some manipulation and then sort of rematerialize it in source code because we have to go back to source code. But the method analyzer stuff is something that I'm hoping to finish up actually in the next couple of weeks and so it will be to the point where you can have it do simple things. The other side of this whole problem is that in Ruby we need to get at the characteristics of the base library and one of the ideas the basic idea for this right now and this is not a scalable idea this isn't sort of the end idea but I actually did go through and try and do analysis of this stuff and found that it was very difficult of the C code to sort of get at the base Ruby library stuff so right now the approach that we're going to take is defining the characteristics of the basic Ruby library using a static method definition basically so rather than saying okay you're going to look at the Ruby implementation of plus or you're going to look at the C implementation of plus we're actually saying you know what plus it takes one argument it never gets changed by a call to plus and this is always safe to call and then if through the process of the program the programmer redefines plus that former definition will just get thrown away or it will get preserved if someone does something like a method chaining so when you method chain it will remember that and then it will throw away the value for plus but it will remember a ridge plus or whatever so hopefully in the next couple of weeks basically the answer to that anything else? What type of speed up do you see in like static languages that you can do this in? That's a good question it's actually not one that I have an answer for so partial evaluation has been something that was talked about like 20 years ago in static languages and then again like 10 years ago in static languages it sort of goes through cycles I haven't actually looked at anything recently I don't think this is a very common thing to do for a lot of static languages partially because in order to get all of it to work a lot of the previous implementations have actually said well we're going to require the programmer to specify what can be partially evaluated what can't be partially evaluated and the moment you start to put those types of requirements on it you immediately put yourself in some sort of niche where you're not going to be generally available for everyone so off the top of my head I don't know it is also a technique that's been used a lot in functional programming and I actually don't I also don't really have a good feel for exactly how much it gets used there but there they are trying to do it in a more automated fashion and it also gets used for logic programming and I believe in prolog it does actually get used in several of the implementations including the WAM anything else? alright, thank you very much