About Stack Frames
#1
About Stack Frames



Chapter 1. Intro

If you have been working on ASM codes for some bit of time, you may have used one of the following sources below...

Code:
stwu sp, -0x50 (sp) #Push stack, make space for 18 registers
stmw r14, 0x8 (sp)

lmw r14, 0x8 (sp)
addi sp, sp, 0x50 #Pop stack


Code:
stwu sp, -0x80 (sp) #Push stack, make space for 29 registers
stmw r3, 0x8 (sp)

lmw r3, 0x8 (sp)
addi sp, sp, 0x80 #Pop stack


These sources are good methods to get extra registers to utilize for your ASM codes. However, it's better to create a custom stack frame instead of using one of the generic versions above. As you should only use as much stack space as your code requires. This will also involve teaching you how to backup Floating Point Registers to the stack frame if you ever run into a situation that calls for it.

It is first recommended you go to this thread HERE and read the examples plus notes/rules for the following instructions: stwu, stmw, lmw, addi, stfd, & lfd.

The purpose of creating a stack frame is to backup certain registers that could/will lose their values during the execution of your ASM code. It's used because its a universal method that can work on any Wii game. Sure you could use the Exception Vector Area, but there isn't that much available space there and many other ASM codes already utilize that space for storing misc values. You could find some other place to store them, but then it may only work just for the Wii game you are using.



Chapter 2. Details of Creating Frame, Storing Registers

Code:
stwu sp, -0x50 (sp)
stmw r14, 0x8 (sp)

lmw r14, 0x8 (sp)
addi sp, sp, 0x50

The above is a common list of instructions used in some MKWii ASM codes to store the non-volatile (Global Variable) registers to the stack and load them back at the end of code. Usually, the default instruction of the code is placed before the stwu instruction or after the addi instruction. The contents of the ASM code itself is placed in the middle of the two sets of stack instructions.

Register 1 aka the Stack Pointer (sp) is the register that holds the address to the current stack. The stack stores important values from previous functinos calls that may need to be retrieved/used at a later time.  The stack grows DOWNWARD. As in towards LOWER memory addresses. So if you were viewing memory addresses on something like Dolphin-memory-engine, the stack is actually growing upward for your view.

So since the stack grows downward, thus anything before sp's current value is free space (more on stack limits later). So first we need to backup sp's value. At the same time, let's store that value to the address we want to, and... update sp to have the new address. This is all done via the first stwu instruction...

Code:
stwu sp, -0x50 (sp)

The old sp value is store -0x50 in reference to its address value. So let's say sp's value is 0x80371550. With the above instruction, the value 0x80371550 is stored at 0x80371500. At this point, you are wondering why the offset amount of -0x50 for the stmw instruction is used. We will address that shortly...

Code:
stmw r14, 0x8 (sp)

This instruction will store all registers starting at r14 going upward to r31; to the memory location of sp plus 0x8. Thus, they are now all copied over to our new stack frame. Your code can use the registers now as the instructions lmw, and addi will retrieve the values and replace sp with its original value.



Chapter 3. Illustration

Now that you understand how a new stack frame is created with the registers' values being stored, let's take a look of an illustration to demonstrate what would memory look like after execution of the stwu and stmw instructions from above

Code:
#      ...       #  Lower Address (Stack grows in the direction towards lower addresses aka downward)
#~~~~~~~~~~~~~~~~#
#     New sp     #  0x80371500 (sp's old address value of 0x80371550 is here)
#~~~~~~~~~~~~~~~~#
# Padding of 0x4 #  0x80371504
#~~~~~~~~~~~~~~~~#
#      r14       #  0x80371508 (0x8)
#~~~~~~~~~~~~~~~~#
#      r15       #  0x8037150C (0xC)
#~~~~~~~~~~~~~~~~#
# r16 thru... 30 #  0x80371510  (0x10 thru 0x48)
#~~~~~~~~~~~~~~~~#
#      r31       #  0x8035154C (0x4C)
#~~~~~~~~~~~~~~~~#
#     Old sp     #  0x80371550
#~~~~~~~~~~~~~~~~#
#      ...       #  Higher Address

As you can see there is padding of 0x4 after new sp. This is ALWAYS done for any stack frame you create. Hence why the stmw instruction is done with an offset of 0x8. At offset 0 (or at new sp) is old sp's value. The padding is done so that if you do happen to call another function (more about function calls HERE), then once that new function is called, the game ALWAYS stores the LR to that spot in the padding. Yes, I know its not needed for most ASM codes as most ASM codes don't do function calls, but this method of adding the padding is to for good habit and to keep consistency.

The values in the parenthesis are the starting offset values for each item on the illustration. r31 starts at 0x4C offset. Ofc since r31 is 4 bytes long, it uses offsets 0x4C thru 0x4F

You can also see I didn't bother list every register of r14 thru r31 separately on the illustration. What's important is that you see r31 is exactly the the memory address before the old sp. So the amount of space we used for our stack frame was the BARE minimum. Ofc you could add more stack space to have padding in between r31 and old sp, but try to only allocate the amount you need.



Chapter 4. Stack Frame Size Rules/Limits

As mentioned moments ago, only allocate the amount of space you need. Personally the most stack space I have seen allocated was around 0x300 of bytes. But sometimes, a big stack allocation can cause a crash as the space might not be available.

Now at this point, you have a good idea of how much space you need. You need 0x4 for old sp's value stored at new sp's location. You need 0x4 of padding afterwards. Then you need space for your registers. Each register takes up 4 bytes of data ofc...

So here's a simple equation to do to calculate stack space for your stack frame...

[4 x (# of Registers)] + 8 = Stack Space

Keep in mind we are using the stmw instruction for backing up registers. So if we want 3 free registers, the registers that would be backed up are r29, r30, and r31. Obviously with this instruction you need at least 2 registers (r30 and r31) or else trying to do a stmw instruction backing up just one register (r31) will output an error within the compiler.

So let's say we want 5 free registers (which would be r27 thru r31 due to stmw instruction) to use.. Let's do the calculation to figure out how much of a stack frame size to create

4 x 5 = 20

20 + 8 = 28

That is a value 0x1C in hex. Great so if we were to create a stack frame, we would start with this....

Code:
stwu sp, -0x1C (sp)
stmw r27, 0x8 (sp)

So are we good? No, we aren't. PowerPC has specific rules for creating stack frames. One of those rules is that all stack frames created must have a size that is 16 bytes (quadword) aligned. Thus because of this, the stack space that you have created must be a value divisible by 0x10 (aka the hex value ends in a zero). The value 0x1C does not end in zero, so we are breaking this rule.

We follow this rule as if an ASM Code has a function call in it, then the misalignment may cause a crash. Ofc, if the ASM code doesn't do any function calls at all, it doesn't matter. But once again, good habit and consistency is why.

Since our calculation for the stack frame size is 0x1C, we need to bump it up to next hex number that ends in zero. So the stack space we will allocate is 0x20. Here's the instructions..

Code:
stwu sp, -0x20 (sp)
stmw r27, 0x8 (sp)



Chapter 5. Overview of Recovering Values From Stack

Now we have a stack frame created, We put it in some code contents, and now we need to retrieve all the values back...

Code:
lmw r27, 0x8 (sp)

The lmw is simply the opposite of stmw. It loads all the values beginning at 0x8 offset of sp and stores them in the source register all the way to r31 depending on which register is listed in the instruction. Ofc you can't do this with r31 as the source register (just like in the stmw instruction). Onto the final instruction..

Code:
addi sp, sp, 0x20

The value you used in the addi instruction is simply the positive value of what was used in the stwu instruction. That's all you need to know. This makes sp have its old value again. And that's it. You don't need to do any instruction to remove what values are left in memory, as the game will wipe/replace them the next time it creates a stack frame after the execution of your code's address.



Chapter 6. Storing Floats to a Stack Frame

When storing floats, you should always store them in their double precision form instead of single.

Since double precision values are used, this changes up the stack calculation as each double precision float in memory takes up 8 bytes. Let's say you wanna store 6 registers (via stmw/lmw so r26 thru r31)) and 2 floating point registers (f1 and f2)... Here's the calculation...

4 x 6 = 24 #for GPRs
8 x 2 = 16 #for FPRs

24 + 16 = 40

40 + 8 = 48 (0x30 in hex)

So our stack space already ends in 0x0 so we don't need to add any more space for quadword alignment. Now at this point, you are wondering which do I store first, the FPRs or the GPRs. It's always easier to store the items that take up less total stack space. Since our two FPRs take up less space than the 6 GPRs, we will store them first. There are no stwm/lmw type instructions that work for floats. Same with store/load string word type instructions for floats. Each FPR must be stored/loaded one at a time... So with all of this in mind, let's look at creating the stack frame and storing the FPRs....

Code:
stwu sp, -0x30 (sp)
stfd f1, 0x8 (sp)
stfd f2, 0x10 (sp)

Alright now we store the GPRs....

Code:
stmw r26, 0x18 (sp)

Ofc, make sure all your offsets are correct so you don't wipe half of an FPR or a whole GPR when storing both floats and GPRs...

Alright let's say we are at the end of our code, lets recover the values and update sp with it's old value..

Code:
lmw r26, 0x18 (sp)
lfd f2, 0x10 (sp)
lfd f1, 0x8 (sp)
addi sp, sp, 0x30



Chapter 7. Quick Handy Note about the Stack Pointer

I've mentioned earlier that the negative area of the Stack Pointer is safe to use. If your code doesn't include any function calls, chances are, you can simply store register values to the negative area w/o creating a new Stack Frame.

Quick example:

Code:
stwm r29, -0xC (sp) #r29 stores at -0xC in reference to sp, r30 at -0x8, r31 at -0x4
...
lmw r29, -0xC (sp)

Alright, at this point you should know how to make your own stack frames 'from scratch'. Happy coding.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)