MIT OpenCourseWare
http://ocw.mit.edu


6.001 Structure and Interpretation of Computer Programs, Spring 2005

Transcript – 9A: Register Machines



[MUSIC PLAYING - "JESU, JOY OF MAN'S DESIRING" BY JOHANN SEBASTIAN BACH]
PROFESSOR: Well, up 'til now, I suppose, we've been learning about a lot of techniques for

organizing big programs, symbolic manipulation a bit, some of the technology that you use
for establishing languages, one in terms of another, which is used for organizing very large

programs. In fact, the nicest programs I know look more like a pile of languages than like a
decomposition of a problem into parts. Well, I suppose at this point, there are still,

however, a few mysteries about how this sort of stuff works.




And so what we'd like to do now is diverge from the plan of telling you how to organize big

programs, and rather tell you something about the mechanisms by which these things can
be made to work. The main reason for this is demystification, if you will, that we have a lot

of mysteries left, like exactly how it is the case that a program is controlled, how a
computer knows what the next thing to do is, or something like that. And what I'd like to do

now is make that clear to you, that even if you've never played with a physical computer

before, the mechanism is really very simple, and that you can understand it completely with
no trouble.




So I'd like to start by imagining that we-- well, the way we're going to do this, by the way,

is we're going to take some very simple Lisp programs, very simple Lisp programs, and
transform them into hardware. I'm not going to worry about some intermediate step of

going through some existing computer machine language and then showing you how that

computer works, because that's not as illuminating. So what I'm really going to show you is
how a piece of machinery can be built to do a job that you have written down as a program.

That program is, in fact, a description of a machine.




We're going to start with a very simple program, proceed to show you some simple

mechanisms, proceed to a few more complicated programs, and then later show you a not
very complicated program, how the evaluator transformsinto a piece of hardware. And of

course at that point, you have made the universal transition and can execute any program
imaginable with a piece of well- defined hardware.





Well, let's start up now, give you a real concrete feeling for this sort of thing.Let's start with
a very simple program. Here's Euclid's algorithm. It's actually a little bit more modern than
Euclid's algorithm. Euclid's algorithm for computing the greatest common divisor of two

numbers was invented 350 BC, I think. It's the oldest known algorithm.




But here we're going to talk about GCD of A and B, the Greatest Common Divisor or two

numbers, A and B. And the algorithm is extremely simple. If B is 0, then the result is going
to be A. Otherwise, the result is the GCD of B and the remainder when A is divided by B.




So this we have here is a very simple iterative process. This a simple recursive procedure,

recursively defined procedure, recursive definition, which yields an iterative process. And

the way it works is that every step, it determines whether B was zero. And if B is 0, we got
the answer in A. Otherwise, we make another step where A is the old B, and B is the

remainder of the old A divided by the old B. Very simple.




Now this, I've already told you some of the mechanism by jus t saying it that way. I set it in

time. I said there are certain steps, and that, in fact, one of the things you can see here is
that one of the reasons why this is iterative is nothing is needed of the last step to get the

answer. All of the information that's needed to run this algorithm is in A and B. It has two
well-defined state variables.





So I'm going to define a machine for you that can compute you GCDs. Now let's see. Every
computer that's ever been made that's a single-process computer, as opposed to a

multiprocessor of some sort, is made according to the same plan. The plan is the computer
has two parts, a part called the datapaths, and a part called the controller.





The datapaths correspond to a calculator that you might have. It contains certa in registers
that remember things, and you've all used calculators. It has some buttons on it and some

lights. And so by pushing the various buttons, you can cause operations to happen inside
there among the registers, and some of the results to be displayed.





That's completely mechanical. You could imagine that box has no intelligence in it. Now it
might be very impressive that it can produce the sine of a number, but that at least is

apparently possibly mechanical. At least, I could open that up in the same way I'm about to
open GCD.




So this may have a whole computer inside of it, but that's not interesting. Addition is

certainly simple. That can be done without any further mechanism.
Now also, if we were to look at the other half, the controller, that's a part that's dumb, too.
It pushes the buttons. It pushes them according to the sequence, which is written down on

a piece of paper, and observes the lights.




And every so often, it comes to a place in a sequence that says, if light A is on, do this

sequence. Otherwise, do that sequence. And thereby, there's no complexity there either.
Well, let's just draw that and see what we feel about that.





So for computing GCDs, what I want you to think about is that there are these registers. A
register is a place where I store a number, in this case. And this one's called a. And then

there's another one for storing b.




Now we have to see what things we can do with these registers, and they're not entirely

obvious what you can do with them. Well, we have to see what things we need to do with
them. We're looking at the problem we're trying to solve.





One of the important things for designing a computer, which I think most designers don't
do, is you study the problem you want to solve and then use what you learn from studying

the problem you want to solve to put in the mechanisms needed to solve it in the computer
you're building, no more no less. Now it may be that the problem you're trying to sol ve is

everybody's problem, in which case you have to build in a universal interpreter of some
language. But you shouldn't put any more in than required to build the universal interpreter

of some language. We'll worry about that in a second.




OK, going back to here, let's see. What do we have to be able to do? Well, somehow, we

have to be able to get B into A. We have to be able to get the old value of B into the value
of A. So we have to have some path by which stuff can flow, whatever this information is,

from b to a. I'm going to draw that with by an arrow saying that it is possible to move the
contents of b into a, replacing the value of a. And there's a little button here which you push

which allows that to happen. That's what the little x is here.




Now it's also the case that I have to be able to compute the remainder of a and b. Now that

may be a complicated mess. On the other hand, I'm going to make it a small box. If we
have to, we may open up that box and look inside and see what it is.
So here, I'm going to have a little box, which I'm going to draw this way, which we'll call the

remainder. And it's going to take in a. That's going to take in b. And it's going to put out
something, the remainder of a divided by b.





Another thing we have to see here is that we have to be able to test whether b is equal to
0. Well, that means somebody's got to be looking at-- a thing that's looking at the value of

b. I have a light bulb here which lights up if b equals 0. That's its job.




And finally, I suppose, because of the fact that we want the new value of a to be the old

value of b, and simultaneously the new value of b to be something I've done with a, and if I
plan to make my machine such that everything happens one at a time, one motion at a

time, and I can't put two numbers in a register, then I have to have another place to put
one while I'm interchanging. OK? I can't interchange the two things in my hands, unless I

either put two in one hand and then pull it back the other way, or unless I put one down,
pick it up, and put the other one, like that, unless I'm a juggler, which I'm not, as you can

see, in which case I have a possibility of timing errors. In fact, much of the type of
computer design people do involves timing errors, of some potential timing errors, which I

don't much like.




So for that reason, I have to have a place to put the second one of them down. So I have a

place called t, which is a register just for temporary, t, with a button on it. And then I'll take
the result of that, since I have to take that and put into b, over here, we'll take the result of

that and go like this, and a button here. So that's the datapaths of a GCD machine.




Now what's the controller? Controller's a very simple thing, too. The machine has a state.





The way I like to visualize that is that I've got a maze. And the maze has a bunch of places
connected by directed arrows. And what I have is a marble, which represents the state of

the controller. The marble rolls around in the maze. Of course, this analogy breaksdown for
energy reasons. I sometimes have to pump the marble up to the top, because it's going to

otherwise be a perpetual motion machine. But not worrying about that, this is not a physical
analogy.





This marble rolls around. And every time it rolls around certain bumpers, like in a pinball
machine, it pushes one of these buttons. And every so often, it comes to a place, which is a

division, where it has to make a choice. And there's a flap, which is c ontrolled by this. So
that's a really mechanical way of thinking about it.
Of course, controllers these days, are not built that way in real computers. They're built with
a little bit of ROM and a state register. But there was a time, like the DEC PDP-6, where

that's how you built the controller of a machine. There was a bit that ran around the delay
line, and it triggered things as it went by. And it would come back to the beginning and get

fed round again.




And of course, there were all sorts of great bugs you could have like two bits going around,

two marbles. And then the machine has lost its marbles. That happens, too. Oh, well.




So anyway, for this machine, what I have to do is the following. I'm going to start my maze

here. And the first thing I've got to do, in a notation which many of you are familiar with, is
b equal to zero, a test. And there's a possibility, either yes, in which case I'm done.

Otherwise, if no, then I'm going have to roll over some bumpers.




I'm going to do it in the following order. I want to do this interchange game. Now first, since

I need both a and b, but then the first-- and this is not necessary-- I want to collect this.
This is the thing that's going to go into b. So I'm going to say, take this, which depends

upon both a and b, and put the remainder into here. So I'm going to push this button first.
Then, I'm going to transfer b to a, push that button, and then I transfer the temporary into

b, push that button. So a very sequential machine, it's very inefficient. But th at's fine right

now.




We're going to name the buttons, t gets remainder. a gets b. And b gets t. And then I'm
going to go around here and it's to go back to start.





And if you look, what are we seeing here? We're seeing the various-- what I really have is
some sort of mechanical connection, where t gets r controls this thing. And I have here that

a gets b controls this fellow over here, and this fellow over here.




Boy, that's absolutely pessimal, the inverse of optimal. Every line heads across every othe    r

line the way I drew it. I suppose this goes here, b gets t.




Now I'd like to run this machine. But before I run the machine, I want to write down a

description of this controller, just so you can see that these things, of course, as usual, can
be written down in some nice language, so that we don't have to always draw these

diagrams. One of the problems with diagrams is that they take up a lot of space. And for a
machine this small, it takes two blackboards. For a machine that's the evaluator machine, I

have trouble putting it into this room, even though it isn't very big. So I'm going to make a
little language for this that's just a description of that, saying define a machine we'll call

GCD.




Of course, once we have something like this, we have a simulator for it. And the reason why

we want to build a language in this form, is because all of a sudden we can manipulate
these expressions that I'm writing down. And then of course I can write things that can

algebraically manipulate these things, simulate them, all that sort of things that I might
want to do, perhaps transform them as a layout, who knows. Once I have a nice

representation of registers, it has certain registers, which we can call A, B, and T. And

there's a controller.




Actually, a better language, which would be more explicit, would be one which named every
button also and said what it did. Like, this button causes the contents of T to go to the

contents of B. Well I don't want to do that, because it's actually harder to read to do that,

and it takes up more space. So I'm going to have that in the instructions written in the
controller.




It's going to be implicit what the operations are. They can be deduced by reading these and

collecting together all the different things that can be done. Well, let's just look at what

these things are. There's a little loop that we go around which says branch, this is the
representation of the little flap that decides which way you go here, if 0 fetch of B, the

contents of B, and if the contents of B is 0, then go to a place called done.




Now, one thing you're seeing here, this looks very much like a traditional computer

language. And what you're seeing here is things like labels that represent places in a
sequence written down as a sequence. The reason why they're needed is because over

here, I've written something with loops.




But if I'm writing English text, or something like that, it's hard to refer to a place. I don't
have arrows. Arrows are represented by giving names to the places where the arrows

terminate, and then referring to them by those names. Now this is just an encoding. There's

nothing magical about things like that.
Next thing we're going to do is we're going to say, how do we do T gets R? Oh, that's easy

enough, assign. We assign to T the remainder. Assign is the name of the button. That's the
button-pusher. Assign to T the remainder, and here's the representation ofthe operation,

when we divide the fetch of A by the fetch of B.




And we're also going to assign to A the fetch of B, assign to B the result of getting the

contents of T. And now I have to refer to the beginning here. I see, why don't I call that
loop like I have here? So that's that reference to that arrow. And when we're done, we're

done. We go to here, which is the end of the thing.




So here's just a written representation of this fragment of machinery that we've drawn here.

Now the next thing I'd like to do is run this. I want us to feel it running. Never done this
before, you got to do it once.





So let's take a particular problem. Suppose we want to compute the GCD of a equals 30 and
b equals 42. I have no idea what that is right now. But a 30 and b is 42. So that's how I

start this thing up.




Well, what's the first thing I do? I say is B equal to 0, no. Then assign to T the remainder of

the fetch of A and the fetch of B. Well the remainder of 30 when divided by 42 is itself 30.
Push that button.




Now the marble has rolled to here. A gets B. That pushes this button. So 42 moves into

here.




B gets C. Push that button. The 30 goes here. Let met just interchange them.





Now let's see, go back to the beginning. B 0, no. T gets the remainder. I suppose the
remainder when dividing 42 by 30 is 12. I push that one.




Next thing I do is allow the 30 to go to here, push this one, allow the 12 to go to here. Go

around this thing. Is that done? No. How about-- so now I have to find out the remainder of

30 divided by 12. And I believe that's 6. So 6 goes here on this button push. Then the next
thing I push is this one, which the 12 goes into here.
Then I push this button. The 6 gets into here. Is 6 equal to 0? No. OK.




So then at that point, the next thing to do is divide it. Ooh, this has got a remainder of 0.

Looks like we're almost done.




Move the 6 over here next. 0 over here. Is the answer 0? Yes. B is 0, therefore the answer

is in A.




The answer is 6. And indeed that's right, because if we look at the original problem, what
we have is 30 is 2 times 3 times 5, and 42 is 2 times 3 times 7. So the greatest common

divisor is 2 times 3, which is 6.




Now normally, we write one other little line here, just to make it a little bit clearer, which is

that we leave in a connection saying that this light is the guy that that flap looks at. Of
course, any real machine has a lot more complicated things in it than what I've just shown

you. Let's look for a second at the first still store.




Wow. Well you see, for example, one thing we might want to do is worry about the

operations that are of IO form. And we may have to collect something from the outside. So
a state machine that we might have, the controller may have to, for example, get a value

from something and put register a to load it up. I have to master load up register b with

another value.




And then later, when I'm done, I might want to print the answer out. And of course, that
might be either simple or complicated. I'm writing, assuming print is very simple, and read

is very simple. But in fact, in the real world, those are very complicated operations, usually

much, much larger and more complicated than the thing you're doing as your problem
you're trying to solve.




On the other hand, I can remember a time when, I remember using IBM 7090 computer of

sorts, where things like read and write of a single object, a single number, a number, is a
primitive operation of the IO controller. OK?
And so we have that kind of thing in there. And in such a machine, well, what are we really

doing? We're just saying that there's a source over here called "read," which is an operation
which always has a value. We have to think about this as always having a value which can

be gated into either register a or b. And print is some sort of thing which when you gate it
appropriately, when you push the button on it, will cause a print of the value that's currently

in register a. Nothing very exciting.




So that's one sort of thing you might want to have. But these are also other things that are

a little bit worrisome. Like I've used here some complicated mechanisms.




What you see here is remainder. What is that? That may not be so obvious how to compute.

It may be something which when you open it up, you get a whole machine. OK? In fact,
that's true.





For example, if I write down the program for remainder, the simplest program for it is by
repeated subtraction. Because of course, division can be done by repeated subtraction of

numbers, of integers. So the remainder of N divided by D is nothing more than if N is less
than D, then the result is N. Otherwise, it's the remainder when we subtract D from N with

respect to D, when divided by D. Gee, this looks just like the GCD program.




Of course, it's not a very nice way to do remainders. You'd really want to use something like

binary notation and shift and things like that in a practical computer. But the point of that is
that if I open this thing up, I might find inside of it a computer.





Oh, we know how to do that. We just made one. And it could be another thing just like this.




On the other hand, we might want to make a more efficient or better-structured machine,
or maybe make use of some of the registers more than once, or some horrible mess like

that that hardware designers like to do, and for very good reasons. So for example, here's a

machine that you see, which you're not supposed to be able to read. It's a little bit
complicated. But what it is is the integration of the remainder into the GCD machine. A nd it

takes, in fact, no more registers. There are three registers in the datapaths. OK?




But now there's a subtractor. There are two things that are tested. Is b equal to 0, or is t
less than b?
And then the controller, which you see over here, is not much more complicated. But it has
two loops in it, one of which is the main one for doing the GCD, and one of which is the

subtraction loop for doing the remainder sub-operation. And there are ways, of course, of, if
you think about it, taking the remainder program. If I take remainder, as you see over

there, as a lambda expression, substitute it in for remainder over here in the GCD program,

then do some simplification by substituting a and b for remainder in there, then I can
unwind this loop. And I can get this piece of machinery by basically, a little bit of algebraic

simplification on the lambda expressions.




So I suppose you've seen your first very simple machines now. Are there any questions?

Good. This looks easy, doesn't it? Thank you. I suppose, take a break.




[MUSIC PLAYING - "JESU, JOY OF MAN'S DESIRING" BY JOHANN SEBASTIAN BACH]




PROFESSOR: Well, let's see. Now you know how to make an iterative procedure, or a

procedure that yields an iterative process, turn into a machine. I suppose the next thing we
want to do is worry about things that reveal recursive processes. So let's play w ith a simple

factorial procedure.




We define factorial of N to be if n is 1, the result is 1, using 1 right now to decrease the

amount of work I have to do to simulate it, else it's times N factorial N minus 1. And what's
different with this program, as you know, is that after I've computed factorial of N minus 1

here, I have to do something to the result. I have to multiply it by N.




So the only way I can visualize what this machine is doing, because of the fact -- think of it

this way, that I have a machine out here which somehow needs a factorial machine in order
to compute its answer. But this machine, the outer machine, has to exist before and after

the factorial machine, which is inside. Whereas in the iterative case, the outer machine
doesn't need to exist after the inner machine is running, because you never need to go back

to the outer machine to do anything.




So here we have a problem where we have a machine which has the same machine inside

of it, an infinitely large machine. And it's got other t hings inside of it, like a multiplier, which
takes some inputs, and there's a minus 1 box, and things like that. You can imagine that's

what it looks like.
But the important thing is that here I have something that happens before and after, in the
outer machine, the execution of the inner machine. So this machine has to have a life. It

has to exist on both times sides of this machine.




So somehow, I have to have a place to store the things that this thing needs to run. Infinite

objects don't exist in the real world. What we have to do is arrange an illusion that we have
an infinite object, we have an infinite amount of hardware somewhere.





Now of course, illusion's all that really matters. If we can arrange that every time you look
at some infinite object, the part of it that you look at is there, then it's as infinite as you

need it to be. And of course, one of the things we might want to do, just look at this thing
over here, is the organization that we've had so far involves having a part of the machine,

which is the controller, which sits right over here, which is perfectly finite and very simple.
We have some datapaths, which consist of registers and operators. And what I propose to

do here is decompose the machine into two parts, such that there is a part which is

fundamentally finite, and some part where a certain amount of infinite stuff can be kept.




On the other hand this is very simple and really isn't infinite, but it's just very large. But it's
so simple that it could be cheaply reproduced in such large amounts, we call it memory,

that we can make a structure called a stack out of it which will allow us to, in fact, simulate

the existence of an infinite machine which is made out of a recursive nest of many
machines. And the way it's going to work is that we're going to store in this place called the

stack the information required after the inner machine runs to resume the operation of the
outer machine.





So it will remember the important things about the life of the outer machine that will be
needed for this computation. Since, of course, these machines are nested in a recursive

manner, then in fact the stack will only be accessed in a manner which is the last thing that
goes in is the first thing that comes out. So we'll only need to access some little part of this

stack memory.




OK, well, let's do it. I'm going to build you a datapath now, and I'm going to write the

controller. And then we're going to execute this to see how you do it. So the factorial
machine isn't so bad. It's going to have a register called the value, where the answer is

going to be stored, and a registered called N, which is where the number I'm taking factorial
will be stored, factorial of. And it will be necessary in some instances to connect VAL to N.
In fact, one nice case of this is if I just said over here, N, because that would be right for N
equal 1N. And I could just move the answer over there if that's important. I'm not worried

about that right now.




And there are things I have to be able to do. Like I have tobe able to, as we see here,

multiply N by something in VAL, because VAL is the result of computing factorial. And I have
to put the result back into VAL.





So here we can see that the result of computing a factorial is N times the result of
computing a factorial. VAL will be the representation of the answer of the inner factorial.

And so I'm going to have to have a multiplier here, which is going to sample the value of N
and the value of VAL and put the result back into VAL like that.





I'm also going to have to be able to see if N is 1. So I need a light bulb. And I suppose the
other thing I'm going to need to have is a way of decrementing N. So I'm going to have a

decrementer, which takes N and is going to put back the result into N. That's pretty much
what I need in my machine.





Now, there's a little bit else I need. It's a little bit more complicated, because I'm also going
to need a way to store, to save away, the things that are going to be needed for resuming

the computation of a factorial after I've done a sub-factorial. What's that? One thing I need
is N.





So I'm going to build here a thing called a stack. The stack is a bunch of stuff that I'm going
to write in sequentially. I don't how long it is. The longer it is, the better my illusion of

infinity. And I'm going to have to have a way of getting stuff out of N and into the stack and
vice versa. So I'm going to need a connection like this, which is two -way, whereby I can

save the value of N and then restore it some other time through that connection. This is the
stack.





I also need a way of remembering where I was in the computation of factorial in the outer
program. Now in the case of this machine, it isn't very much a problem. Factorial always

returns, has to go back to the place where we multiply by N, except for the last time, when
it has to return to whatever needs the factorial or go to done or stop. However, in general,

I'm going to have to remember where I have been, because I might have computed
factorial from somewhere else. I have to go back to that place and continue there.
So I'm going to have to have some way of taking the place where the marble is in the finite
state controller, the state of the controller, and storing that in the stack as well. And I'm

going to have to have ways of restoring that back to the state of the-- the marble. So I
have to have something that moves the marble to the right place.





Well, we're going to have a place which is the marble now. And it's called the continue
register, called continue, which is the place to put the marble nexttime I go to continue.

That's what that's for. And so there's got to be some path from that into the controller.




I also have to have some way of saving that on the stack. And I have to have some way of

setting that up to have various constants, a certain fixed number of constants. And that's
very easy to arrange. So let's have some constants here. We'll call this one after-fact. And

that's a constant which we'll get into the continue register, and also another one called fact-
done.





So this is the machine I want to build. That's its datapaths, at least. And it mixes a little
with the controller here, because of the fact that I have to remember where I was and

restore myself to that place.




But let's write the program now which represents the controller.I'm not going to write the

define machine thing and the register list, because that's not very interesting. I'm just going
to write down the sequence of instructions that constitute the controller.





So we have assign, to set up, continue to done. We have a loop which says branch if equal
1 fetch N, if N is 1, then go to the base step of the induction, the simple case.





Otherwise, I have to remember the things that are necessary to perform a sub -factorial. I'm
going to go over here, and I have to perform a sub-factorial. So I have to remember what's

needed after I will be done with that.




See, I'm about to do something terrible. I'm about to change the value of N. But this guy
has to know the old value of N. But in order to make the sub-factorial work, I have to

change the value of N. So I have to remember the old value. And I also have to remember

where I've been. So I save up continue.
And this is an instruction that says, put something in the stack. Save the contents of the
continuation register, which in this case is done, because later I'm going to change that,

too, because I need to go back to after-fact, as well. We'll see that.




We save N, because I'm going to need that for later. Assign to N the decrement of fetch N.

Assign continue, we're going to look at this now, to after, we'll call it. That's a good name
for this, a little bit easier and shorter, and fits in here.





Now look what I'm doing here. I'm saying, if the answer is 1, I'm done. I'm going to have to
just get the answer. Otherwise, I'm going to save the continuation, save N, make N one less

than N, remember I'm going to come back to someplace else, and go back and start doing
another factorial.





However, I've got a different machine [? in me ?] now. N is 1, and continue is something
else. N is N minus 1.





Now after I'm done with that, I can go there. I will restore the old value of N, which is the
opposite of this save over here. I will restore the continuation.




I will then go to here. I will assign to the VAL register the product of N and fetch VAL. VAL

fetch product assign.




And then I will be done. I will have my answer to the sub -factorial in VAL. At that point, I'm

going to return by going to the place where the continuation is pointing. That says, go to
fetch continue.





And then I have finally a base step, which is the immediate answer. Assign to VAL fetch N,
and go to fetch continue. And then I'm done.





Now let's see how this executes on a very simple case, because then we'll see the use of
this stack to do the job we need. This is statically what it's doing, but we have look

dynamically at this. So let's see.
First thing we do is continue gets done. The way that happened is I pushed this. Let's call
that done the way I have it. I push that button. Done goes into there.





Now I also have to set this thing up to have an initial value. Let's consider a factorial of
three, a simple case. And we're going to start out with our stack growing over here. Stacks

have their own little internal state saying where they are, where the next place I'm going to
write is.





So now we say, is N 1? The answer is no. So now I'm going to save continue, bang. Now
that done goes in here. And this moves to here, the next place I'm going to write.





Save N 3. OK? Assign to N the decrement of N. That means I've pushed this button. This
becomes 2.




Assign to continue aft. So I've pushed that button. Aft goes in here.





OK, now go to loop, bang, so up to here. Is N 1? No.




So I have to save continue. What's continue? Continue is aft. Push this button. So this

moves to here.




I have to save N. N is over here. I got to 2. Push that button. So a 2 gets written there. And

then this thing moves down here.




OK, save N. Assign N to the decrement of N. This becomes a 1. Assign continue to aft. A -F-T
gets written there again.





Go to loop. Is N equal to 1? Oh, yes, the answer is 1.




OK, go to base step. Assign to VAL fetch of N. Bang, 1 gets put in there.
Go to fetch continue. So we look in continue. Basically, I'm pushing a button over here that
goes to the controller. The continue becomes aft, and all of a sudden, the program's running

here.




I now have to restore the outer version of factorial. So we go here. We say, restore N. So

restore N means take the contents that's here. Push this button, and it goes into here, 2,
and the pointer moves up.





Restore continue, pretty easy. Go push this button. And then aft gets written in here again.
That means this thing moves up. I've gotten rid of something else on my stack.





Right, then I go to here, which says, assign to VAL the product of N an VAL. So I push this
button over here, bang. 2 times 1 gives me a 2, get written there.




Go to fetch continue. Continue is aft. I go to aft. Aft says restore N. Do your restore N,

means I take the value over here, which is 3, push this up to here, and move it into here,

N. Now it's pushing that button.




The next thing I do is restore continue. Continue is now going to becom e done. So this
moves up here when I push this button. Done may or may be there anymore, I'm not

interested, but it certainly is here.




Next thing I do is assign to VAL the product of the fetch of N and the fetch of VAL. That's

pushing this button over here, bang. 2 times 3 is 6. So I get a 6 over here.




And go to fetch continue, whoops, I go to done, and I'm done. And my answer is 6, as you

can see in the VAL register. And in fact, the stack is in the state it originally was in.




Now there's a bit of discipline in using these things like stacks that we have to be careful of.

And we'll see that in the next segment. But first I want to ask if there are any questions for
this. Are there any questions? Yes, Ron.
AUDIENCE: What happens when you roll off the end of the stack with--




PROFESSOR: What do you mean, roll off of?





AUDIENCE: Well, the largest number-- a larger starting point of N requires more memory,
correct?





PROFESSOR: Oh, yes. Well, I need to have a long enough stack. You say, what if I violate
my illusion?





AUDIENCE: Yes.




PROFESSOR: Well, then the magic doesn't work. The truth of the matter is that every
machine is finite. And for a procedure like this, there's a limit to the number of sub-

factorials I could have.




Remember when we were doing the y-operator a while ago, we pointed out that there was a

sequence of exponentiation procedures, each of which was a little better than the previous
one. Well, we're now seeing how we implement that mathematical idea. The limiting process

is only so good as as far as you take the limit.




If you think about it, what am I using here? I'm using about two pieces of memory for every

recursion of this process. If we try to compute factorial of 10,000, that's not a lot of
memory. On the other hand, it's an awful big number.





So the question is, is that a valuable thing in this case. But it really turns out not to be a
terrible limit, because memory is el cheapo, and people are pretty expensive. OK, thank

you, let's take a break.




[MUSIC PLAYING - "JESU, JOY OF MAN'S DESIRING" BY JOHANN SEBASTIAN BACH]
PROFESSOR: Well, let's see. What I've shown you now is how to do a simple iterative

process and a simple recursive process. I just want to summarize the design of simple
machines for specific applications by showing you a little bit more complicated design, that

of a thing that does doubly recursive Fibonacci, because it will indicate to us, and we'll
understand, a bit about the conventions required for making stacks operate correctly.





So let's see. I'm just going to write down, first of all, the program I'm going to translate. I
need a Fibonacci procedure, it's very simple, which says, if N is less than 2, the result is N,

otherwise it's the sum of Fib of N minus 1 and Fib of N minus 2. That's the plan I have here.




And we're just going to write down the controller for such a machine. We're going to

assume that there are registers, N, which holds the number we're taking Fibonacci of, VAL,
which is where the answer is going to get put, and continue, which is the thing that's linked

to the controller, like before. But I'm not going to draw another physical datapath, because
it's pretty much the same as the last one you've seen.





And of course, one of the most amazing things about computation is that after a while, you
build up a little more features and a few more features, and all of the sudden, you've got

everything you need. So it's remarkable that it just gets there so fast. I don't need much
more to make a universal computer.





But in any case, let's look at the controller for the Fibonacci thing. First thing I want to do is
start the thing up by assign to continue a place called done, called Fib-done here. So that

means that somewhere over here, I'm going to have a label, Fib-done, which is the place
where I go when I want the machine to stop. That's what that is.





And I'm going to make up a loop. It's a place I'm going to go to in order to start up
computing a Fib. Whatever is in N at this point, Fibonacci will be computed of, and we will

return to the place specified by continue.




So what you're going to see here at this place, what I want here is the contract that says,

I'm going to write this with a comment syntax, the contract is N contains arg, the
argument. Continue is the recipient. And that's where it is. At this point, if I ever go to this

place, I'm expecting this to be true, the argument for computing the Fibonacci.
Now the next thing I want to do is to branch. And if N is less than 2-- by the way, I'm using

what looks like Lisp syntax. This is not Lisp. This does not run . What I'm writing here does
not run as a simple Lisp program. This is a representation of another language.





The reason I'm using the syntax of parentheses and so on is because I tend to use a Lisp
system to write an interpreter for this which allows meto simulate the machine I'm trying

to build. I don't want to confuse this to think that this is Lisp code. It's just I'm using a lot of
the pieces of Lisp. I'm embedding a language in Lisp, using Lisp as pieces to make my

process of making my simulator easy. So I'm inheriting from Lisp all of its properties.




Fetch of N 2, I want to go to a place called immediate answer. It's the base step. Now,

that's somewhere over here, just above done. And we'll see it later.




Now, in the general case, which is the part I'm going to write down now, let's just do it.

Well, first of all, I'm going to have to call Fibonacci twice. In each case -- well, in one case at
least, I'm going to have to know what to do to come back and do the next one. I have to

remember, have I done the first Fib, or have I done the second one? Do I have to come
back to the place where I do the second Fib, or do I have to come back to the place where I

do the add?




In the first case, over the first Fibonacci, I'm going to need the value of N for c omputing for

the second one. So I have to store some of these things up. So first I'm going to save
continue. That's who needs the answer. And the reason I'm doing that is because I'm about

to assign continue to the place which is the place I want to go to after.




Let's call it Fib-N-minus-1, big long name, classic Lisp name. Because I'm going to compute

the first Fib of N minus 1, and then after that, I want to come back and do something else.
That's the place I want to go to after I've done the first Fibonacci calculation. And I want to

do a save of N, because I'm going to need it later, after that.




Now I'm going to, at this point, get ready to do the Fibonacci of N minus 1. So assign to N

the difference of the fetch of N and 1. Now I'm ready to go back to doing the Fib loop.




Have I satisfied my contract? And the answer is yes. N contains N minus 1, which is what I
need. Continue contains a place I want to go to when I'm done with calculating N minus 1.
So I've satisfied the contract. And therefore, I can write down here a label, after-Fib-N-

minus-1.




Now what am I going to do here? Here's a place where I now have to get ready to do Fib of

N minus 2. But in order to do a Fib of N minus 2, look, I don't know. I've clobbered my N
over here. And presumably my N is counted down all the way to 1 or 0 or something at this

point. So I don't know what the value of N in the N register is.




I want the value of N that was on the stack that I saved over here so that could restore it

over here. I saved up the value of N, which is this value of N at this point, so that I could
restore it after computing Fib of N minus 1, so that I could count that down to N minus 2

and then compute Fib of N minus 2. So let's restore that. Restore of N.




Now I'm about to do something which is superstitious, and we will remove it shortly. I am

about to finish the sequence of doing the subroutine call, if you will. I'm going to say, well, I
also saved up the continuation, since I'm going to restore it now.






But actually, I don't have to, because I'm not going to need it. We'll fix that in a second. So

we'll do a restore of continue, which is what I would in general need to do. And we're just
going to see what you would call in the compiler world a peephole optimization, which says,

whoops, you didn't have to do that.




OK, so the next thing I see here is that I have to get ready now to do Fibonacci of N minus

2. But I don't have to save N anymore. The reason why I don't have to save N anymore is
because I don't need N after I've done Fib of N minus 2, because the next thing I do is add.

So I'm just going to set up my N that way. Assign N minus difference of fetch N and 2.




Now I have to finish the setup for calling Fibonacci of N minus 2. Well, I have to save up

continue and assign continue, continue, to the place which is after Fib N 2, that place over
here somewhere. However, I've got to be very careful. The old value, thevalue of Fib of N

minus 1, I'm going to need later.




The value of Fibonacci of N minus 1, I'm going to need. And I can't clobber it, because I'm
going to have to add it to the value of Fib of N minus 2. That's in the value register, so I'm
going to save it. So I have to save this right now, save up VAL. And now I can go off to my

subroutine, go to Fib loop.




Now before I go any further and finish this program, I just want to look at this segment so

far and see, oh yes, there's a sequence of instructions here, if you will, that I can do
something about. Here I have a restore of continue, a save of continue, and then an assign

of continue, with no other references to continue in between. The restore followed by the
save leaves the stack unchanged.





The only difference is that I set the continue register to a value, which is the value that was
on the stack. Since I now clobber that value, as in it was never referenced, these

instructions are unnecessary. So we will remove these.




But I couldn't have seen that unless I had written them down. Was that really true? Well, I

don't know.




OK, so we've now gone off to compute Fibonacci of N minus 2. So after that, what are we
going to do? Well, I suppose the first thing we have to do-- we've got two things. We've got

a thing in the value register which is now valuable. We also have a thing on the stack that

can be restored into the value register. And what I have to be careful with now is I want to
shuffle this right so I can do the multiply.




Now there are various conventions I might use, but I'm going to be very picky and say, I'm

only going to restore into a register I've saved from. If that's the case, I have to do a

shuffle here. It's the same problem with how many hands I have. So I'm going to assign to
N, because I'm not going to need N anymore, N is useless, the current value of VAL, which

was the value of Fib of N minus 2.




And I'm going to restore the value register now. This restore matches this save. And if

you're very careful and examine very carefully what goes on, restores and saves are always
matched. Now there's an outstanding save, of course, that we have to get rid of soon.




And so I restored the value register. Now I restore the continue one, which matches this

one, dot, dot, dot, dot, dot, dot, dot, down to here, restoring that continuation. That
continuation is a continuation of Fib of N, which is the problem I was trying to solve, a

major problem I'm trying to solve. So that's the guy I have to go back to who wants Fib of
N. I saved them all the way up here when I realized N was not less than 2. And so I had to

do a complicated operation.




Now I've got everything I need to do it. So I'm going to restore that, assign to VAL the sum

of fetch VAL and fetch of N, and go to continue. So now I've re turned from computing
Fibonacci of N, the general case. Now what's left is we have to fix up a few details, like

there's the base case of this induction, immediate answer, which is nothing more than
assign to VAL fetch of N, because N was less than 2, and therefore, the answer is N in our

original program, and return continue-- bobble, bobble almost-- and finally Fib done.




So that's a fairly complicated program. And the reason I wanted you see to that is because I

want you to see the particular flavors of stack discipline that I was obeying. It was first of
all, I don't want to take anything that I'm not going to need later. I was being very careful.

And it's very important. And there are all sorts of other disciplines people make with frames
and things like that of some sort, where you save all sorts of junk you're not going to need

later and restore it because, in some sense, it's easier to do that. That's going to lead to
various disasters, which we'll see a little later.





It's crucial to say exactly what you're going to need later. It's an important idea. And the
responsibility of that is whoever saves something is the guy who restores it, because he

needs it. And in such discipline, you can see what things are unnecessary, operations that
are unimportant.





Now, one other thing I want to tell you about that's very simple is that, of course, the
picture you see is not the whole picture. Supposing I had systems that had things like other

operations, CAR, CDR, cons, building a vector and referencing the nth element of it, or
things like that. Well, at this level of detail, whatever it is, we can conceptualize those as

primitive operations in the datapath. In other words, we could say that some machine that,

for example, has the append machine, which has to do cons of the CAR of x with the append
of the CDR of x and y, well, gee, that's exactly the same as the factorial structure. Well, it's

got about the same structure.




And what do we have? We have some sort of things in it which may be registers, x and y,
and then x has to somehow move to y sometimes, x has to get the value of y. And then we

may have to be able to do something which is a cons. I don't remember if I need to like this

is in this system, but cons is sort of like subtract or add or something. It combines two
things, producing a thing which is the cons, which we may then think goes into there. And

then maybe a thing called the CAR, which will produce-- I can get the CAR or something.
And maybe I can get the CDR of something, and so on.
But we shouldn't be too afraid of saying things this way, because the worst that could
happen is if we open up cons, what we're going to find is some machine. And cons may in

fact overlap with CAR and CDR, and it always does, in the same way that plus and minus
overlap, and really the same business. Cons, CAR, and CDR are going to overlap, and we're

going to find a little controller, a little datapath, which may have some registers in it, some

stuff like that. And maybe inside it, there may also be an infinite part, a part that's semi-
infinite or something, which is a lot of very uniform stuff, which we'll call memory.




And I wouldn't be so horrified if that were the way it works. In fact, it does, and we'll talk

about that later. So are there any questions?




Gee, what an unquestioning audience. Suppose I tell you a horrible pile of lies. OK. Well,

thank you. Let's take our break.




[MUSIC PLAYING - "JESU, JOY OF MAN'S DESIRING" BY JOHANN SEBASTIAN BACH]
MIT OpenCourseWare
http://ocw.mit.edu


6.001 Structure and Interpretation of Computer Programs, Spring 2005





Please use the following citation format:

       Eric Grimson, Peter Szolovits, and Trevor Darrell, 6.001 Structure and

       Interpretation of Computer Programs, Spring 2005. (Massachusetts Institute
       of Technology: MIT OpenCourseWare).    http://ocw.mit.edu (accessed MM DD,
       YYYY). License: Creative Commons Attribution-Noncommercial-Share Alike.


Note: Please use the actual date you accessed thismaterial in your citation.



For more information about citing these materials or our Terms of Use, visit:
http://ocw.mit.edu/terms