 In this video, we're going to explain how an assembly subroutine executes, and we're going to make the following assumption. We're going to suppose that we are executing a regular program, an assembly program, that is calculating certain things, different instructions, and at some point it invokes the instruction call subroutine. Now the effect of this instruction is actually that the sequence of the instructions is broken, and this program continues executing at the place where the labeled subroutine has been written, and we have additional calculations here. And the subroutine finishes its execution whenever it executes the instruction in red with no operands. And the effect of this instruction is the intuitive one, which is returning back to the place it was invoked, but to the next, what we call the next instruction following the call. So this would be like a jump that takes the sequence of instructions back there. Now the address of this instruction is what we call the return address. So as we can see, and this program, by the way, continues executing certain other things. So what we can see here is that the cycle of the invocation of a subroutine is started with the instruction call, and the subroutine keeps executing until the instruction red brings the sequence back to the point where it was invoked. So the two instructions that are required to implement these mechanisms are called with a label, and this label has to be the first instruction of the subroutine, and the second instruction we have seen that closes this loop is red, which returns to the address of instruction following. So this pretty much explains the mechanism used for subroutine invoking. Now there are four observations that need to be taken into account in order to explain exactly how this mechanism works, and these observations are the following. Number one is that the subroutines may be invoked from different locations. This is very important. In other words, if I have a subroutine here, it is not always the case that this subroutine is invoked by this program. Let's see an example. Suppose that within this subroutine in the middle of it, I call another subroutine. Let's call it this one S2. So this other subroutine could be written here, S2, a label, again corresponding with the first instruction, a bunch of instructions, and then the instruction returns. So this would be a second subroutine. So we have three elements here. One of these first subroutines we talked about, the main program, and the second one. If we call this subroutine, we know the effect of this instruction. This instruction will break the sequence and start executing the first instruction of this subroutine. But we also know the effect of this red. It returns to the place where it was invoked. In this case, it would be the instruction following this call. Now what happens if this initial program that we draw here made a call to S2? And the effect is very similar. So this instruction would break the sequence and take the execution of the program to the same point here. But now, and check this out, the return instruction here would return instead of to this position over here like it did before, it returns back to the instruction following this call over here. And then this program would continue. So these are the three units that we are considering or three elements considering in this example. Let's review again the sequence of steps we are considering. First, this program executes and in call number one, it invokes this subroutine. This subroutine starts executing and at some point makes a call to subroutine number two. Let's call this arrow number two. Subroutine number two keeps executing all the way to the end of this return instruction. This return instruction will return back to this place. So let's put this arrow number three, which means the program will keep executing these instructions until it hits this instruction red, which will take us back to the initial call. That would be arrow number four. This program then keeps executing until it reaches this call, which brings it back to this location. That would be arrow number five. And as we said before, after we are done, this return instruction no longer returns back here, but it returns back to this instruction. That would be arrow number six. So this statement here, what it says is that a subroutine may be invoked from two different locations. And this is illustrated by this example. We see that subroutine S2 can be invoked either from here or from here, which brings a very important issue. Whenever the instruction return or red is reached, the return address changes depending on the sequence of steps that were taken before. And here is clearly the example illustrated in this feature. Arrow number three takes us back to this location, whereas arrow number six takes us back to this location. This is also an example of another factor that we have to observe, which is that subroutines sometimes are invoked in a nested way. So we have to understand this mechanism from the point of view of nested invocation of subroutines. And what we mean by that is basically that S2 over here, at some point, was invoked inside subroutine, which itself was invoked inside program. So this invocation is nested inside this subroutine, which itself is nested inside this program over here. Number three observation, this nesting can be arbitrarily deep. So this example is with the depth of two, but I could have this subroutine calling another one and so on and so forth. And finally, an observation that is very important, which is not illustrated here, is that we could be facing a situation for self-invocation. Or they also know as recursive functions. So these are subroutines that invoke another subroutine that happens to be itself. And that's what we mean by self-invocation. So these are four properties that are very common in pretty much all the programming languages that we find out there. Now the issue that needs to be resolved is the following. How come red, which is an instruction that has no parameters, knows where to return? How do we know where to return? So this is the very important question that by understanding this mechanism, we're going to be able how these four conditions can be addressed simultaneously. So let's consider our options. We need to remember where to return. And we know that that location changes depending on the sequence of calls that happen so far. So one possibility would be, well, I need to return to the address of the instruction following the call. This means I need to store the return address of a subroutine. Whenever I move over here, I need to store the return address so that I can get back here. The same thing here, whenever I call the subroutine, I need to store the return address such that return then returns to the proper location. So this question could be rephrased as, where should I store the return address so that it can be used in this fashion and also support these four observations over here? The first option would be to store it in a register. That would be one possibility. This means that this is a fixed location, which would be good because the instruction red would go to that location, get return address and proceed with the execution sequence as expected. However, if I put it there, registers are somewhat limited because if I have nesting of arbitrary depth, it means I would need a very large number or an arbitrary number of registers to remember all the return addresses that still need to be used. Let's put this example over here. The program called the subroutine, the subroutine called this other subroutine. At this point, I have two return addresses pending to be used, the return address to go back here and the return address to go back here. In other words, if I have arbitrary nesting of arbitrary depth, I need to store all these return addresses in the meantime. Therefore, for nesting, this solution is not working properly. So again, where should I store the return address? A second option could be memory locations. Again, I could choose a fixed location. Let's say something like each subroutine would remember the return address when it's being invoked. But it has a little bit of a limitation here because if it is a fixed location, it's probably one per subroutine. Each subroutine would remember where to return. After all, the instruction return is specific for each subroutine. But we have another problem here, which is the issue of self-invocation. If I use memory location to store the return address and it's a fixed location per subroutine, if a subroutine calls itself, I would need an extra memory location, which should be placed somewhere to be distinguished from the previous. So as we can see, even though memory location solves the issue of limited resources, as in the case of the register, it still doesn't support properly the fact that some functions would invoke themselves. And the third and final solution, which is the one that is used in most microprocessor, is to use the stack. Now the stack, it's a fixed location, but I can create as many as needed, even in the case when subroutines invoke themselves. And this is the mechanism that is used, as I said, by most of the microprocessor. And what it means is that the instruction call not only breaks the sequence and goes to the next instruction in the subroutine, but at the same time call pushes the return address to stack. And most importantly, the instruction red assumes, and in fact, accesses, the return address from the stack. So the way it works is, if I have my program here in securing these instructions and I have call s as a subroutine, this call x, as a matter of fact, what it does is it pushes the return address on top of the stack. And this is where the stack pointer is pointing. And after doing that, it leaves that return address there in the stack such that the execution goes to the subroutine, let's say s. And whenever red is executing, red is going to assume that the top of the stack contains the return address. This value is actually going to become the program counter of the microprocessor and therefore the execution returns to the right location. And this is, as I said, the most common mechanism that microprocessors use to implement subroutine calls.