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
32 bits for parameters
RAM variable RAM_OF + symbol_name
ram_of + symbol_name
Read/Write only on bank 0
32 bits for parameters
Near-Pointer variable NEAR_OF + symbol_name
near_of + symbol_name
Register I is required for upper addressing
32 bits for parameters
Far-Pointer variable FAR_OF + symbol_name
far_of + symbol_name
Read/Write anywhere
32 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

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
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 PTR_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

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) + CVar(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
BEGIN_FUNCTION 2	; Allocate 2 bytes for locals
  GET_PARAM 2, REG_A
  mov i, a
  GET_PARAM 1, REG_HL
  mov ba, [hl]
  SET_LOCAL 0, REG_BA
  GET_PARAM 4, REG_A
  mov i, a
  GET_PARAM 3, REG_HL
  mov ba, [hl]
  GET_LOCAL 0, 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.

  1. 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.
  2. 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
  3. 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

BEGIN_FUNCTION locals_size

Initialize the function code

  1. locals_size - Size of locals in bytes to be allocated, set to 0 if locals aren't needed.

END_FUNCTION end_flags

Terminate the function code

  1. 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

Working 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 or locals.

Macros to manipulate the parameters and locals will backup any temporary register (except register F) 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.

Locals start at index 0 too 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
GETPARAM param_index, cvar

Get parameter value inside a function

param_index must be between 0 and number of params - 1
cval as immediate isn't valid

Locals and pointers are processed in 16-bits

SETLOCAL local_index, cvar

Set 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
16-bits registers are processed in 16-bits
8-bits registers are processed in 8-bits

GETLOCAL local_index, cvar

Get value from a local inside a function

local_index must be between 0 and number of locals - 1

Locals and globals are processed in 16-bits

SETLOCALW local_index, cvar

Set value into 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
16-bits registers are processed in 16-bits
8-bits registers are processed in 16-bits

GETLOCALW local_index, cvar

Get 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 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
16-bits registers are processed in 8-bits
8-bits registers are processed in 8-bits

GETLOCALB local_index, cvar

Get 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:

.set PM_MULTIPASS 8

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