News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu
avatar_zhulien

coding conventions and portability and something new...

Started by zhulien, 08:53, 07 September 16

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

zhulien

I was experimenting with different calling conventions, easiest coding vs lease register juggling vs re-entrancy support etc...


Calling conventions:

method 1: Filling up registers

This seems to be the fastest way but because Z80 hasn't many registers, it requires lots of juggling of registers with deep nesting of functions.  If there is consistency in their use eg, order of filling them up, then it is easy to remember what gets used for each function, but then results likely need to be moved into inputs, lots of preseving before calling etc... Aside from that, without lots of deep nested functions, this is still the fastest parameter passing method with about 30% faster than pushing/popping off the stack. When deeply nested the 30% decreases quite a bit.  Also soon enough we run out of parameters needing pointers to structures in memory to be passed.

eg:

ld hl, mystring
call print_string    ;corrupts af, hl
ld hl, mystring2
call print_string    ;corrupts af, hl

mystring: defb "hello", 0
mystring2: defb "goodbye", 0



method 2: Pushing & popping off the stack

Recursion problems resolved... but now we have lots of stack overhead, especially for small operations. No register limit to worry, a bit of register juggling upon entry and exit of functions.

eg:

ld hl, mystring
push hl
call print_string    ;corrupts whatever

ld hl, mystring2
push hl
call print_string    ;corrupts whatever

mystring: defb "hello", 0
mystring2: defb "goodbye", 0



method 3: Parameter constructs following each call

This is an interetsing idea i had and haven't seen anyone doing this, but basically every operation has it's parameters immediately following the call.  Since the receiving end of the call knows the usual return point, it obviously is automatically fed the parameter block.  At the time of reading the parameter push the return address before returning.  Downside... not easily ROMable.  Upside. almost no register juggling and slightly smaller program size.

eg 1:

call print_string
defb "hello", 0
call print_string
defb "goodbye", 0

eg 2:

call print_string_ptr
defb mystring
call print_string_ptr
defb mystring2

mystring: defb "hello", 0
mystring2: defb "goodbye", 0



method 4: Parameters are always a block maintained by an index register pointed to by a 2nd manually controlled stack of points to parameter blocks.  This has no register limit but is really quite slow because index register operations are slow.


- So far I still like method 1 most, but there is merit considering the others.  Probably there are more methods, can you think of any?



Portability:

Now, to make things more portable, how to make the same EXE code work on all Z80 based platforms without change.  This is done by keeping a couple of values always available within registers.  ie: it could be an index register, or it could be an alternative register set... 1 is the OS Entry point (i.e. using de'), basically a stub that is initialised by the program loader before it loads so the portable program.  2 is the interrupt state, basically a nest count (i.e. using a').

So every program will have something like this at the start to call the os entry that is stored in de'.  c' being a function number.  There are a lot of variants that can work well...  This has given us 2 extra registers to play with and allowing the program to call a variable system entry point.  We still need a relocator upon program load, but that is pretty straight forward.  The safe ei and di's are so that the application whether it is a callback from the OS, or a function that needs di that is sometimes nested in the program itself, we don't need to worry about whether we should or not enable them.

osentry:    push de
        ret
        ret

safe_di:    exx
        di
        inc a
        exx
        ret

safe_ei:    exx
        and a
        jr z, safe_ei_ret
       
        dec a
        jr nz, safe_ei_ret
        ei
safe_ei_ret:    exx
        ret

and calling the os is like:

main:        ld hl, mystring
        exx
        ld c, print_string
        call osentry
        ld hl, mystring2
        exx
        ld c, print_string
        call osentry

mystring: defb "hello", 0
mystring2: defb "goodbye", 0



FAT EXE oddities:

I was playing around with CPU detection a while ago, how to make the same executable work on a 6502, z80 and 8086 and did have some lucky with it. Ultimately though it is kind of pointless because 8bit systems don't have RAM to waste to keep unused 'other cpu' code lying around.

Basically i looked at all the opcodes that didn't cause flow issues on each CPU, and also allowed jumping to their respective native entry points - it is intersting and possible but more a novelty.  If the program was 1% code and 99% shared data, perhaps it is worthwhile.


Why this topic?  Soon (over the next months) I will be releasing a couple of hopefully exciting softwares for CPC with source available. 

These are...

1. 8bitology.net (non-commercial) Web platform to allow CPC (and other 8bit platforms) to register, chat, share files, come online within MMO games, register achievement and hiscore leaderboards within games among other things - this will be open to all 8bit platforms, but I am focussing on CPC first.  ALMOST READY

2. BASIC version of 8bitology.net management tools (possibly with some RSXs provided) to allow you to register/unregister yourself, register/unregister games, achievements, view achievements, share/unshare files and a whole lot of other things.  As it is open (server and client) I am hoping we will see some future games supporting it.  NOT STARTED but also not a big project.

3. CPC Web Browser. Initially will be in BASIC with some functions done in portable assembler.  Future version likely fully portable multi-taskable written in assembler.  POC of resolution-responsive renderer is done.

4. MCP full Multi-tasking and memory management services for CPC when in BASIC/AMSDOS with optional automatic reset and poweroff recovery. I am hoping that some adoption of this is possible with 3rd party tools so that people don't keep trashing CPCs extra RAM when it is being used.  IN DEVELOPMENT


Thanks Duke for the inspiration... with your awesome Wifi board - everyone who doesn't buy it... don't know what they are missing!!!  It along with a Mother X4 and X-Mem from Cent Pour Cent are indispensable.

Docent


Method 3 was quite popular in arcade ROMs to display text.
It has one serious drawback (at least for me) - the code is not easily readable, especially when 7 bit of the last text character is used as the end of string marker instead of zero byte.

For correct handling interrupt states I'd suggest to store the flag somewhere in memory instead of a'. You can use the following code:

ld a,i ; check if interrupts are enabled
ld a,#0
jp po, intdisabled
inc a
intdisabled:
ld (int_enabled),a ; store interrupt flag

.... at the end

ld a,(#int_enabled) ; check interrupt status
or a
ret z
ei ; reenable interrupts
ret
int_enabled:
.db 0


This will guarantee correct restoring of z80  interrupt state at exit.
Additionally, some z80 derivatives don't have alternate register set (for example the cpu in Gameboy) or it can be used by the system/basic rom.

fano

On so small CPU method 1 is still the most interesting because it is faster.I use too method 3 for my own kernel calls with fixed params (MMR/ROM configuration for example)
But , if i'd have to write something portable on various 8 bits machine, i'd consider using a transitional macro language, because it allows to abstract CPU architecture.Btw , for the machines with more memory, that should be possible to compile program to save speed.I know this is a huge job but i think it would worth the try.
"NOP" is the perfect program : short , fast and (known) bug free

Follow Easter Egg products on Facebook !

SRS

this is interesting. in my long time ago informatics lessons they loved stacjk but would do registers where possible.

if you really want it to run on more machines you should go to high level like C, a self defined "makro language" or something existing on nearly all computer
from 1980 on: p-code.

just my 2 eurocents

FloppySoftware

#4
I use method 3 in MESCC when calling runtime functions.

It have the advantadge of less code size, at the cost of less speed.

Method 2 is used by MESCC also in the regular code when calling C functions, because it allows recursivity.
floppysoftware.es < NEW URL!!!
cpm-connections.blogspot.com.es

zhulien



EXAMPLE app:; using PATTERN C
; compatible with most Z80 systems?
; de holds the real OS entry so application doesn't need to know


start:
call os_entry
defb txt_out_char, "A"

call os_entry
defs txt_out_string, "Hello, World!", 0


call os_entry
defs txt_input_ptr, 20, inputted

call os_entry
defs txt_out_string_ptr, inputted


ret

os_entry: exx
push de
ret


inputted: defs 21




; real os entry would look something like

os_entry: pop hl ; address of parameters
push de ; os entry

ld b, 0
ld c, (hl) ; bc = function number

inc hl ; next parameter
push hl

add bc, bc ; bc = jumpblock entry
add bc, jumpblock

ld e, (bc) ; de = addr of function to call routine
inc bc
ld d, (bc)

pop hl ; hl = parameters beyond the function number

push de ; call the function in de
ret ;

pop de ; os entry

push hl ; return to the next command
ret ;

jumpblock: txt_out_char, txt_out_string, txt_input_ptr, txt_out_string_ptr


txt_out_char:
ld a, (hl)
; output the character in A
inc hl
ret

txt_out_string:
ld a, (hl)
and a
jr txt_out_str2
; output the character in A
inc hl
jr txt_out_string

txt_out_str2:
inc hl
ret


...

m_dr_m

Method 3 is used by the firmware! E.g. RST 8, RST &18.
I've also used that a lot.
C.f. for instance http://memoryfull.net/post.php?id=41&page=1#318
which would be used like that:

MACRO SET_CRTC reg,val
     RST 0:BYTE reg,val
ENDM


Method 5:
Having a block of messages, either separated by 0 or bit 7.
Then, you pass the message number N to the routine, which scans N strings before getting to the right one.
Slow but often negligible.
Used by firmware, amsdos, dams.


Method 5':
Orgams does method 5 with compressed text block.

m_dr_m

That being said, the general question of coding convention is a great one.
Most of the bugs I introduce are due to not respecting the interface of my own routines!


So, we could be tempted by a generic rule:
* Always saving all registers except of course for output parameters.
* Never saving registers...
But, one size doesn't fit all, and any of this guidelines would blow code footprint, which is inconvenient in ROM.

So, it's essential to properly document the routines, like the firmware vectors.



&BB66 - TXT WIN ENABLE
Sets the boundaries of the current text window - uses physical coordinates.


Entry

       
  • H holds the column number of one edge.
  • D holds the column number of the other edge.
  • L holds the line number of one edge.
  • E holds the line number of the other edge
Exit

       
  • AF, BC, DE and HL are corrupt.

But I'd like to go one step further: formalise the way the interface is documented, so it can be checked via static analysis.

luckpro


I prefer the first method for speed for games (I always look for speed). But honestly I could not say which method is better, sorry.

It seems to me a very interesting project, when we make games it is very lazy to investigate how the m4 card works to connect to the internet, investigate how to make a server to save and share information and test that everything works correctly.

If with your project we do not need to worry about all that and we only have to make simple calls to your functions, you facilitate this whole process and it is very possible that developers are encouraged (I include myself) to add scoreboards or online play (why not? ) to the games.


As a small observation, if you want to play online in real time I do not recommend that you use php for the server, it is preferable to use a C++ program or a nodejs server and directly open a TCP port to avoid HTTP protocol.

In any case, it looks very good. Keep working on it please.

Powered by SMFPacks Menu Editor Mod