It seems good to have the caller push and pop function arguments:
caller:
ld hl, 0x0001
push hl
ld hl, 0x0002
push hl
call callee
pop hl
pop hl
Then the callee can extract those arguments:
callee:
ld hl, 0x0002
add hl, sp
ld e, (hl)
inc hl
ld d, (hl) ; de==0x0002
inc hl
ld c, (hl)
inc hl
ld b, (hl) ; bc==0x0001
...
The ld hl, 0x0002
and add hl, sp
is for skipping the return address on the stack. This is because the "call" itself will push the return address after your params (closer to the top of the stack). You don't have to extract the arguments at the beginning of the function. If an argument is only needed later on you can do the add hl, sp
dance later also.
If you are in tight control of the caller and callee then it might be better to pass arguments via registers:
caller:
ld bc, 0x0001
ld de, 0x0002
push bc
push de
call callee
pop de
pop bc
callee:
; can just go ahead and use bc and de
Because the caller has pushed de
and bc
those values in the caller are backed-up and restored. If the caller doesn't need those values after the call they can just not push them.
Surprisingly using the indexed registers is slower:
callee:
ld ix, 0x0002
add ix, sp
ld e, (ix+0)
ld d, (ix+1)
ld c, (ix+2)
ld b, (ix+3)
I usually just set a value in hl
and return.
You can specify locations in memory to serve as variables:
save_variable:
ld de, 0x1234
ld (0x0008), de ; save 0x1234 at memory 0x0008 and 0x0009
Or create lables for your variables so the code is more readable:
var_myfoo: equ 0x0006 ; the myfoo variable is located here
var_mybar: equ 0x0008 ; remember to leave space is prev is 16 bit
save_variable:
ld de, 0x1234
ld (var_myfoo), de
Very often you want a table of data in memory, like an array of structs.
It is useful for your tables to be 256 items in length so you can use an 8 bit register as an index and it will auto-wrap. Alternatively you can mask the index, meaning your table can be 2, 4, 8, 16 .. etc items long. In general is is simpler to have sizes be a power of 2.
When you have a table index and you want to convert an index into a memory address it is convenient if your table width (record length) is 2, 4, 8, 16 .. etc in size. for example if your record length is 8, the address of the entry is (base + (idx * 8))
. For example:
base_addr: equ 0x0100 ; base address of table
main:
ld de ; make a backup of de
ld hl, 0x0005 ; record index (record nr. 6)
call get_record_addr
pop de ; restore de
; hl converted into address
get_record_addr:
add hl, hl ; multiply x2
add hl, hl ; multiply x4
add hl, hl ; multiply x8
ld de, table_base_addr ; load the table base address
add hl, de ; hl is now the memory addr of idx 5
ret
For my OS project I wanted to have programs call into kernel routines without knowing their memory address. For this