Using Macros & Statements for Ease of Compilation (ASM)
#1
Using Macros & Statements for Ease of Compilation (ASM)






Are your ASM codes getting complex enough where you need to use macros and statements? This tutorial is for you. Once ASM codes get large enough, you will start using values multiple times, or a set/list of instructions over and over again. No sense typing those instructions/values out when you can just use a label name like you do for branches. Also, if your ASM is using addresses that differ per region and you are tired of manually porting those addresses within the ASM on compiled byte code, I will also cover how to get rid of this nuisance.



Setting Label Names via Statements (.set)

.set _Amount, 0x100

Very simple statement that you should already understand. Any time you type _Amount in your source, the value 0x100 is used.

.set _Offset, 0x00C0

Another simple Macro. Let's combine the two simple Macros to show you a quick demo...

li r4, _Amount
stw r4, _Offset (r31)

As you can see we utilized these two Macros to store _Amount to a Memory address based off of r31.



Setting a Simple Macro

You have the following list of instructions...

lis r12, 0x8000
ori r12, r12, 0x1500

And you use this set of instructions a multitude of times in your code. Instead of writing it out every time, use a Macro instead! Like this....

.macro _setTheAddress
lis r12, 0x8000
ori r12, r12, 0x1500
.endm

Very simple Macro. The ".macro" starts the Macro followed by the Macro's label name. The next two 'tabbed' lines are the macro contents. Finally the ".endm" tells the compiler that it is the end of that specific Macro. If you put _setTheAddress in your source, the above lis & ori instructions via r12 will be used.



Getting More Complex with Macros

So now you know how to use a basic macro for a list of instructions. But what if we don't wanna use the same register (r12) every time for the macro we just made? Simple, like this...

.macro _setTheAddress register
lis \register, 0x8000
ori \register, \register, 0x1500
.endm

The term 'register' after the Macro's label name tells the compiler that a register value must be applied for the macro. The macro contents are adjusted to the take that designated register and use it for the lis and ori instructions. Like in this provided snippet of compilable source...


Code:
.macro _setTheAddress register
    lis \register, 0x8000
    ori \register, \register, 0x1500
.endm

.set _theValue, 0x4 #Set label name for 0x4

addi r19, r19, _theValue #Instruction utilizing the label name for 0x4
_setTheAddress r11 #lis r11, 0x8000 with ori r11, r11, 0x1500 is preformed
stw r19, 0 (r11)


Alright great, this allows you to use any register you want for the macro _setTheAddress. Let's go one step further....Let's change the macro to allow the use of any address plus register...


Code:
.macro _setTheAddress register, address
    lis \register, \address@h
    ori \register, \register, \address@l
.endm

.set _theValue, 0x4 #Set label name for 0x4

addi r19, r19, _theValue #Instruction utilizing the label name for 0x4
_setTheAddress r11, 0x81458000 #lis r11, 0x8000 with ori r11, r11, 0x1500 is preformed
stw r19, 0 (r11)

The "@h" after the first use of address represents the upper 16 bits while the "@l" for the second use of address represents the lower 16 bits.



Using Labels with Address Values and Macros Together

Now you know how to change up the macros a bit, we can now use the .set function to set Memory Address values to use with address term of our Macros... Here's a snippet of code demonstrating that:


Code:
.macro _setTheAddress register, address
    lis \register, \address@h
    ori \register, \register, \address@l
.endm

.set _theValue, 0x4 #Set label name for 0x4
.set _theAddress, 0x8042BABC

addi r19, r19, _theValue #Instruction utilizing the label name for 0x4
_setTheAddress r11, _theAddress
stw r19, 0 (r11)




Getting a Bit More Complex with .set

While .set can establish simple values, you use basic equations too.

.set _basevalue, 6

.set plus_2, _basevalue+2 #6 plus 2 = 8
.set times_8, _basevalue*8 #6 times 8 = 48 (0x30 in Hex)
.set divide_3, _basevalue/3 #6 divided by 3 = 2
.set subtract_5, _basevalue-5 #6 minus 5 = 1

This can come in handy for basic equations.



Using .set and if statements to compile different versions of source based on region.

For example, let's say you have an ASM code, and in it is a stw instruction that stores to this address 0x80421500. Like this....

li r4, 0x5
lis r5, 0x8042
ori r5, r5, 0x1500
stw 4, 0 (r5)

But the address in r5 is for NTSC-U only. It differs slightly for every region of MKWii. Instead of writing the specific source for NTSC-U, compiling it, then finding the compiled hex byte code instruction to manually change.... You can instead use a fancy trick with .set and if/end-if statements to get around this nuisance. 

.set region, '' #Fill in E, P, J, or K within the quotes for your region when Compiling! Lowercase letters can also be used.

.if     (region == 'E' || region == 'e') # RMCE
        .set The_Addr, 0x80421500
.elseif (region == 'P' || region == 'p') # RMCP
        .set The_Addr, 0x8042BCCC
.elseif (region == 'J' || region == 'j') # RMCJ
        .set The_Addr, 0x8042A074
.elseif (region == 'K' || region == 'k') # RMCK
        .set The_Addr, 0x8041FFF0
.else # Invalid Region
        .abort
.endif

As you can see we use multiple different address values for the label of The_Addr. The if/else-if statements were put in to only use one of those versions of the label name depending on the alphabetical letter used in the ".set region" statement. The if/else-if statements read the value for the label "region" and if one of the values is found, the label name will be given the corresponding address for compilation. If the value for the ".set region" statement is left blank, or an invalid value is used, the compiler will abort the compilation and notify you that compilation could not be done. This is accomplished via the use of .else and .abort.

This saves a lot of headaches for codes that have varying address instructions due to the version/region of the code.



Macros within If/Else-If statements

If desired, you can put whole macros within the if/else if statements.... Take a look at this snippet of compilable source


Code:
.set region, '' #Fill in E, P, J, or K within the quotes for your region when Compiling! Lowercase letters can also be used.

.if     (region == 'E' || region == 'e') # RMCE
        .macro _setTheAddress register
        lis \register, 0x8000
        ori \register, \register, 0x1500
        .endm
.elseif (region == 'P' || region == 'p') # RMCP
        .macro _setTheAddress register
        lis \register, 0x8000
        ori \register, \register, 0x1504
        .endm
.elseif (region == 'J' || region == 'j') # RMCJ
        .macro _setTheAddress register
        lis \register, 0x8000
        ori \register, \register, 0x1508
        .endm
.elseif (region == 'K' || region == 'k') # RMCK
        .macro _setTheAddress register
        lis \register, 0x8000
        ori \register, \register, 0x150C
        .endm
.else # Invalid Region
        .abort
.endif

add r3, r3, r14 #Random instruction
_setTheAddress r11 #Set up r11 address
stw r3, 0 (r11) #store word of r3 to r11's address


To test out the compilation of this small source, throw this into your compiler and use E/P/J/K for the region value. You will notice the compiled code will slightly differ per region. If an invalid or missing value is used for region, the source will not compile.



Conclusion

Remember, the goal of these macros is to make it easier & more convenient to compile/modify your source. If the macros and/or statements are not helping, then don't use them. For an actual real word code that uses some good macros plus statements, check out Star's Screenshot code - http://mkwii.org/showthread.php?tid=1080
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)