COURS 6.TXT
****************************************************************** * * * 68000 ASSEMBLER COURSE ON ATARI ST * * * * by The Ferocious Rabbit (from 44E) * * * * Lesson number 6 * * * ****************************************************************** THE STACK We have already used the concept of a 'tube' when it comes to memory. We can store different things in it, and if we remember the address, we can come back later to that place to retrieve what we left there. Try with this small program: MOVE.L #$12345678,D0 MOVE.L D0,$493E0 MOVE.L #0,D0 MOVE.L $493E0,D0 Assemble and then use MONST to step through. D0 is initially filled with $12345678, then the contents of D0 are transferred to address $493E0. Note that there is no # in front of $493E0, to indicate that it is indeed an address. Once this line is executed, activate window 3 ([Alternate+3]) and set the start of it to address $493E0 ([Alternate+A] then type 493E0) You can clearly see 12345678 at this location in the 'tube'. If I have chosen this address it's because it's located 300 Kilobytes from the beginning of memory. It is therefore accessible even on a 520, and it is far enough away not to be in GENST or MONST. Indeed there is only one memory 'tube'! So we are writing into memory while some of it is occupied by GENST and MONST! Writing within the areas oc- cupied by these programs is possible, which will very likely cause some crashes! Let’s continue to step through; we set D0 to 0 then the contents of address $493E0 (without #) are moved back into D0. The stack is a part of this tube, but that we will manage in a slightly different way. Indeed, instead of placing the data in the tube and noting their addresses, this time we will stack them and to retrieve them, unstack them. The advantage is the time saved (no need to wonder at which address we stored the data) and a space saving (if it's for temporarily storing data, no need to keep a portion of 'tube' just for that). On the other hand, the disadvantage is that management must be ri- gorous. Let's imagine that I stack one first number then 10 others on top of it. Next, I unstack, but, mistake on my part, I only unstack 9 numbers! When I unstack once more, thinking I will find the first stacked number, I will actually re- cover the first of the series of 10. We conclude 2 things from this: first, the stack is a sim- ple way to save data, but then it is a potential source of annoyance, such that some programmers he- sitate to use it. It's usually due to a lack of ri- gor, which I hope will not happen to you. Another note: the last element placed on the stack will al- ways be the first to come out. This is the same principle as that of a stack of plates: Look at your home, there's certainly a huge stack of plates, but simply because storing after washing is done by stacking and setting the table is done by unstacking, you are actually always eating in the same plates... (hence the interest of doing the dishes well!) This stack structure is called LIFO structure, that is Last In First Out. This structure is different from another structure frequently encountered in computing, that of the queue, also called struc- ture FIFO (First In First Out), the queue being similar to a waiting line in front of a counter: the first in line will be the first to leave. But concretely, what is the stack for? We will see with an example. Type the following program: MOVE.L #$12345678,D0 MOVE.L #$BD88,D1 MOVE.L #$BD88,A0 BSR ADD MOVE.L #0,D0 MOVE.L D2,D0 ADD MOVE.L #$11112222,D2 ADD.L D1,D2 RTS First note: this program differs from the previous ones in that we are using a label, a label called ADD. This word, 'ADD', must be found all the way to the left, against the edge of the editor window. It is not something to place IN the tube but rather a mark BESIDE the tube. Another note, assembler listings, unlike lis- tings in other languages, are quite free in terms of presen- tation. It is quite possible to skip lines, which is done here to separate the 2 parts. Assembler sources are often very long, and even if it wastes some lines, spacing the modules allows to find one's way easier. Assemble and then debug. Step through with Control+Z. The first 3 lines are familiar to us but not the fourth. This line reads BRANCH SUB ROUTINE ADD, meaning bran- ching to a subroutine called ADD. To specify which su- broutine one wants to go to, its label is specified. Here in this case it's ADD but the name doesn't matter much. It is all quite possible to put fairly long names and I can only advise you to avoid names like X Y, Z or AX1 etc... which are still less explicit than IMAGE_START, NEW_PALETTE or END_GAME. Now be very attentive: upon reading this instruction many things will happen. The command therefore asks the 68000 to continue reading its instructions in a sub- program whose start is located in the tube, opposite the la- bel ADD. However, this is indeed a subroutine. This means that once completed, the 68000 will go back up to execute the following line of BSR ADD, namely MOVE.L #0,D0. Ques- tion: how will the 68000 know where to go back to? Indeed the nature of a subroutine is to be able to be called multiple times and from several different places and to be able to return each time to the very place that called it. Well the 68000 will use the stack to note this place of return. This stack certainly has an address, where is it noted? In A7. Yes, this somewhat special register corresponds to the stack. But A7' then? Well, it's also a stack, but reserved for the Supervisor mode. So if we were running 2 programs at the same time, one in user mode and the other in supervisor, each would have their own stack. Before executing the line BSR ADD, observe carefully the address registers and the data registers. We have seen that the registers, whether they are for data or address, can contain numbers coded over 32 bits. We have also seen that there are 2 types of numbers for the machine: those inside the 'tube' and those outside, AGAINST this tube, and indicating a sort of distance from the start of that tube. This second type of number is called an address. However, it is quite possible to store a number representing an address in a data register (D0-D7). Let's imagine now that we needed to store a player's score in the game we are programming. This score will for example be placed in memory (in the 'tube') at address $80792. But what will happen if we transfer this address to use with A1 for example? well A1 will take the value $80792. That's nice, but that's not what interests us. What we want to modify, check etc... it's what is IN the tube at this address. Well our debugger anticipates a bit this request. Indeed, considering that the numbers stored in D0-D7 or A0-A6 can represent address values, it indicates next to the regis- ters, what is in the tube at the address indicated in the register. For the data registers, MONST displays to their right the value of 8 bytes found in the tube at the address indicated in the register. For address registers, it is 10 bytes which are indicated. You have certainly noticed that in front of D0 register (which should contain $12345678 if you have done the program advance correctly), MONST has only displayed stars. This is normal because the number $12345678 corresponds to an em- placement that would only be accessible with 305 megabytes of memory!!! MONST therefore indicates that it cannot reach this memory area by displaying stars. Now look at D1 and A0. The numbers on their right show the same thing, which is normal since both registers D1 and A0 are filled with the same number. We say they point to address $BD88. Let's check the memory just to verify the display. Activate window 3 with Alternate+3. This one dis- plays the contents of memory, but we are far from $BD88! Let's ask that this address be the one at the top of window 3, with Alternate+A. We type this address (BD88). Window 3 is re-displayed with $BD88 at the top. On the right column we see the contents of memory, which we had already a peak with the display to the right of D1 and A0. Is it clear? Reactivate window 1 (alternate+1). Normally the small ar- row should always be in front of BSR ADD. Note the number in register A7 (thus the address of the Stack) and watch carefully the numbers on the right of this register, while making Control+Z. The numbers have changed! First the A7 register no longer contains the same number. The one currently there is indeed smaller than the previous one. Note that this difference is 4. The stack address has therefore been decremented by 4. Plus nu- mbers have been placed on the stack (they are seen to the right of the A7 register). Now, look at the number that is to the left of the instruction MOVE.L #0,D0 in our program, that is the address where the 68000 should return once the subroutine is complete: it is indeed this number that has been placed on the stack. So there is stacking of the return address, which also explains the change in the stack address by 4. Indeed an address is coded on 4 bytes! Note: since we are talking about a stack, it is more common to say that the data are placed on the stack and less often in the stack. Continue our program with Control+Z. We are now in the subroutine. Stop just before RTS. This ins- truction will make us "go back up". It reads RETURN FROM SUB ROUTINE. Observe A7 (its value but also the contents of the 'tube' at this address) and take a step (Control+Z). The return address has been unstacked, A7 has resumed its old address and we now point to MOVE.L #0,D0. Quit this program with Control+C, erase it and type this one. MOVE.L #$12345678,D0 MOVE.L #$AAAAAAAA,D1 BSR ADD MOVE.W D2,D3 ADD MOVE.W #$EEEE,D1 MOVE.W #$1111,D2 ADD.W D1,D2 RTS Assemble and then debug. Step through: D0 takes the value $12345678 D1 the value AAAAAAAA, then we depart towards the subrou- tine ADD. Unfortunately this one uses D1 and on return we note that this one no longer contains AAAAAAAA. Indeed jumping to a subroutine only saves the return address, and in assembly local variables and other languages evol- ved features do not exist! It is therefore up to us to save the regis- ters, and this is what we will do now. Note: the A7 register containing the address of the stack pointer (this address varying of course with the stacking and unstacking), we can consider this address as a finger permanently pointing towards the top of the stack. For this reason the A7 register is also called the stack pointer. As always we will use the English vocabulary, and we will say Stack Pointer, abbreviated as SP. For this reason and because it is customary, we will henceforth replace A7 with SP (which is not read "ess-pee" but STACK POINTER!!!). Suppose we wanted to save D0 at the entry to the su- broutine: We must not forget to retrieve it at the end! Let’s move the contents of D0 to the stack. Let's try MOVE.L D0,SP and think about it: This will put the contents of D0 into A7, unfortunately that's not what we want to do. Indeed we want to put the contents of D0 INSIDE the tube, at the point indicated by A7 (so SP). This will be done with MOVE.L D0,(SP), the parentheses indicating that the source of the operation is the inside of the tube. Erase the current program and type the following one. MOVE.L #$12345678,D0 MOVE.L D0,(A0) MOVE.W D0,(A1) Assemble and then debug as usual. D0 takes the value $12345678, then D0 is transferred in its entirety (due to the .L indicating that the operation is on a long word) to the address noted in A0, then the lower half of D0 is transferred into the tube at the address noted in A1. To verify this, you can activate window 3 and ask to place the address noted in A0 at the top of this window, and you will see that indeed the value of D0 is in the 'tube'. We are therefore going to use this type of transfer to save D0 But let's think a little. MOVE.L D0,(SP) will indeed place the contents of the long word D0 in the tube, but if we want to pla- ce another value on the stack, it is going to overwrite our first value because with MOVE.L D0,(SP) the address indicated by SP (thus A7) will not be modified, which should be the case. We are therefore going to perform the transfer differently (in fact we are going to further improve our vocabulary, since we are going to talk about addressing type or mode now). We are going to do MOVE.L D0,-(SP) This is the addressing mode with pre-decrement. Behind this vo- cabulary hides a whole series of events. In a single instruction, we decrease the address of the stack pointer by 4 (since in our example we wanted to transfer a long word therefore 4 bytes), and we place the long word D0 in memory at this address. To recover D0, that is to say unstack, we will have to: MOVE.L (SP)+,D0 As we decremented the stack pointer to then de- posit D0 at this address, we then recover D0 without for- getting to modify the stack pointer in the other direction, to make it find its old position again. Note that in this case, and if we limit ourselves to think very summarily, it would have been possible to save D0 with MOVE.L D0,(SP) and to reco- ver it with MOVE.L (SP),D0. It's without counting that the stack is a common reservoir for many things. It is therefore bet- ter to play each time the game of a correct stacking and a re- flection but also an unstacking 'sticking' perfectly with the pre- vious stacking. Let's verify all this with the following example: MOVE.L #$12345678,D0 value in D0 MOVE.W #$AAAA,D1 value in D1 MOVE.L D0,-(SP) saves D0.L on the stack MOVE.W D1,-(SP) same with D1 but in word MOVE.L #0,D0 sets D0 to 0 MOVE.W #0,D1 and D1 too MOVE.W (SP)+,D1 retrieves D1 (word) MOVE.L (SP)+,D0 then D0 Assemble and go through this program step by step under MONST. Note several things: first of all comments have been added to the source. It is enough that they are separated from the ope- rands for the assembler to know that it's about comments. If you want to type a line of comments (that is to say that on it there will be nothing else than this comment), you must precede it with an asterisk or a semicolon. Second thing, we had stacked D0 then D1, then we un- stacked D1 then D0. Indeed, we must be very careful with the order and the sizes of what we stack, in order to unstack the same sizes, in the reverse order of stacking. Here is one last example. MOVE.L #$12345678,D0 BSR ADD jump to subroutine MOVE.L D0,D1 transfer ADD MOVE.L D0,-(SP) saves d0.l on the stack MOVE.W #8,D0 MOVE.W #4,D1 ADD.W D0,D1 MOVE.L (SP)+,D0 RTS Assemble and then follow the execution under MONST, paying close attention to the process. You will see that the BSR saves the return address on the stack, then D0 is placed on top of it to be later retrieved. Next, the return address is retrieved and the program returns. Now, let's provoke a small but fatal error for our program. Instead of retrieving D0 with a MOVE.L (SP)+,D0, let's make a typo and type MOVE.W (SP)+,D0 instead. Assemble and follow step by step. At the moment of saving D0, 4 bytes are indeed placed on the stack, altering it accordingly. Unfortunately, the retrieval will only re-modify the stack by 2 bytes. When the RTS instruction tries to retrieve the return address, the stack pointer will be off by 2 bytes from where the return address actually is, and the return will occur to a wrong address. In conclusion: caution and precision are needed! We have just seen that the stack is used by the 68000 for certain instructions and is very convenient for saving. It is also possible to use it for transmitting data, which we will see to conclude this chapter. Problem: Our main program uses registers A0 to A6 and D0 to D6. It will call a subroutine designed to add 2 numbers and return the result in D7. Therefore, we will need to use 2 registers, for example D0 and D1, to work in our routine, and therefore save them at the entry of it. Here is the beginning of the program. MOVE.L #$11111111,D0 MOVE.L #$22222222,D1 MOVE.L #$33333333,D2 MOVE.L #$44444444,D3 MOVE.L #$55555555,D4 MOVE.L #$66666666,D5 MOVE.L #$77777777,D6 The first 7 registers are filled with dummy values, just to allow us to check for any changes. Now we need to place the 2 numbers we want to add somewhere they can be retrieved by the addition routine. Let's put these 2 numbers on the stack. MOVE.L #$12345678,-(SP) MOVE.L #$00023456,-(SP) BSR AJOUTE and off we go! Let's now write our subroutine, following the order of work of the 68000. What will we need in this routine? We'll need D0 and D1, which will receive the stacked numbers and will be used for calculation. We will also need an address register. Indeed, when we unstack, we will modify the stack pointer, but we just did a BSR and the 68000 has thus stacked the return address on the stack, and modifying it will compromise the return! We will therefore copy the stack address into A0, and use this copy. Note: I've decided to use D0, D1, and A0, but any other register would have been just as suitable. Let's start by saving our 3 registers. This could be done by: MOVE.L D0,-(SP) MOVE.L D1,-(SP) MOVE.L A0,-(SP) Note: remember that this reads move long! But the 68000 has a very useful instruction in such a case, which allows transferring several registers at once. We will therefore do: MOVEM.L D0-D1/A0,-(SP) Which reads: move multiple registers. If we needed to transfer from D0 to D5 we would have done: MOVEM.L D0-D5,-(SP) and, to transfer all the registers at once: MOVEM.L D0-D7/a0-A6,-(SP) Got it? Now let's save the stack address in A0. As it's the address we need to save and not the content, it's done by: MOVE.L A7,A0 transfer from register A7 to A0 Now we will retrieve the 2 numbers we had stacked before the BSR instruction. Imagine what happened. (By the way, I HIGHLY recommend you to use paper and pencil for this. Don't hesitate to write on these courses. They are yours and I won't ask for them back! Making a small drawing or placing objects on your desk to help you understand is an excellent idea. Often, memory manipulations tend to become abstract, and a little drawing clears things up!) We moved the STACK POINTER by 4 bytes, then we deposited $12345678 in it. But in which direction did we move this SP? Towards the start of memory, towards address 0 of our tube since we did -(SP). The stack pointer therefore moves up along the tube. We then did the same thing again to deposit $23456. Then BSR, so the same thing but automatically performed by the 68000 to deposit the return address (4 bytes). Is that all? No, because once in the subroutine, we deposited the registers D0, D1, and A0 on the stack. The transfer being performed in the long word format (MOVEM.L), we transferred 3 times 4 bytes, which makes 12 bytes. Our copy of A7, which is in A0, therefore does not point to our 2 numbers but much further away. The number we placed second on the stack is therefore 16 bytes towards the start of the tube (do the calculation: 1BSR, + 12 bytes of backup makes 16 bytes) and the number placed first on the stack follows its buddy and is therefore 20 bytes from here, always by virtue of the stack principle: the last entered, is the first out. We can therefore say that $23456 is at A0 offset by 16 and that $12345678 is at A0 offset by 20. To retrieve these 2 numbers several actions are possible: 1) add 16 to the address of A0 then retrieve. An address addition is done by ADDA (add address). So we do ADDA.L #16,A0 A0 now points to $23456, so let's retrieve this number and take advantage of the addressing mode to advance the address indicated in A0 and thus be immediately ready to retrieve the other number. MOVE.L (A0)+,D0 The address having been increased we can therefore retrieve the continuation: MOVE.L (A0)+,D1 2) Another method, using a different addressing mode: The previous method has a disadvantage: after the ADDA, A0 is modified and if we wanted to keep this address, we would have had to save it. Or we could have added the offset to A0, retrieve the data and then remove the offset from A0 so that it regains its initial state. Another method, therefore, indicate in the addressing the offset to apply. This is done by: MOVE.L 16(A0),D0 MOVE.L 20(A0),D1 This allows pointing to the 16th byte from the address given by A0 and then pointing to the 20th relative to A0. In both cases, A0 is not modified. Here is the complete listing of this example. MOVE.L #$11111111,D0 initialization of D0 MOVE.L #$22222222,D1 same MOVE.L #$33333333,D2 same MOVE.L #$44444444,D3 same MOVE.L #$55555555,D4 same MOVE.L #$66666666,D5 same MOVE.L #$77777777,D6 same MOVE.L #$12345678,-(SP) passing number 1 into the stack MOVE.L #$00023456,-(SP) passing number 2 into the stack BSR ADD and off we go! MOVE.L D7,D0 transferring the result to see.. * our subroutine ADD MOVEM.L D0-D1/A0,-(SP) saving MOVE.L A7,A0 copy of SP in A0 MOVE.L 16(A0),D0 retrieves 23456 and puts it in D0 MOVE.L 20(A0),D1 retrieves 12345678 in D1 ADD.L D0,D1 addition MOVE.L D1,D7 transferring the result MOVEM.L (SP)+,D0-D1/A0 recovery RTS and return * Note: this program not having a 'normal' end, when you* get to the return of the subroutine that is after the line" MOVE.L D7,D0 ", exit it with Control+C, Assemble and follow ALL the proceedings well. Of course, it would have been possible to do this all differently. For example, we could have avoided working with A0. Indeed 16(A0) and 20(A0) not modifying A0, it would have been simpler to do 16(A7) and 20(A7) instead of first copying A7 into A0. Similarly, it would have been possible to transfer $23456 directly to D7 and $12345678 to D1 then do ADD.L D1,D7 to avoid saving D0 (which would have been unused), and the transfer D1 to D7 which would then not have had to happen. Likewise, we could have returned the result through the stack instead of doing it through D7. Many possible variants, isn't it? To finish, a little exercise. Relaunch this little program and analyze PERFECTLY EVERYTHING that happens there. Something is not right! I help you by saying that it is of course about the stack. Search and try to find out how to fix it. The answer will be at the beginning of the next course but try to imagine that it's your program and it's not working and search!!! Well, the course on the stack ends here. It was a bit long but I think, not too complicated. Reread it, because the stack is a tricky thing that we are going to use VERY abundantly in the next course. If you have understood almost everything so far there is still time to catch up and start over from the beginning, because it is necessary to have PERFECTLY understood everything and not almost! To cheer you up, I'll tell you that you are almost halfway through the course...
Back to ASM_Tutorial