Tip:
Highlight text to annotate it
X
In the last couple of videos we have talked about code generation for simple
programming language and I mentioned at the end of the last video that realistic
compilers do things a bit differently and in particular they do a better job of
keeping values and registers and of managing the temporaries that have to be
stored in the activation record. We're actually going to talk about both of those
problems. In this particular video, we're only going to talk about the second one
and so we're going to be covering a better ways for compilers to manage temporary
values. So the biggest idea which we've already seen is to keep temporaries in the
activation record. Now, this is not as efficient as keeping temporaries in
registers but that's the subject of a future video, we're not going to talk
about that today. What we're going to talk about is improving the language we manage
temporaries that happened to be in the activation record for whatever reasons. So
why, it doesn't matter why we want them to be into activation record, but given that
it's there, that's the most efficient code that we can generate And, the improvement
that we're going to make Is have the co-generator assign a fixed location In
the activation record for each temporaries. We're going to pre-allocate
memory or a spot in the activation record for each temporary and then we will be
able to save and restore the temporary without having to do the stack pointer
manipulations. So, let's take a look at the [inaudible] program for a simple
programming language. Here is the Fibonacci function again and let me change
colors to something that says more contrast and let's think about how many
temporaries we need to evaluate this functions. So, this function body when it
executes we'll need a certain number of temporaries and if we know how many
temporaries that needs in advance then we could allocate the space for those in the
activation record rather having to do push and pop, pushing and popping from the
stack at runtime. So, let's take a look and if then else is going t o involve a
temporary because it always do this predicate comparison here, we're going to
have to evaluate the, the first argument to the predicate and then save the result
of that while we evaluate the second argument to the predicate. So this one
involve one temporary, we'll need one temporary for that predicate. Similarly
for this predicate, to evaluate it since it's a two argument operation in
comparison, we'll also need one temporary for that. 1010. There's this expression
over here which is kind of complicated. How many temporaries will we need for
these? Well, remember how this works. So, evaluate the first expression and then we
save the results of that so this will require one temporary for the result of
the called fib going to have to be saved and only evaluate the plus And while we
are evaluating the call, the fib though is actually, before we evaluate to call the
fib, we have to evaluate the argument of fib and that involve the subtractions. We
also need one temporary here for the subtraction. Okay And now we have about
the second side of the, this edition here. Well this also involves a subtraction.
Okay So, we got to have one temporary here to hold on to the value x while we're
evaluating the minus to compute the value of the argument before we call
[inaudible]. Okay? So how many temporaries do we need in total? While we need one
here for the predicate, but notice that once the predicate is decided, once we
know the answer to whether this predicate is true or false, we don't need that
temporary anymore. So in fact, that temporary can be reclaimed; we don't need
the space for that temporary anymore by the time we get to the false branch. And
again, once this predicate is evaluated, we don't need the space for that temporary
anymore, okay? So now we're down to the plus. The first thing that happens is we
evaluate the argument to this first call the fib. Once that's evaluated, we don't
need the temporary for it anymore. Now the results of fib has to be saved somewhere
while we do the plus, okay? And then we'r e going to have to evaluate the argument
to the second call of fib and then notice that this happens while we still need this
temporary here so in fact, we need both of these temporaries at the same time. Okay
because while we're evaluating this argument, the second call of fib, we still
need to be holding on to the first argument to the plus. And so in fact this
particular function can be evaluated with just two temporaries. So all the space we
need to compute the value of this function body. So in general, we can define a
function nt of e that computes a number of temporaries needed to evaluate e1 + e2.
So, that's going to need at least as many temporaries as e1. Okay, so if we need a
number of temporary's k to evaluate e1, let's have at least k temporaries to
evaluate the whole expression And then, we'll also need at least as many
temporaries as it's needed to evaluate the two+1 because we have to hold on to the
value of e2 while we are evaluating so we have to hold on the value of e1 while
we're evaluating the two. Okay And it's going to be the maximum. Over these two so
it'll be the maximum number with between the maximum number of temporaries need to
evaluate a one and one + the number of temporaries to evaluate two. That would be
the total number of temporaries, the minimum number of temporaries needed to
evaluate e1 + e2 And the reason is a max instead of a sum. Is that once we've
evaluate e1 we don't need any of the space that was used to evaluate e1 anymore. All
those temporaries are done. All we need is the answer. We don't need the immediate
results and that means that the temporaries that were used to evaluate e1
can be reused to evaluate e2. So, generalizing from that one example, here
is the system of equations that subscribes the number of temporaries needed to
evaluate every kind of expression in our little language. So, let's take a look.
So, we already talked about e1+e2 is just the max of over the number or temporaries
to value of e1 and one + number of temporaries to value of e2. So, e1-e2 is
exactly the same thing because the same structure is a different computational
operation but is a binary operation and we have to save the value of e1 while
evaluated e2. So, it's the same formula. [inaudible] Now for if and else well what
do we need? We need one, I'm sorry we need, it's going to max again. It's going
to be max over some number of different quantities. How many temporaries might we
need? Well, we might need as many temporaries or as needed to evaluate the
value of e1 and we certainly need at least as many, alright. So, if you want to take
a certain number of temporaries, the whole f and l is going to require at least as
many temporaries. Now of course, once e1 is done evaluating, we don't need its
temporaries anymore. And, and we can evaluate e2, okay. And while we are
evaluating e2, we have to hold on. To the results of e1, that's where the one plus
comes from. So, to that, while we're evaluating e2, we need one plus the number
of temporaries to evaluating two to hold all the temporaries of the computation.
And then once the predicate is done, we don't need any of those temporaries
anymore at all ad we're going to evaluate either e3 or e4. And so then, we just need
however many temporaries each of those requires and whatever the maximum is over
these four quantities, that's the minimum number of temporaries we can get away with
to evaluate the entire if then else. Let's take a look at a function call. So that
the space needed for the function call is number of temporaries, the max over the
number of temporaries to evaluate anyone of the arguments and this is actually an
interesting case because notice. That we don't need, we don't have anywhere in this
formula space for the results for the e1 through en Of course once we've evaluated
the e1 then we need to save it somewhere and so you would think that we might see
some numbers in here representing the temporary space needed to hold on to the
results of the evaluating these expressions. And the reason that we don't
have that in here is that. Even though those values are saved, they are indeed
saved; they're not saved in the current activation record The space where the
results of e1 and the results of all, any of the arguments. Yeah, again, is saved in
the new activation record that we're building And so, the space for the, the
results of e1 through en is that those values are stored in new activation record
and that storage of current activation record and we're trying to compute the
number of temporaries needed to evaluate inside of the current activation And then
for integer, that doesn't take any space at all to require any temporaries I mean.
So there's zero temporaries required for that and also for a variable reference so
it requires no temporaries. So now let's go through our example and work out
systematically using the equations. How many temporaries we will need? Okay? So,
here for this if then else, remember it was going to be the max over the number
required to evaluate e1, well that zero. One + the number to evaluate e2 which is
the second expression in the predicate so that would be one, because the number one
requires zero temporaries and the one, the we have one hold on to x, all right? And
then max over the branches. So, to evaluate zero requires Zero temporaries
and now. We have to compute. The number required here. Okay so once again to
evaluate the first expression if and else requires zero temporaries to evaluate the
second one we require one. One + the number required, one + zero to evaluate
that constant we got zero temporaries and now for the last expression how many will
this one will require. Well this is going to require zero for this guy. One for the
second argument so to evaluate fib is going to require one temporary, okay and
then it's going to be one plus over here. We have to hold on to the results there.
The value of x - two so how much that going to require? That is going to require
the max of zero and one + zero okay so this would be one alright so we have over
here we have one + one = two okay and now we're taking the max over two and one. So
that's two, okay? And this is the last expression in the, our if and else. So
clearly, this if then else here will require two temporaries okay? Because the
max over the number required for either part of the predicate, the then branch and
the else branch And now, this whole expression. Requires two temporaries and
that'll be the max of the four components of the outer if then else And so then for,
for the entire expression we get two temporaries. Once it computed the number
of temporaries required to evaluate the function value, we can add that much space
to the activation record. So, now our activation record is going to require two
+ n + nt (e) elements And so, the two of course are for the return address for the
frame pointer. The n is for the n argument of the function And then, the rest of it
is just the space required for the temporaries And now we can talk about how
we're going to layout the activation record. We'll leave the first part of it
the same, so everything up to the return address is laid out just before. First the
color string pointer then the and arguments in reverse order, and then the
return address. And then after the return address come the and locations or the
nt(e), excuse me, locations for the temporaries. Now, that we know how many
temporaries or intermediate values we need to evaluate a function and we also know
where those intermediate value is going to be stored in activation record. The last
thing we need to know in order to code generation is how many temporaries are in
use at each point in the program. Change colors here and so the way we're going to
do that is we're going to add a new argument to co-generation which is the
position of the next available temporary. So as temporaries gets used up, this
argument, the co-generation will change while other expressions to save their
values and save places without stepping on temporaries that are already having saved
by all other expressions. And as you'll see in a, in a moment here when we do an
example, the temporary area of the activation r ecord is going to be used
like a small fixed size stack. Essentially, we're going to have the same
stack discipline that we had before only all the computation on the stack pointer,
all the discussion, all the computation of what all sets to use has already been done
by the compiler. So, what we used to do by pushing and popping element from the stack
in the generated code allow that computation has been moved into the
compiler and all that happens now is a bunch of stores and load. To fix off that
from the frame pointer So let's take a look at how this works. Here's the code
that we had for e1 + e2 under the old scheme where we didn't have a separate
area in the activation records for temporaries. So we would generate a code
for e1, and then we would save the results of e1 on the stack, and that would be done
by saving the value of the accumulator under the stack and then we would have to
adjust the stack pointer And then after we had evaluated the two, then we would load
the results of e1 back into a temporary register, we could do the add And then we
could pop the value off of the stack, the intermediate value off of the stack Down
to the new scheme. Co-generations going to take a second argument saying what is the
position of the next available temporary so what is the position of the next unused
temporary inside of the activation record and so now we generate code for e1 and we
pass along the argument okay because e1 may itself have some temporaries that it
needs to store And, and then after you [inaudible] to evaluating, now we just do
a direct store into, into the activation record at all set empty from the frame
pointer And so now as we have to do in store, we have to save e1 in the
activation record so we have it for later on but we have to do any manipulation of
the stacks. So, we could place two instructions here by one And then we
generate code for e2 but now. We just save the temporary value at position at, at all
set empty from the frame pointer so the next available temporary would be of
address empty o r offset, excuse me, nt+4 And then after each was evaluating, now we
have to load the value of e1 back into a temporary and again that was all set NT
from the frame pointer of the current activation record and then we can do the
add and once again we saved the manipulation of the stack pointers. So
this code sequence here is two instructions shorter than the one we had
before and this actually substantially more efficient.