Author Topic: [cpctelera 1.4][sdcc 3.5] how to use assembly code inside C-function  (Read 774 times)

0 Members and 1 Guest are viewing this topic.

Offline gryken

  • CPC464
  • **
  • Posts: 5
  • Country: fr
  • Liked: 2
Hello,

I'm trying to find out how to embed assembly code, inside c function.

I've found A LOT examples :

for instance :
- http://www.cpcmania.com/Docs/Programming/Programming.htm
- http://cpcwiki.eu/index.php/SDCC_and_CPC
- http://sitedesteph.free.fr/cpc/lec/index.php?page=sdcc&cour=
- http://cpcrulez.fr/coding-crossdev_sdcc-01-developper_en_C_pour_CPC.htm

I would like to get c parameters, inside assembly code.
And send back, a return value.

I'm completely lost
- sometimes, they use stack
- sometimes, they use ix, directly

But, these examples seem to to be outdated for SDCC 3.5 (inside CPCTELERA 1.4)...

- Since CPCTELERA 1.4, there no need to use a custom crt0.s ? (neither putchar_s for printf, stdio ?)
- Is compilation option  "--fno-omit-frame-pointer" the default one ? mandatory one ?
- When i tried to handle input parameters with (ix), the program crash every time,
- Is return value using HL register ?

Thanks for your help

Code example :

Code: [Select]

#include <cpctelera.h>
#include <stdio.h>

/* function prototypes  */
void choisir_mode(unsigned char mode);
void wait_key(void);

/* end prototypes */

const unsigned char message1[] = "current_mode : %d";

int main(void) {
    unsigned char m;
  /* loop with 3 graphics mode (from 0 et 2) */
  for (m=0;m<=2;m++)
  {
 
  choisir_mode(m);

   printf(message1,m);

   wait_key();
 
  }
   
   return(0);
}


/*-----------------------------------------------------------------------*/

/*   attente pression touche            */
/* no entry param ; no return param */
void wait_key(void)
{
  __asm
   call 0xBB18
  __endasm;
}

/*-----------------------------------------------------------------------*/

/*   one parameter  (with stack SP)   (not ix) */
/* no return value */
void choisir_mode(unsigned char mode)   /* firmware SCR_SET_MODE */
{
 
  __asm
    ld    hl,#2
    add    hl,sp
    ld    a,(hl)
    call    #0xBC0E   ; SCR_SET_MODE
  __endasm;
   
}

Offline SRS

  • Supporter
  • 464 Plus
  • *
  • Posts: 427
  • Country: de
  • Schneider CPC464 - what else ?
  • Liked: 379
cpcmania is a real good source to learn ASM in SDCC :)

You need to remeber SDCC ASM is not MAXAM style, so:

CALL 0XBB18 has top be CALL #0xBB1



Parameters are given at (IX) with SDCC,

so for your example
Code: [Select]
void choisir_mode(unsigned char mode)   /* firmware SCR_SET_MODE */
{
__asm

ld a, 4 (IX) ; gets the byte from "mode"
call    #0xBC0E   ; SCR_SET_MODE
  __endasm;
}


Your program crashes imho from changing the Stackpointer in your mode-routine.



You can only use PRINTF from cpctelera programs (and your other two examples) if you let firmware enabled. with fimrware enabled, a lot of the cpctelera inside functions won't work.

Offline ronaldo

  • Dev
  • 6128 Plus
  • *****
  • Posts: 527
  • Country: es
    • Fremos Blog
  • Liked: 656
Hi @gryken ,

Many questions in your post, and not easy answers for all of them. I'll try to give you some clues for you to continue learning. Let's give it a go,
I would like to get c parameters, inside assembly code.
And send back, a return value.
Communication between function callers and callees is performed by "calling conventions". You may look for this in google and will find many interesting references. These conventions are sets of strict rules on where and how to put/retrieve parameters.

Most standard one is to put parameters on the stack. Before the call is performed, the caller function pushes parameters on to the stack in reverse order. Then, when the call is performed, the return address is also pushed to the top of the stack. Then, parameters are available there for the callee. The callee may retrieve them they way it wants, provided it follows these rules: 1) it must leave the stack pointer at the same location it was, and 2) the return address must be at the top of the stack.

Then, after returning from the function, the caller will automatically pop the parameters from the stack again, leaving it as it was previous to the call. Think of it for a moment: if you leave the stack pointer in a different place, this would have exploding consequences: all the local variables and return addresses are kept in the stack. If it gets changed, variables will get unexpected values and return addresses could point to any random place in memory. This is way is so important to follow the rules of the "calling convention" strictly.

With respect to the return value, if you are returning a byte, it must be held in L register; if it is a word, in HL and, if it is a double-word, in the pair DE:HL.

Then, to retrieve parameters from the stack, there are 2 main methods: 1) popping them and then pushing again to leave the stack unchanged, 2) Pointing to the stack with any index register (HL, IX or IY) and copying the values to registers. Just one advice: if you use IX or IY in your own assembly routines be sure to save and restore their values. SDCC uses IX and IY to access local variables and won't notice it changed, leading your program to undefined behaviour.

I'm completely lost
- sometimes, they use stack
- sometimes, they use ix, directly

But, these examples seem to to be outdated for SDCC 3.5 (inside CPCTELERA 1.4)...
In fact, CPCMania tutorials and others are outdated. In old versions of the SDCC compiler, IX was synchronized with SP on every call. That let programmers get parameters by simple accessing the stack using IX as index. This was deprecated and removed because it was unnecesarily CPU consuming. Now, if you want IX to point to the stack, you can make it on your own, at the start of your functions.

- Since CPCTELERA 1.4, there no need to use a custom crt0.s ? (neither putchar_s for printf, stdio ?)
All these things are part of what CPCtelera automatizes. The main idea behind CPCtelera is to save time to the developer: no need to continue doing "bureaucratic" stuff when all you want is to program. You may do these things if you really need them, but is automatized for you in the 99% of the cases. You just create your project, write your code and compile.

Also, CPCtelera has its own version of putchar, so printf works in CPCtelera. If you navigate the examples folder, you'll find many of them. You can also find examples written in assembler.

- Is compilation option  "--fno-omit-frame-pointer" the default one ? mandatory one ?
- When i tried to handle input parameters with (ix), the program crash every time,
- Is return value using HL register ?
This option is deprecated and its use should be avoided at all costs. Its implementation under SDCC has bugs that won't be fixed (because it's deprecated) and it caused generated code to be bigger and slower. It was used by some developers for comfort: it caused SDCC not to use IX register for local variables, so that it could be used to get stack parameters without the need for saving/restoring it. That looks like a gain, but it was instead a big loss, as the cost of having worse code generated was much much greater than the comfort gained.

This and previous comments explain why your code crashes when you try to use IX to get parameters. It won't be pointing to the stack unless you make it point there by yourself.

As previously said, yes, L, HL and DE:HL are used for returning values, depending on its type.

With respect to writing inline assembly code, I personally recommend you not to do it. Using CPCtelera it will be much more comfortable for you to write your assembly code on separate assembly .s files. CPCtelera will automatically compile .s files under your src/ folder. So, if you wanted to create a simple function like in your example, you may do it this way.

main.c:
Code: [Select]
#include <cpctelera.h>
#include <stdio.h>

// Please, prefer always single-line comments to multi-line

// function prototypes
// ( Required for the compiler to know these functions exists.
//   As they are defined in other file, the linker will link
//   them after compilation ends )
//
void choisir_mode(u8 mode);  // u8 = unsigned char in CPCtelera
void wait_key(void);

// Global constant ( This works well because it is a constant.
//   Remember that global variables won't get initialized
//   because of SDCC's embedded behaviour )
//
const unsigned char message1[] = "current_mode : %d";

int main(void) {
   // CPCtelera typedefs are more comfortable to
   // work with the exact byte-sizing you desire
   u8 m;    // u8 stands for unsigned 8-bits. Same as unsigned char m;
 
   // loop with 3 graphics mode (from 0 et 2)
   for (m=0;m<=2;m++) {
      choisir_mode(m);
     
      // You don't actually need message1 to be declared constant
      // You may write it directly inside printf. Same effect.
      printf(message1,m); 
     
      wait_key();
   }
   
   // Parentheses are not required here
   return(0);
}

firmware_funcs.s:
Code: [Select]
;;
;; Choisir_mode function
;; input: 1 parameter (mode) 8-bits
;; output: nothing
;;
choisir_mode::       ;; 2 colons make it a global symbol
   ld    hl,#2     ;; /  Make HL point to SP+2
   add   hl,sp     ;; \  (as the first 2 bytes are the return address)
   
   ld    a,(hl)    ;; A = mode to be set (got from the stack)
   call  #0xBC0E   ;; Firmware function SCR_SET_MODE

   ret             ;; Return

;;
;; WaitKey function
;; input:  nothing
;; output: nothing
;;
wait_key::
   call  #0xBB18   ;; Call firmware function.

   ret             ;; Return

If you compile these two files under your CPCtelera project, it should work (disclaimer: untested code. Didn't compile it myself).

Last, but not least, CPCtelera is free software. All library functions are written in assembler and you may read sources. All CPCtelera sources have been written with tons of comments to help those wanting to learn from them. You may find the code interesting for you. The easiest way to navigate and read its code is to read it directly in Github.
« Last Edit: 19:37, 10 January 17 by ronaldo »

Offline SRS

  • Supporter
  • 464 Plus
  • *
  • Posts: 427
  • Country: de
  • Schneider CPC464 - what else ?
  • Liked: 379
ah well - my knowledge is deprecated - sigh :)

One hint ...

wait_key::
   call  #0xBB18   ;; Call firmware function.

   ret             ;; Return

Faster and smaller: JP #0xBB18 and no RET ;)

Offline Docent

  • CPC664
  • ***
  • Posts: 80
  • Country: pl
  • Liked: 56
Hello,

I'm trying to find out how to embed assembly code, inside c function.

I've found A LOT examples :

for instance :
- http://www.cpcmania.com/Docs/Programming/Programming.htm
- http://cpcwiki.eu/index.php/SDCC_and_CPC
- http://sitedesteph.free.fr/cpc/lec/index.php?page=sdcc&cour=
- http://cpcrulez.fr/coding-crossdev_sdcc-01-developper_en_C_pour_CPC.htm

I would like to get c parameters, inside assembly code.
And send back, a return value.

I'm completely lost
- sometimes, they use stack
- sometimes, they use ix, directly

But, these examples seem to to be outdated for SDCC 3.5 (inside CPCTELERA 1.4)...

- Since CPCTELERA 1.4, there no need to use a custom crt0.s ? (neither putchar_s for printf, stdio ?)
- Is compilation option  "--fno-omit-frame-pointer" the default one ? mandatory one ?
- When i tried to handle input parameters with (ix), the program crash every time,
- Is return value using HL register ?

Thanks for your help


SDCC supports a number of calling convention methods for parameters passing.
Basically, you have four options:
Default calling convention
All parameters are passed on the stack, in right-to-left order (the last parameter is first put on the stack etc).
Return values are passed in registers. 8-bit return values should be passed in L, 16-bit values in HL, 32-bit values in DEHL.

smallc calling convention
Forced by putting keyword __smallc after function declaration
All parameters are passed on the stack, in left-to-right order.
Return values are passed in registers. 8-bit return values should be passed in L, 16-bit values in HL, 32-bit values in DEHL.

z88dk fastcall calling convention
Forced by putting keyword __z88dk_fastcall after function declaration.
Supports only one parameter of max 32 bits, passed in registers.
8-bit values should be passed in L, 16-bit values in HL, 32-bit values in DEHL.
Return values are passed in registers. 8-bit return values should be passed in L, 16-bit values in HL, 32-bit values in DEHL.

z88dk callee convention
Forced by keyword __z88dk_callee after function declaration.
Parameters are passed on the stack, but the stack is not adjusted for the parameters after the call. Can be combined with other keywords, for eg.__smallc.
Return values are passed in registers. 8-bit return values should be passed in L, 16-bit values in HL, 32-bit values in DEHL.

Except default option, all other calling conventions are implemented to support existing libraries or code.

SDCC uses IX register as a frame pointer. Frame pointer is a pointer to space for function's local variables, allocated on the stack. After storing used registers on stack, pushing arguments on stack and setting frame pointer, SDCC will call a function and then restore stack pointer and saved registers.

This behavior can be adjusted with compile options listed below or function attributes like __z88dk_callee.

--callee-saves-bc compile option tells the compiler not to save BC on the stack before calling a function. If you do not use BC in your asm function, it can save time and size required to store and restore BC from the stack for each function call.

--fomit-frame-pointer compile option will cause that frame pointer will be omitted when the function uses no local variables. As per SDCC documentation, for z80 code generator if this option is used, frame pointer will be omitted for all functions.

--fno-omit-frame-pointer will never omit the frame pointer, ie. frame pointer will be always set up for each function even without local variables. This will generate some overhead in prologue and epilogue of each function, but guarantee that IX will always point to frame pointer and local vars can be accessed via IX+n addressing.

This also explains why you saw both stack and IX methods of referencing function parameters in various examples - some of these examples used --fno-omit-frame-pointer and accessed parameters through IX register, while others just popped them from the stack.

BTW: If you want full control of your asm function, you can use the __naked function attribute. It prevents the compiler from generating prologue and epilogue code for that function.
This gives you full control of the function code, but you are fully responsible for saving any registers that may need to be preserved, not forgetting of returning via ret :)
In case of call BC0E - AF, BC, DE and HL are destroyed, so you should preserve them just to be on the safe side.
« Last Edit: 01:18, 11 January 17 by Docent »

Offline Sykobee (Briggsy)

  • 6128 Plus
  • ******
  • Posts: 605
  • Country: gb
  • Liked: 187
Are there some performance metrics for the different types?


I presume that _naked would be like writing the CPC firmware functions which do the same? Low-level, lots of embedded assembler, not-portable, C for structure, type coding?

Offline gryken

  • CPC464
  • **
  • Posts: 5
  • Country: fr
  • Liked: 2
Hi,

Thanks everybody, for all these precious answers !! :)

Offline Docent

  • CPC664
  • ***
  • Posts: 80
  • Country: pl
  • Liked: 56
Are there some performance metrics for the different types?


I presume that _naked would be like writing the CPC firmware functions which do the same? Low-level, lots of embedded assembler, not-portable, C for structure, type coding?

Using __naked will be always nonportable, because there is no guarantee that other compiler supports it, and because it tells SDCC compiler to skip any function assembly prologue and epilogue it usually adds to each function. It basically forces you to provide the body of such function in assembly.
Obviously, using __naked will be faster because it will eliminate additional code added by the compiler.
--fno-omit-frame-pointer adds additional call at the beginning of each function to setup ix register and a pop ix at the end.
Speedwise, the combination of __naked and __z88dk_fastcall for functions with one parameter and __z88dk_callee for others, together with --fomit-frame-pointer and (if bc is not used) --callee-saves-bc will give you probably the best results.


Offline ronaldo

  • Dev
  • 6128 Plus
  • *****
  • Posts: 527
  • Country: es
    • Fremos Blog
  • Liked: 656
Obviously, using __naked will be faster because it will eliminate additional code added by the compiler.
--fno-omit-frame-pointer adds additional call at the beginning of each function to setup ix register and a pop ix at the end.
Speedwise, the combination of __naked and __z88dk_fastcall for functions with one parameter and __z88dk_callee for others, together with --fomit-frame-pointer and (if bc is not used) --callee-saves-bc will give you probably the best results.
As I said before, --fomit-frame-pointer and --fno-omit-frame-pointer are deprecated for Z80, have bugs and are nt  supported anymore by SDCC developers since 3.0. They are also not recommended, as the overall code that SDCC generates with them is worse (see this analysis).

The best recommendation for including asm functions in a CPCtelera project is writting them in their own .s file (not using __naked inline functions) and using the best calling convention for the parameters being passed (__z88dk_fastcall or __z88dk_callee are usually better than standard call, but make sure to understand calling conventions well before using them). No need to use any other hint to the compiler, as the compiler won't assume anything about your function. It will just save what it is using, set up the call and make it. Better not to use --callee-saves-bc, as it forces callees to always save BC, even when it's not required. SDCC will take care of BC for you only when required, same as other standard registers, if you don't pass this option to the compiler.

Then, if you make use of IX or IY and change them in your functions, be sure to save them to prevent side-effects, as SDCC may use them for accessing parameters on the stack. That's better approach than asking SDCC not to use them or set up them for you.
« Last Edit: 14:37, 15 January 17 by ronaldo »

Offline SRS

  • Supporter
  • 464 Plus
  • *
  • Posts: 427
  • Country: de
  • Schneider CPC464 - what else ?
  • Liked: 379
Is using Z88DK (and its enhanced libraries or optimized SDCC version) planned for version 1.5 ? seems to generate faster / better code nowadays.