News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

c->asm and asm->c

Started by arnoldemu, 15:18, 24 May 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

arnoldemu

For a game I am working on the majority of the engine is written in assembler.

The game is triggered from assembler. Assembler takes control and sets up the hardware state and implements a lot of the functions.

For a normal C program, we have a CRT which takes control, we define it's address in the linker file, it calls into main() in the C program, and we have library functions that the C code executes. The location of the library files are not defined until link time, but that's ok C linker does all the work. Normally C calls into assembler but only the library functions. Asm doesn't call c.C is the master. We use C for control and assembler library functions to make it faster.

For a normal assembler code, we implement the code as we wish, we only call assembler functions. We must maintain registers, and implementing AI can be quite tedious, especially if iteration is required. It's ok if we have the exact algorithm nailed down and we know exactly the data we want and how the game flows. Ok to make AI for simple games. Hard work to make AI if the game is more complex.

Now, I started the game in assembler. Then I had a crazy idea. I would prototype the AI in C, get it right, optimise it (i.e. the minimum C required - removing unnecessary ifs and code that kind of thing. get the algorithm right and fast), then re-implement in z80 to make it faster and make the whole game assembler.

Here i present a method for writing the AI in C using pasmo assembler and SDCC C compiler.

The assembler code calls into the C code to update the AI passing it the player and enemy structures.

The C code can modify these structures and call back into the assembler code to trigger animations and check keys for example. The two are linked, the two talk to each other.
Ultimately the asm code has the control. C is the slave.

To do this:

1. the structures in asm code need to be replicated in the C code.
Bytes become unsigned chars and words become unsigned short.
Pointers are 16-bit so these are represented using normal pointers.

My "person" structure is replicated in this way.
When I set an anim I do it by index for simplicity.

2. minimise the amount of data you need to access from C, or the amount of data you need to access from asm. Keep C code contained, keep asm code contained, but allow them to talk to each other. If you need them to share data, you need to pass the data to them so they know about it and keep a reference to it.

3. I need to provide "glue" code. I need to
a) setup the stack to call the C code from asm (C is stack based)
b) process the stack and call the asm code. (asm is register based)

4. I need to provide a way for SDCC to link to my C code, pasmo to do my assembler code and merge it all together.

Here is how I did it. Example will follow when I've cleaned up the code.


Glue code  example (asm->C):

in asm:


ld ix,enemy2
call c_do_enemy_ai


glue code:

do_enemy_ai_c equ c_base+0

;; asm->c
c_do_enemy_ai:
push ix  ;; put it onto the stack for C
call do_enemy_ai_c ;; call C function via jumpblock
pop hl ;; clean up stack the same way C would do it.
ret


on the C side it looks like this:


void do_enemy_ai(person *p)
{
}


Glue code example (C->asm):

C looks like this:


// declare external function
extern void update_anim(person *p);


glue code (which is in the asm code):

push de,push ix are required because I modify them in my asm code, but C doesn't like me to modify these, so I need to protect them for C.

Without these stack has:

<return address>
<pointer to person>

C will clean up stack for us!

c_update_anim:
push ix
push de

ld hl,#6  ;; skip pushed ix, pushed de, and return address
add hl,sp
ld e,(hl) ;; pointer we want
inc hl
ld d,(hl)
push de
pop ix ;; want it in ix
;; call asm function
call update_anim
pop de ;; restore de,ix for C
pop ix
ret ;; back to C


Assembler code is like this:


update_anim:
bit anim_done,(ix+anim_flags)
.
.
.


To link them together I use jump tables. A jump table on the assembler side which is called by C.
A jump table on the C side which is called by asm. It's worth noting that if you have written any code in C which uses DLLs and you don't have a lib to link against you create the glue logic via the GetProcAddress (in win32), storing the address, calling via a typedef and telling C which "callee" method to use to ensure the stack is in a valid state after calling the function.


Linking them together (C->asm):

On the asm side I setup a jump table for each call C->asm each entry calls into the glue code:
I put this at a known location.


org &8000
jump_table:
jp c_update_anim
jp c_is_anim_done


In the C crt I did this:


jump_table=0x08000
_update_anim=jump_table+0
_is_anim_done=jump_table+3


and in the C code I put this:


extern void update_anim(person *p);
extern int is_anim_done(person *p);


The maintenance work is setting up the externs, adding the jumpblock info to the crt.
Note the _ when I declare in crt, C has this before all functions.

Now I could build the C code using SDCC. The linker is happy because I defined the jumptable in the crt. The C code calls into the jumpblock defined in the asm code, the glue logic and into asm.

Linking them together (asm->C):

On the C side, I had to implement a jump block too. I gave it a specific address. I did this by putting it into the crt and making it the first code so that setting the code location for C defined the start of the jumpblock. I could have put it into a section and defined the section location using the linker file.

I had to do the same as in C: I had to use equ to define the functions to call.

e.g.

update_player_ai equ c_code_base+0

I could now build each independently and they both generated binary files.

Joining them together:

I chose to build the C code first and generate a binary without a header.
I then used incbin to include this into my asm code.

e.g.


incbin "ccode.bin"


After all that, I can have an assembler engine, I can call into C and back again.

I can tell you that already, prototyping the AI in C is much quicker and easier. Yes i need to maintain these jump tables and I need to do code in both, but it's much easier to use.

:)





My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ronaldo

@arnoldemu: I find this idea interesting and useful in some contexts. It is an approach worth considering :)

I have created a simple example of this using CPCtelera. I wanted to see what it would be to create something similar. It is quite easy, and there is no need for creating "Glue code". As CPCtelera adds assembly and C files automatically to the project, the only thing to do is create the functions and call them.

You can check the complete code for the example at Github. It is tested and works. Summing it up:

       
  • main.s: the main program written in asm. It calls some CPCtelera functions and calls AI function written in C for 2 enemies. It also contains the data for 2 Person structures (2 enemies)
  • anim.s: It acts as the animation code in asm. Contains update_animation function that will be called from the AI code written in C.
  • ai.c: contains AI code, concretely do_enemy_ai_c function definition.
  • ai.h: definition of Person structure and declarations for do_enemy_ai_c and update_animation functions. update_animation is defined as extern because it its implemented in other file (anim.s)
And that's it. No glue code is required. Just declaring the names of the functions as global symbols for the compiler to know their existance on compilation time :).

arnoldemu

@ronaldo:

A good example, and it does show how to call C from asm, but it's different to how I am using it.

I am using a seperate assembler, pasmo which is not part of sdcc, they are not compatible and sdcc linker can't be used.

So I provided a method which allows them to talk without being compatible.

Your method is good for working within sdcc toolchain and shows how to call c from the sdcc assembler :)

You say you don't have glue code, which is not strictly true, in your asm code it is in-line and it's within the scope of sdcc.

With your main.s, is that not the normal crt main?

@others:

I may also be able to declare the function like this:


extern void update_animation(person *p) at 0x0800;

Notice the use of "at" to declare it's location, but I didn't see if this worked.
The 0x0800 is an example address.


My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ronaldo

Quote from: arnoldemu on 09:18, 25 May 15
I am using a seperate assembler, pasmo which is not part of sdcc, they are not compatible and sdcc linker can't be used.
So I provided a method which allows them to talk without being compatible.

Yep, I noticed, and it is really nice :). I thought that your main point was calling C from asm and vicecersa, but not using Pasmo. Pasmo is nice, but for me is a great drawback not being able to generate linkable code. You are doing kind of a "link" by hand. It's nice engineering to overcome this problem, buy I prefer ASZ80, which is included with SDCC, and generates linkable code.

Quote from: arnoldemu on 09:18, 25 May 15
You say you don't have glue code, which is not strictly true, in your asm code it is in-line and it's within the scope of sdcc.

I do not understand exactly what do you mean with this. Asm is not inline, they are separate files and compile independently, they are not within the scope of SDCC. They generate object files that are linked by SDCC linker lately. The 3 assembler and c files are independent and compile independently. Functions need to be declared as simbols because of this: if not, compiler won't know anything about them. Same as using C header files to declare functions that are compiled elsewhere.

Quote from: arnoldemu on 09:18, 25 May 15
With your main.s, is that not the normal crt main?

main.s is not a CRT main. I only tell the assembler which part should go to DATA area and which other to CODE area. CRT file is included by default by SDCC. If you wanted a different CRT file you should create it and add it in the compiling step. However, that is not required normally, unless you wanted to do something special.

For locating a function in a given place in memory in SDCC, you should use this

__at(0x800) extern void update_animation(person *p);

Why exactly would you want to place the update_animation code at an exact place in memory?

gerald

Quote from: ronaldo on 09:33, 25 May 15
I prefer ASZ80, which is included with SDCC, and generates linkable code.
Me too, but you get to get use to the syntax of IX/IY instruction .... and forget about using undocumented one (or put tthem as data), which is the main drawback I see.

Quote from: ronaldo on 09:33, 25 May 15
Why exactly would you want to place the update_animation code at an exact place in memory?
No idea for code, but for data you sometimes need that for optimisation. And here the linker is not really helpfull as it loose all the align directive. I hope they fix this one day.

ronaldo

Quote from: gerald on 09:47, 25 May 15
Me too, but you get to get use to the syntax of IX/IY instruction .... and forget about using undocumented one (or put tthem as data), which is the main drawback I see.
You can easily use undocumented opcodes as "mnemonic" by creating a macro with the hex opcodes.


;; Define ld_e_hy as macro
.macro ld_e_hy
   .db 0xfd, 0x5c  ;; ld e, hy
.endm
...
   ld_e_hy         ;; Do ld e, hy
...


This is a much readable than using opcodes directly :).

Quote from: gerald on 09:47, 25 May 15
No idea for code, but for data you sometimes need that for optimisation. And here the linker is not really helpfull as it loose all the align directive. I hope they fix this one day.

I asked Alan for this change some time ago, and he said he'll work on it. However, data aligned using C prefix __at() maintains its aligning. You can align any data you wanted if you insert it as C arrays, for instance. You can take a look at CPCtelera example on reproducing a song (the song must be put in a concrete location in memory).

The question for @arnoldemu was exactly about what he wants aligning code for. I do not know if it has any sense, other than aligning code that is not linkable, as the one he generates with Pasmo. For data, aligning is clear  :)

arnoldemu

Quote from: ronaldo on 09:33, 25 May 15
Yep, I noticed, and it is really nice :) . I thought that your main point was calling C from asm and vicecersa, but not using Pasmo. Pasmo is nice, but for me is a great drawback not being able to generate linkable code. You are doing kind of a "link" by hand. It's nice engineering to overcome this problem, buy I prefer ASZ80, which is included with SDCC, and generates linkable code.

I had two points:
1. calling c from asm (which you also show) :)
2. not using the assembler in the sdcc package

The  second point came from the fact I don't like asz80's assembler syntax, but also because I already had a lot of the code made with pasmo and it would take too long to convert to work with asz80. I like SDCC because it's not small c.

Quote from: ronaldo on 09:33, 25 May 15
I do not understand exactly what do you mean with this. Asm is not inline, they are separate files and compile independently, they are not within the scope of SDCC.
The inline referred to the code in main.s:






ld hl,#ai_person_1
push hl
call _do_enemy_ai_c
pop hl


Where I had this as a seperate function. I think of this as part of the glue logic so in this case it still exists.

Quote from: ronaldo on 09:33, 25 May 15
They generate object files that are linked by SDCC linker lately. The 3 assembler and c files are independent and compile independently. Functions need to be declared as simbols because of this: if not, compiler won't know anything about them. Same as using C header files to declare functions that are compiled elsewhere.

main.s is not a CRT main. I only tell the assembler which part should go to DATA area and which other to CODE area. CRT file is included by default by SDCC. If you wanted a different CRT file you should create it and add it in the compiling step. However, that is not required normally, unless you wanted to do something special.
I was confused about main.s because I didn't check the example fully. Sorry.

Quote from: ronaldo on 09:33, 25 May 15
For locating a function in a given place in memory in SDCC, you should use this

__at(0x800) extern void update_animation(person *p);

Why exactly would you want to place the update_animation code at an exact place in memory?
great. :)

Two reasons:

1.
Well, in my example it would replace the:


_update_animation=0x0800


and


.globl _update_animation


in my crt and it then makes the code more simple to maintain for my case.

2. I was thinking of a second case where:

a) You have a core program as one binary
b) you have one or more extra programs which are the code for a number of levels in a multi-load system.

Each level can contain it's own logic and call into the main core program to use common functions.

But then I realised it wouldn't work as well as I thought ;)

So my other idea was to call into code such as futureos (an example), but then you still need to provide the glue code (in a seperate s file as you have or as sdcc library code), so you couldn't declare the function like that for the final destination in futureos.


Please I hope I didn't offend:
- I have used sdcc before, I have called my library functions with it. I like it.
- I think the cpctelera system is great

My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ronaldo

Quote from: arnoldemu on 13:41, 25 May 15
The  second point came from the fact I don't like asz80's assembler syntax, but also because I already had a lot of the code made with pasmo and it would take too long to convert to work with asz80. I like SDCC because it's not small c.

It's perfectly fine, man :). Tools are there to use them, and different tools are okay for different styles, persons or even momments :).

Quote from: arnoldemu on 13:41, 25 May 15

ld hl,#ai_person_1
push hl
call _do_enemy_ai_c
pop hl

Where I had this as a seperate function. I think of this as part of the glue logic so in this case it still exists.
I was confused about main.s because I didn't check the example fully. Sorry.

Ok. I call this "interfacing" and I thought "glue logic" was more like jump tambles or direct symbol managing (like _update_animation = 0x800). Something you need to maintain apart from the always requiered interface code. There is no way to call a C function without calling it or using a calling convention :).

Quote from: arnoldemu on 13:41, 25 May 15
Each level can contain it's own logic and call into the main core program to use common functions.

Yeah, didn't thought of this. Could be a use for static code placement. There also tons of other forms to do it, but it could be a use. Nice one.

Quote from: arnoldemu on 13:41, 25 May 15
Please I hope I didn't offend:
- I have used sdcc before, I have called my library functions with it. I like it.
- I think the cpctelera system is great

Of course you don't offend, man :). Discussing and giving different ways of thinking is nice to share ideas. Sharing different points of view can lead us to confusion or missunderstanding, but there is no offence at all. Everything here is for enjoying our common interests :).

I was just trying to replicate your idea because I want CPCtelera to be able to do interesting things for all possible uses (step by step :)). As I told you, I think your idea of using asm code and some C code for difficult parts is a great idea: can have plenty of interesting uses. So I just was continuing with your thoughts and learning with you :).

AMSDOS

Absolutely anyone taking pot shots at Small-C is offensive  ;D
* Using the old Amstrad Languages :D * And create my own ;)
* Incorporating the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

arnoldemu

I am really enjoying coding the player and ai in c. I use it at work and it's natural for me to code this way. It has speeded up the iteration of the ai, and it helped me to recognise where code can be re-used for the player and enemy.

if I had been coding in asm this would not be so clear and I would have spent more time managing the registers.

If you're working with sdcc you will know this already. ;)

If you're making a game that you plan to be all in assembler, consider prototyping part of in c and then re-making that in assembler.

I can't say that keeping the AI in C will be fast enough, but I am sure I can explore ways to balance the cpu time more in C. (e.g. splitting enemy update over multiple frames).
My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ronaldo

Yep, for more theoretical and abstract things, C is much faster than ASM when talking about design and testing purposes. Moreover, programming in C is much more natural for everyone than programming in ASM. That's why I created CPCtelera: to ease the required time for people to start developing games :) .

In the end, you need to go down to ASM when you want to get the best from the machine. But starting from C can very much help clear the way down to that end :) .

And, man, show us your work! we are impatient to know what you are doing! :D

FloppySoftware

Quote from: AMSDOS on 11:56, 26 May 15
Absolutely anyone taking pot shots at Small-C is offensive  ;D

I do my CP/M & Amstrad PCW projects in MESCC, my own Small C versión  8) , and I'm very happy with it, because:

       
  • It's mine.
  • It's free.
  • It uses Z80ASMUK (or ZSM), another very good free tool.
  • It's CP/M native.
  • It allows to use inline assembler code.
  • It's expandable.
  • A lot more.
Small C (and its derivatives) it's not the very best tool in the world, but it has a lot of sense in Z80 / CP/M / Amstrad / ... development.
When I code for my Amstrad, I like to feel what I felt nearly 30 years ago (oh, by the way: happy birthday Amstrad PCW!).

:)
floppysoftware.es < NEW URL!!!
cpm-connections.blogspot.com.es

AMSDOS

Quote from: FloppySoftware on 17:06, 31 May 15

I do my CP/M & Amstrad PCW projects in MESCC, my own Small C versión  8) , and I'm very happy with it, because:

       
  • It's mine.
  • It's free.
  • It uses Z80ASMUK (or ZSM), another very good free tool.
  • It's CP/M native.
  • It allows to use inline assembler code.
  • It's expandable.
  • A lot more.
Small C (and its derivatives) it's not the very best tool in the world, but it has a lot of sense in Z80 / CP/M / Amstrad / ... development.
When I code for my Amstrad, I like to feel what I felt nearly 30 years ago (oh, by the way: happy birthday Amstrad PCW!).

:)


Small C only attracted me since Juergen Weber wrote CPCIOLIB as a means of porting programs to AMSDOS & with FIOLIB programs code be ported to FutureOS (as you're probably aware).


I haven't been able to implement non-system specific code for CP/M and GSX eludes me in the same manner C does. Hisoft Pascal 80 seems to be the best compiler for trying to incorporate GSX into it (unless you know better), though GSX (while is documented in the Pascal 80 Manual), has it's own complications.
* Using the old Amstrad Languages :D * And create my own ;)
* Incorporating the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

arnoldemu

Quote from: ronaldo on 15:50, 31 May 15
And, man, show us your work! we are impatient to know what you are doing! :D
I am working on the code for the beat em up sigh has been designing.
I don't want to show my progress yet because it's got bugs in it and it's not optimised yet.

I'm currently replicating renegade like game play and then I will extend that with the other moves sigh has designed.

See this thread:
Quick question on Renegade/Target Renegade graphics

My games. My Games
My website with coding examples: Unofficial Amstrad WWW Resource

ronaldo

Incredible and amazing work, man :). Hope to have great news from you soon :D

I expect to be able to announce #CPCRetroDev 2015 in next 2 weeks or so, may be you could consider entering the contest :D. There will be plenty of time, as submission will be open till late october :).

TFM

Quote from: AMSDOS on 11:45, 01 June 15
Small C only attracted me since Juergen Weber wrote CPCIOLIB as a means of porting programs to AMSDOS & with FIOLIB programs code be ported to FutureOS (as you're probably aware).


If someone wants to use CPCIOLIB (for native CPC firmware) or FIOLIB (for FutureOS) for any kind of C on CPC or PC (targeting CPC) please let me know and I will help as much as I can.  :)
TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

AMSDOS

Quote from: TFM on 20:52, 01 June 15

If someone wants to use CPCIOLIB (for native CPC firmware) or FIOLIB (for FutureOS) for any kind of C on CPC or PC (targeting CPC) please let me know and I will help as much as I can.  :)


Extending on CPCIOLIB is easy enough, disadvantage is code blows out from 4kb to 7-8kb for some simple program. If Small-C had some method of using RSXes it would interest me, I guess it could be something CALLed, which Small-C can talk to, not a huge fan of how Small-C communicates with Assembly routines. Just like something which improves management of Arrays.
* Using the old Amstrad Languages :D * And create my own ;)
* Incorporating the Firmware :P
* I also like to problem solve code in BASIC :)   * And type-in Type-Ins! :D

Home Computing Weekly Programs
Popular Computing Weekly Programs
Your Computer Programs
Updated Other Program Links on Profile Page (Update April 16/15 phew!)
Programs for Turbo Pascal 3

TFM

Quote from: AMSDOS on 11:35, 02 June 15

Extending on CPCIOLIB is easy enough, disadvantage is code blows out from 4kb to 7-8kb for some simple program. If Small-C had some method of using RSXes it would interest me, I guess it could be something CALLed, which Small-C can talk to, not a huge fan of how Small-C communicates with Assembly routines. Just like something which improves management of Arrays.


Let me try to remember ;-) ...
Well, for FIOLIB I did it the following way: As much stuff as possible gets moved into the expansion RAM. The usage of the first 64 KB is quite small that way. Also "large" functions can be added that way. This *could* be doable for the CPCIOLIB too if there is more than 128 KB of RAM (since CP/M Plus needs 128 KB already), not a solution for the "off the shelf" CPC, but hey: We got X-MEM.  :)
TFM of FutureSoft
Also visit the CPC and Plus users favorite OS: FutureOS - The Revolution on CPC6128 and 6128Plus

Powered by SMFPacks Menu Editor Mod