0

I have a function that should be callable with the following syntax:

void __fastcall__ initSIDplayer1(uint8_t a);

The __fastcall__ modifier here means that the argument is passed in the Accu instead of the software stack. The address is given as 16-bit address, for example, 0x1000 for a SID player file that is loaded with the.

I have used the following line to define the function with the specified address:

static void  __fastcall__ (*initSIDplayer1)(uint8_t)=(const void *)0x1000;

The approach works, but the compiler generates a lot of code in comparison to the statically defined function initSIDplayer1:

;
; code generated for initSIDplayer1(1);
;
        lda     #$61
        jsr     _initSIDplayer
;
; code generated for initSIDplayer2('a');
;
        lda     _initSIDplayer2
        ldx     _initSIDplayer2+1
        jsr     pushax
        lda     #$61
        pha
        ldy     #$00
        lda     (sp),y
        sta     jmpvec+1
        iny
        lda     (sp),y
        sta     jmpvec+2
        pla
        jsr     jmpvec
        jsr     incsp2

The generated code for initSIDplayer2 writes the target address into memory (using two stack operations) and then jumps to that memory address-1 where it executes another jmp. This is a very flexible, but slow and cumbersome approach.

How do I need to write the function definition with the given address so that the compiler translates it into a simple jsr ADDR call like with initSIDplayer1?

I know that this can be also done with inline assembler or an external assembler file, but I would be interested in the C code syntax.

For completeness, this is the minimum test program:

#include <stdint.h>

void __fastcall__ initSIDplayer1(uint8_t a);

static void  __fastcall__ (*initSIDplayer2)(uint8_t)=(const void *)0xFFD2;

void main() {
  initSIDplayer1('a');
  initSIDplayer2('a');
}

I compiled it with cc65 function.c which generates an .s file.

5
  • Err, you get a .o file? AFAIR cc65 only delivers a .s (to be later assembled using ca65, wouldn't it? Did you maybe use cl65 instead?
    – Raffzahn
    Jan 18 at 0:10
  • You're right. The .o file comes from the assembly step. Fixed it in the text, thx.
    – Peter B.
    Jan 18 at 0:14
  • 1
    Just plunged it into the compiler. It might be helpful to show that the function pointer is really stored as pointer within the initialized data segment (.segment "DATA") as that's why it builds the jump table from before jumping. It essentially ignores the const part. Now we only need to look why.
    – Raffzahn
    Jan 18 at 0:22
  • Sorry just curious, where is fastcall defined? I haven't seen this type of modifier before. Is it a standard C thing?
    – dashnick
    Jan 18 at 2:49
  • 1
    Fastcall is a Feature of cc65 a compiler for the 6502 microprocessor. The 6502 does only have a small hardware stack of 256 bytes, so the C implementation makes a software stack to handle function parameters. Operating this stack for call parameters takes a lot of code and time, which is why the fastcall modifier allows to define a function that gets its last parameter passed in the processor registers instead.
    – Peter B.
    Jan 18 at 9:33
4

Preface: I'm not the world greatest C expert - not at least as I dislike C quite a lot :))


What Happenes

The general issue here is that CC65 puts the address into a pointer variable, visible when looking at the generated source:

.segment    "DATA"
_myfunction2:
    .word   $FFD2

Later that variable gets loaded onto the stack - like CC65 does with every 16 bit value - and from there moved to a prepared jump which then is taken. Kind of ok-ish with variable function pointers, less cool with a constant address.

Solution in C

No idea. Personally I'd write

static void __fastcall__ (* const myfunction2)(uint8_t a) = (const void *) 0xFFD2;

as myfunction2 is a constant, but that should not make a difference and true, it doesn't :(

I played a bit around, but couldn't see any way to change that behaviour. Even with using implied type conversion (adds only a warning) didn't change the result. The same for leaving out the non standard __fastcall__ attribute (A is used anywayfor the first parameter).

What I'd do:

The main problem here is that issues like external function addresses, and that's what a ROM function is, aren't really a things C cares for. That's a job for the linker, isn't it? So why not adding an external reference and have the linker resolve it?

In this case, the function definition would be simply

void __fastcall__ myfunction2(uint8_t a);

The address could now be delivered to the linker using parameter or with its configuration file. Then again, I think it's safe to assume that there will be more than just one entry point to ROM than just this. Going by the address it's as well quite likely we have a C64 at hand, right? So lets add a collection of entry points:

; C64_Kernal_Calls.s:
;
; Commodore-compatibles Kernal functions

CHRIN  :=       $FFCF
CHROUT :=       $FFD2

       .export  CHRIN
       .export  CHROUT

Of course this can be beautified using a macro:

; C64_Kernal_Calls.s:
;
; Commodore-compatibles Kernal functions

.macro  entrypoint LBL,ADR
LBL     :=      ADR
        .export ADR
.endmacro

      entrypoint CHRIN, $FFCF
      entrypoint CHROUT,$FFD2

Calling that from C now works rather simple:

void __fastcall__ CHROUT(uint8_t a);

// ...

    CHROUT('a');

Going that road´provides, beside solving the issue, considerable better structuring than embedding addresses into random source files, doesn't it?

Always program as if you're not writing another Hello_World but the next GEOS.

3
  • Thanks for the solution using a seperate Assembler file as a module to contain the call addresses. A bit of a downside is that we need to define a spearate .s file for getting the address in. While this is definitely a good idea for a collection of Kernal calls like in your example, when you think of an externally generated program (like a SID music play routine) that needs to be called, I would prefer to add it directly in a C program instead of writing an empty Assembler file. So I think the question what is a better structure depends on the particular application.
    – Peter B.
    Jan 18 at 9:47
  • 1
    In your example, you need to export the labels from the Assembler file with a leading underscore (or define them with an underscore in the first place) to match the respective label in C. For example, .export _CHROUT := CHROUT
    – Peter B.
    Jan 18 at 10:27
  • @PeterB. Well, even with a small project I'd still use a separate file as it simply is an external code to be called. Also, Such a file would be a standard file for the platform used, collecting all kinds of entry points and reused every time. For assembly projects the same way as for C. In fact, I wouldn't be surprised if CC65 doesn't already include something similar.
    – Raffzahn
    Jan 18 at 16:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.