LIB: pm_cstyle
Experimental CStyle layer for High Level programming
pm_cstyle.s
Type: Header File
Dependency: pm_init.s
Including Rules: Must be after pm_init.s
Overview
Note: All macros in this document are case insensitive
About CVar
CVar is a special format taken by the cstyle macros that specify the type of source/destination the user require, they can be:
Type | Usage | Description |
---|---|---|
Immediate value | immediate_value | Can be negative Cannot be more than 16-bits |
Local variable | LOCAL_OF + local_index local_of + local_index |
Only work when the base is valid 16 bits for parameters 16 or 8 bits for locals |
RAM variable | RAM_OF + symbol_name ram_of + symbol_name |
Read/Write only on bank 0 16 bits for parameters 16 or 8 bits for locals |
Near-Pointer variable | NEAR_OF + symbol_name near_of + symbol_name |
Register I is required for upper addressing 16 bits for parameters 16 or 8 bits for locals |
Far-Pointer variable | FAR_OF + symbol_name far_of + symbol_name |
Read/Write anywhere 16 bits for parameters 16 or 8 bits for locals |
Registers | REG_BA / reg_ba REG_HL / reg_hl REG_X / reg_x REG_A / reg_a REG_B / reg_b REG_L / reg_l REG_H / reg_h |
Directly access registers 8-Bits registers are processed on lower 8-bits of CVar |
High registers | REG_UA / reg_ua REG_UB / reg_ub REG_UL / reg_ul REG_UH / reg_uh |
8-Bits registers are processed on higher 8-bits of CVar |
Unsafe register | REG_Y_DESTROY / reg_y_destroy | Cannot be read Writing will prevent the future use of parameters and locals |
About CPtr
CPtr is another special format taken by the parameters to usualy specify a location to memory, they can be:
Type | Usage | Description |
---|---|---|
Immediate value | immediate_value | Cannot be negative or more than 21-bits |
Text string | "text string" | Pointer to string constant |
Local variable | LOCAL_OF + local_index local_of + local_index |
Only work when the base is valid 21 bits for parameters |
RAM variable | RAM_OF + symbol_name ram_of + symbol_name |
Read/Write only on bank 0 21 bits for parameters |
Near-Pointer variable | NEAR_OF + symbol_name near_of + symbol_name |
Register I is required for upper addressing 21 bits for parameters |
Far-Pointer variable | FAR_OF + symbol_name far_of + symbol_name |
Read/Write anywhere 21 bits for parameters |
Zero Upper Pointers | PTR_ZBA / ptr_zba PTR_ZHL / ptr_zhl PTR_ZX / ptr_zx PTR_ZY / ptr_zy |
Use registers to build the pointer Upper 16-bits will be zero |
Far Pointers | PTR_FBA / ptr_fba PTR_FHL / ptr_fhl PTR_FX / ptr_fx PTR_FY / ptr_fy |
Use registers to build the pointer Register BA will use register I |
Reg A Upper Pointers | PTR_AHL / ptr_ahl PTR_AX / ptr_ax PTR_AY / ptr_ay |
Use registers to build the pointer Register A will be used to indicate the upper register |
Reg B Upper Pointers | PTR_BHL / ptr_bhl PTR_BX / ptr_bx PTR_BY / ptr_by |
Use registers to build the pointer Register B will be used to indicate the upper register |
Reg L Upper Pointers | PTR_LBA / ptr_lba PTR_LX / ptr_lx PTR_LY / ptr_ly |
Use registers to build the pointer Register L will be used to indicate the upper register |
Reg H Upper Pointers | PTR_HBA / ptr_hba PTR_HX / ptr_hx PTR_HY / ptr_hy |
Use registers to build the pointer Register H will be used to indicate the upper register |
Creating a function
To create a function, the user need to declare the function, initialize, program what the function will do and finaly terminate it, here's an example:
DECLARE_FUNCTION "test", PTR_2|PTR_3, 5 ; CVar(1) + CPtr(2) + CPtr(2) = 5 Param CVar ; Param @0 = 1st Parameter CVar ; Param @1 = 2nd Parameter CPtr, lower 16-bits ; Param @2 = 2nd Parameter CPtr, higher 16-bits ; Param @3 = 3rd Parameter CPtr, lower 16-bits ; Param @4 = 3rd Parameter CPtr, higher 16-bits VARLOCAL _test, 2 ; Allocate 2 bytes for _test local BEGIN_FUNCTION GETPARAM 2, REG_A mov i, a GETPARAM 1, REG_HL mov ba, [hl] SETLOCALW _test, REG_BA GETPARAM 4, REG_A mov i, a GETPARAM 3, REG_HL mov ba, [hl] GETLOCALW _test, REG_HL add hl, ba cmp hl, ba RETURNC_WITH z, 0 mov ba, 1 END_FUNCTION 0 ; No end flags required
DECLARE_FUNCTION "funcname", cptr_mask, par_cvars
Declaring a function requires the name of the function as a string, mask of CPtr type used in the function and the number of parameters.
- funcname - Function name won't create a label with the exact same name, this allow normal labels to have the same name as the function.
- cptr_mask - The mask to indicate which parameters are CVar or CPtr, each CPtr is converted into 2 CVar during calls, use logic OR or addition to specify multiple CPtr
- par_cvars - Number of CVar the function require, note that 1 CPtr takes 2 parameters slots.
Flags | Description |
---|---|
PTR_1 | CPtr on 1st parameter |
PTR_2 | CPtr on 2nd parameter |
PTR_3 | CPtr on 3rd parameter |
PTR_4 | CPtr on 4th parameter |
PTR_5 | CPtr on 5th parameter |
PTR_6 | CPtr on 6th parameter |
PTR_7 | CPtr on 7th parameter |
PTR_8 | CPtr on 8th parameter |
PTR_9 | CPtr on 9th parameter |
PTR_10 | CPtr on 10th parameter |
PTR_11 | CPtr on 11th parameter |
PTR_12 | CPtr on 12th parameter |
PTR_13 | CPtr on 13th parameter |
PTR_14 | CPtr on 14th parameter |
PTR_15 | CPtr on 15th parameter |
PTR_16 | CPtr on 16th parameter |
VARLOCAL variable_name, variable_size
Define a local, it's recommended to start variable with underscore to avoid symbols conflicts.
Must be defined between DECLARE_FUNCTION and RESERVELOCAL.
- variable_name - Name of the variable.
- variable_size - Number of bytes to allocate, 2 for CVar and 4 for CPtr.
RESERVELOCAL reserve_size
Reserve local space.
Must be defined between DECLARE_FUNCTION and RESERVELOCAL.
- reserve_size - Number of bytes to reserve.
BEGIN_FUNCTION
Initialize the function code
END_FUNCTION end_flags
Terminate the function code
- end_flags - Flags to specify how to terminate a function, they can be mixed with logic OR or addition.
Flags | Description |
---|---|
ENDF_BYTE endf_byte |
Result is a byte, register B will be automaticaly set to 0 |
ENDF_RESTORE_YI endf_restore_yi |
Restore register YI if was destroyed between the function |
ENDF_HL_STACK endf_hl_stack |
Manipulate stack restoration with register HL in case the function takes a variable number of parameters |
Accessing parameters inside the function
Coding inside the function is exactly the same as if was outside of it with exception that the register Y shouldn't be touched unless no longer requires to access parameters.
Macros to manipulate the parameters will backup any temporary register during the operation so they are safe to work between registers manipulations.
Note that parameters start at index 0, not 1! Each parameter is full 16-bits so 3 parameters are 6 bytes in total.
CPtr are 32-bits, the lower 16-bits are stored first on the parameters list.
Macro | Description |
---|---|
GETPARAM param_index, cvar | Get parameter value inside a function param_index must be between 0 and number of params - 1 Locals and pointers are processed in 16-bits |
Manipulating locals inside the function
Register Y shouldn't be touched unless no longer requires to access parameters or locals.
Macros to manipulate the locals will backup any temporary register during the operation so they are safe to work between registers manipulations.
Locals start at index 0 but each is 8-bits, so 3 16-bits locals (to host 3 CVar) take space of 6 locals with the following indexes: 0, 2 and 4.
Warning! Writing outside range will corrupt the stack.
Macro | Description |
---|---|
SETLOCALW local_index, cvar | Set 16-bits value into a local inside a function local_index must be between 0 and number of locals - 1 Locals and globals are processed in 16-bits |
GETLOCALW local_index, cvar | Get 16-bits value from a local inside a function (strict 16-bits) local_index must be between 0 and number of locals - 1 Locals and globals are processed in 16-bits |
SETLOCALB local_index, cvar | Set 8-bits value into a local inside a function (strict 8-bits) local_index must be between 0 and number of locals - 1 Locals and globals are processed in 8-bits |
GETLOCALB local_index, cvar | Get 8-bits value from a local inside a function (strict 8-bits) local_index must be between 0 and number of locals - 1 Locals and globals are processed in 8-bits |
Returning from a function
In the middle of the function the user may need to terminate the function, the following macros can be used without the need to create a label at the end of the function:
Macro | Description |
---|---|
RETURN | Return from a function, same as jumping to END_FUNCTION |
RETURNC cond | Return from a function if the condition is met, same as conditional jumping to END_FUNCTION |
RETURN_WITH cvar | Return from a function with a specific value |
RETURNC_WITH cond, cvar | Return from a function with a specific value if the condition is met |
Conditional returns can have the following conditions:
Condition | Description | Flags Logic |
---|---|---|
C | Carry | Carry = 1 |
NC | Not carry | Carry = 0 |
Z | Zero | Zero = 1 |
NZ | Non-Zero | Zero = 0 |
L | Less than | (Overflow=1) != (Sign=1) |
LE | Less or equal | ((Overflow=0) != (Sign=0)) OR (Zero=1) |
G | Greater than | ((Overflow=1) == (Sign=1)) AND (Zero=0) |
GE | Greater or equal | (Overflow=0) == (Sign=0) |
O | Overflow | Overflow = 1 |
NO | Not overflow | Overflow = 0 |
NS | Not sign (Positive) | Sign = 0 |
S | Sign (Negative) | Sign = 1 |
Invoking / Calling a function
Calling a function can be done by using the correct macro which depends of how many parameters the function was created, using the wrong version might cause some stack corruption.
Macro | Description |
---|---|
CINVOKE0 funcname | Invoke a function without parameters |
CINVOKE1 funcname, par1_cvar | Invoke a 1 parameter function |
CINVOKE2 funcname, par1_cvar, par2_cvar | Invoke a 2 parameters function |
CINVOKE3 funcname, par1_cvar, par2_cvar, par3_cvar | Invoke a 3 parameters function |
CINVOKE4 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar | Invoke a 4 parameters function |
CINVOKE5 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar | Invoke a 5 parameters function |
CINVOKE6 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar | Invoke a 6 parameters function |
CINVOKE7 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar | Invoke a 7 parameters function |
CINVOKE8 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar | Invoke a 8 parameters function |
CINVOKE9 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar | Invoke a 9 parameters function |
CINVOKE10 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar | Invoke a 10 parameters function |
CINVOKE11 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar, par11_cvar | Invoke a 11 parameters function |
CINVOKE12 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar, par11_cvar, par12_cvar | Invoke a 12 parameters function |
CINVOKE13 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar, par11_cvar, par12_cvar, par13_cvar | Invoke a 13 parameters function |
CINVOKE14 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar, par11_cvar, par12_cvar, par13_cvar, par14_cvar | Invoke a 14 parameters function |
CINVOKE15 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar, par11_cvar, par12_cvar, par13_cvar, par14_cvar, par15_cvar | Invoke a 15 parameters function |
CINVOKE16 funcname, par1_cvar, par2_cvar, par3_cvar, par4_cvar, par5_cvar, par6_cvar, par7_cvar, par8_cvar, par9_cvar, par10_cvar, par11_cvar, par12_cvar, par13_cvar, par14_cvar, par15_cvar, par16_cvar | Invoke a 16 parameters function |
Note that nearly all registers may be destroyed after returning from a call, here's a table with the rules:
Register | State after call |
---|---|
BA | 16-Bits Result |
HL | Saved |
X | Not saved, can be used as 16-Bits Result too |
Y | Saved, used as Frame Base User shouldn't manipulate this register |
I | Not saved, may be destroyed |
XI | Not saved, may be destroyed |
YI | Must be set to 0! ENDF_RESTORE_YI can be used on END_FUNCTION to restore automaticaly User shouldn't manipulate this register |
Register BA can be used to handle the result after invoking but there's also a macro that can store the result into a CVar:
Macro | Description |
---|---|
RETURN_TO cvar | Put last invoked function result into a CVar Must be called immediately after invoking |
Avoiding unused functions to take space
A problem of designing a library is how to spend the most minimal space but still offer functionality and performance. This comes with a bigger problem when the assembler can't tell if a certain block of code will be actually used on run-time so unused code will waste some valuable space that could be used for something else.
Is possible to setup CStyle functions to only include the code if the function is actually invoked in some part of the code, by calling CINVOKE the macro will create a definition that will allow to tell if the function should be included or not.
If the function is called foo, CINVOKE will create a definition called foo_cinvoked, bar will be bar_cinvoked.
By surrounding the function BEGIN_FUNCTION and END_FUNCTION with .ifdef foo_cinvoked and .endif the assembler will be able to include the foo function only if the definition is defined.
DECLARE_FUNCTION "foo", 0, 0 .ifdef foo_cinvoked BEGIN_FUNCTI0N 0 mov ba, 0 END_FUNCTION 0 .endif
Due to PMAS 0.20 multipass, the *_cinvoked definition will remail alive even when the function was defined before the call, this feature also allows multiple functions to be chained up to a certain limit.
Increasing the number of multipasses will increase the limit but will make the assembler to take longer to finish, by default this value is 4. For example to override multipass to 8 use this before including pm_cstyle.s:
Calling functions with more friendly syntax by using macros
When working with large amount of functions, it might be easier to handle and be less error prone to define macros to invoke your functions, this avoid mistakes like using the wrong CINVOKE macro and makes the source more cleaner and simple.
To create a macro use the .macro directive followed by the name of the macro and the parameters separated by commas on the same line, place the CINVOKE with the function name and the macro parameters on the next line and in the 3rd line use the .endm directive to close the macro.
.macro foo param, anotherparam CINVOKE2 foo, param, anotherparam .endm
Now the user can type "foo 0, 0" and the assembler will expand to "cinvoke2 foo, 0, 0".
Example
.include "pm_libs/pm_init.s" .include "pm_libs/pm_cstyle.s" ; 1nd: Game Title string, must be 12 characters ; 2rd: Interrupts to map, OR flags for multiple interrupts ; 3th: Extra flags pm_header "PMCStyle TsT", IRQ_KEY_POWER, 0 ; Power button, shutdown PM irq_key_power: ; Interrupt function (same name as definition but in lowercase) cint CINT_SHUTDOWN ; Call shutdown in BIOS .ram somevar 2 ; Store 16-bits ; Define a custom function declare_function "function_test", 3 begin_function 2 ; Get 1st parameter (index 0) and transfer into register BA getparam 0, REG_BA ; Get 2nd parameter (index 1) and transfer into 1st local (index 0) getparam 1, local_of + 0 mov hl, 256 add ba, hl returnc z ; Return if zero ; Get 3nd parameter (index 2) and transfer into ram getparam 2, ram_of + somevar ; Get 1st local (index 0) and transfer into register HL getlocal 0, REG_HL add ba, hl ; Terminate the function, returns will jump here end_function 0 ; Another function but this one is only included in the binary ; if the function was invoked ; 3rd parameter is a CPtr declare_function "nice_function", PTR_3, 4 .ifdef nice_function_cinvoked begin_function 0 getparam 3, REG_A mov i, a getparam 2, REG_HL mov hl, [hl] getparam 0, REG_BA getparam 1, REG_X add ba, hl add ba, x end_function .endif ; Define a friendly macro for the function .macro nice_function foo, bar, addr cinvoke2 nice_function, foo, bar, addr .endm ; Same method but this one won't be included because the source ; never invokes this function declare_function "unused_function", 0 .ifdef unused_function_cinvoked begin_function 0 nop end_function 0 .endif .ram othervar 2 ; 16-bits too! ; Code starts here main: ; Enable power key interrupt enable_irqs IRQ_KEY_POWER ; Enable interrupts enable_mirq ; Invoke the first function cinvoke3 function_test, 0, ram_of + othervar, 0 ; Invoke the second function with a nicer syntax nice_function ram_of + othervar, 0x1337, somevar ; Don't invoke the third function, no code will be generated for that one : halt jmp :b pm_rominfo ; Optional, report ROM and RAM size