Jump to content

KickC Optimizing C-Compiler now supports Commander X16


Recommended Posts

I am glad you got everything to compile! How ASM and C works together can still use some work - both improvements of the features allowing for for inlining ASM easily and the documentation of how you use it.

6 hours ago, TomXP411 said:

The names I'm considering are: cxopen, cxgetc, cxgets, cxseek, cxputc, cxputs, and cxclose. This keeps the original fopen() and related function names free, in case someone decides to implement a standardized version of those functions in the future. 

I believe that is a good starting point! 

If they are common enough to be easily portable to the other commodore platforms maybe they should be called cbmopen, cbmgetc, ...
 

Edited by Jesper Gravgaard
Link to comment
Share on other sites

  • Super Administrators
50 minutes ago, Jesper Gravgaard said:

I am glad you got everything to compile! How ASM and C works together can still use some work - both improvements of the features allowing for for inlining ASM easily and the documentation of how you use it.

I believe that is a good starting point! 

If they are common enough to be easily portable to the other commodore platforms maybe they should be called cbmopen, cbmgetc, ...

Thanks. I'm moving forward, slowly but surely. My next step is to implement the command channel, so I can issue disk commands. Once that's done, I can start working on the UI code.

 

Link to comment
Share on other sites

  • Super Administrators

Just another question, and I know this one comes out of left field...

I was talking about a windowing library on another thread, and I am wondering if there's a way in KickC to generate an object file that does NOT optimize away unused functions. I'm thinking about how to create a function library that I can burn into ROM and make available to other programs later. Right now, your compiler seems to simply ignore functions that aren't called from another function, and when they are called, there's a lot of coupling at the assembly level. 

For example, if I have a routine named print(char* text), the text variable isn't always passed as a distinct value; instead, the compiler simply grabs the original declaration in the calling function and references that directly in the callee. I don't see evidence of using the stack or heap to marshal parameters, like in the C calling convention. 

It seems easy enough to devise my own marshaling subsystem, by using __address variables to directly access the R0-R15 byte pairs in zero page (the first 32 bytes are set aside for parameter passing.)  But I don't see a simple way to force the compiler to include a non-referenced function. 

I guess what I'm asking is... is there a way to force a function to be compiled, even if it's not used, and is there a way to force the C calling convention of putting parameters on the stack or a stack-like structure?

 

Link to comment
Share on other sites

17 hours ago, TomXP411 said:

I was talking about a windowing library on another thread, and I am wondering if there's a way in KickC to generate an object file that does NOT optimize away unused functions. I'm thinking about how to create a function library that I can burn into ROM and make available to other programs later. Right now, your compiler seems to simply ignore functions that aren't called from another function, and when they are called, there's a lot of coupling at the assembly level. 

It seems easy enough to devise my own marshaling subsystem, by using __address variables to directly access the R0-R15 byte pairs in zero page (the first 32 bytes are set aside for parameter passing.)  But I don't see a simple way to force the compiler to include a non-referenced function. 

I guess what I'm asking is... is there a way to force a function to be compiled, even if it's not used, and is there a way to force the C calling convention of putting parameters on the stack or a stack-like structure?

 

There is no direct support (like a keyword) for this at the moment. However, it can be achieved, with a little linker trick.

By creating a linker specification that declares a segment that is not included in the generated binary file then we can use the main() function to call all the ROM functions - but ensure that the main function itself is not included in the binary.

I have created an example C-file and linker configuration file that creates a ROM at $F000 with three functions.

The example also shows how to pass parameters and return values in three different ways:

- through the stack

- through zeropage 

- through registers (and/or zeropage)

You can see the code here: https://gitlab.com/camelot/kickc/-/tree/master/src/test/kc/examples/rom

Let me know if this makes sense, or if it leads to new questions.

Link to comment
Share on other sites

  • Super Administrators
5 hours ago, Jesper Gravgaard said:

There is no direct support (like a keyword) for this at the moment. However, it can be achieved, with a little linker trick.

By creating a linker specification that declares a segment that is not included in the generated binary file then we can use the main() function to call all the ROM functions - but ensure that the main function itself is not included in the binary.

I have created an example C-file and linker configuration file that creates a ROM at $F000 with three functions.

The example also shows how to pass parameters and return values in three different ways:

- through the stack

- through zeropage 

- through registers (and/or zeropage)

You can see the code here: https://gitlab.com/camelot/kickc/-/tree/master/src/test/kc/examples/rom

Let me know if this makes sense, or if it leads to new questions.

That's perfect. I had come to a similar conclusion, but was unsure how to prevent main() from being included or executed. 

So, looking at the calls in main, where you call each function twice with two different parameters -  I suspect that's to fool the optimizer into using proper parameter passing, rather than converting the parameter into a shared variable?

 

Thanks!

 

Edited by TomXP411
Link to comment
Share on other sites

  • Super Administrators
On 1/18/2021 at 11:24 PM, Jesper Gravgaard said:

I am glad you got everything to compile! How ASM and C works together can still use some work - both improvements of the features allowing for for inlining ASM easily and the documentation of how you use it.

I believe that is a good starting point! 

If they are common enough to be easily portable to the other commodore platforms maybe they should be called cbmopen, cbmgetc, ...

I took a look at CC65 Tuesday evening, and I found their versions of those functions. 

The wrapper functions that simply invoke the KERNAL call are prefixed with cbm_k_, and the aggregate functions (ie: "open") are prefixed with cbm_

So that's the convention I will adopt. It's a little verbose and awkward to type, but I'd like to be able to compile the same code on cc65 or KickC without needing to make any changes. 

 

  • Like 3
Link to comment
Share on other sites

  • 4 weeks later...

Guys ... You want to use something very important in the compiler!

The following directive is mandatory to be used:

#pragma zp_reserve(0x80..0xA8)
 
This directive ensures that you are not using zero page registers which are used by the X16, but only when not using any BASIC or floating point!
When you use BASIC or floating point, use this pragma:
 
#pragma zp_reserve(0x80..0xFF)
 
See the following extract from the manual:
 

This is the allocation of fixed RAM in the KERNAL/BASIC environment.

Addresses Description
$0000-$007F User zero page
$0080-$00FF KERNAL and BASIC zero page variables
$0100-$01FF CPU stack
$0200-$03FF KERNAL and BASIC variables, vectors
$0400-$07FF Available for machine code programs or custom data storage
$0800-$9EFF BASIC program/variables; available to the user

The following zero page locations are completely unused by KERNAL/BASIC/FPLIB and are available to the user:

Addresses
$0000-$007F

In a machine language application that only uses KERNAL (no BASIC or floating point), the following zero page locations are also available:

Addresses
$00A9-$00FF

This is the allocation of banked RAM in the KERNAL/BASIC environment.

Bank Description
0 Used for KERNAL/CBDOS variables and buffers
1-255 Available to the user

(On systems with only 512 KB RAM, banks 64-255 are unavailable.)

During startup, the KERNAL activates RAM bank 1 as the default for the user.

 
When your program is growing, (like mine), it will consume a lot of zeropage registers.
Just ensure that you use the #pragma at the start of your program, or the kernal will start consuming zero page addresses in the 80-FF space,
which basically will erase local variables you've setup, with unexpected results. I had this problem and just could not understand why.
(when my program got bigger i only got into this problem).
But i started to debug the zeropage and saw that the kernal was overwriting some of the local variables (file I/O kernal routines), which were allocated at zero page $8D ...
So, hopefully you'll see this message and you'll take note!

It took me 3 weeks to get to this understanding ... I also have seen that the vera card lib additions are using a lot of zero page addresses, so i need to recode some of the variables.
 
 
Edited by svenvandevelde
Link to comment
Share on other sites

Another note ... global variables should be minimized ...

When declaring global variables of the byte or char type, kickc will allocate these variables in the zero page space, which is a good technique to allow for speed execution.

However ... when having a lot of global variables, it seems that the zero page range fills up quickly! So minimize the amount of local variables. I see this now in my implementation of conio for the X16, which I'm now reworking to fix this issue.

Conio is declaring too many global variables. Instead, I will solve this with a global struct {} that will occupy just one zero page register ...

Link to comment
Share on other sites

1 hour ago, svenvandevelde said:

The following zero page locations are completely unused by KERNAL/BASIC/FPLIB and are available to the user:

Addresses
$0000-$007F

Note that this is only the case in the mainstream emulator. The hardware uses $00 and $01 for bank switching, and there is a branch of the emulator that does this as well, and eventually that will be merged into a release build. So, you really only have $02-$7F, but if you plan on using any Kernal subroutines that use the zero page 16-bit "registers", you want to exclude those too. That really brings your availabe addresses to $22-$7F. For maximum reusability, you'll want to keep within that part of the zero page.

Link to comment
Share on other sites

1 hour ago, SlithyMatt said:

Note that this is only the case in the mainstream emulator. The hardware uses $00 and $01 for bank switching, and there is a branch of the emulator that does this as well, and eventually that will be merged into a release build. So, you really only have $02-$7F, but if you plan on using any Kernal subroutines that use the zero page 16-bit "registers", you want to exclude those too. That really brings your availabe addresses to $22-$7F. For maximum reusability, you'll want to keep within that part of the zero page.

OK! Thank you, that is very useful information. That should be added in the documentation actually!

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

Please review our Terms of Use