Jump to content
Jesper Gravgaard

KickC Optimizing C-Compiler now supports Commander X16

Recommended Posts

KickC is a C-compiler for 6502-based platforms creating optimized and readable assembler code. 

The newest version 0.8.5 adds support for developing for the Commander X16 platform.

The compiler includes header-files and linker-files for the chipset of Commander X16. Also included is veralib.h and a conio.h implementation contributed by @svenvandevelde.

It also includes some example-programs that work in the emulator (and hopefully on the real platform). Below you can see a bit of the included sprites.c example program.

You can get it here: https://gitlab.com/camelot/kickc/-/releases

PS. I am the author of KickC.

Screenshot 2021-01-16 at 00.56.52.png

Screenshot 2021-01-16 at 00.57.33.png

Edited by Jesper Gravgaard
  • Like 7
  • Thanks 1

Share this post


Link to post
Share on other sites

@Jesper Gravgaard Hi Jesper,

Congratulations with your compiler. This is a truly amazing work. Really!

Your compiler does a trade-off between efficiency (and it does it very, very well), and functionality.

It is also very transparent, the assembly code generated is readable, which makes it very useful for debugging or learning.

Kudos!

Sven

  • Like 1

Share this post


Link to post
Share on other sites

Hi, @Jesper Gravgaard

I'm trying out Kick C (I appreciate it doesn't require CygWin just to compile some c code), but I can't get the examples to compile for the Commander X16. 

I'm getting this:

image.thumb.png.b23cd4b62ff8bc75037fbeeea121de6f.png

 

I tried adding veralib.c to the command line, and I get Redefinition of variable: vera_layer_config

The error on the initial compile is telling me there's a library or object file missing, but I can't figure out how to add it on the command line. Do I need to compile something else first to create the Commander X16 library file? What am I missing?

(On another note: the program compiled and ran perfectly on VICE without any errors. Thanks!)

** edit: figured it out. I had to add

#include <cx16.h>
#include <veralib.h>

To the program. Once I did that, everything started up. Now I just have to remember my old school C and console commands. 

Thanks! This works great!

 

Edited by TomXP411
  • Like 1

Share this post


Link to post
Share on other sites

Hi @TomXP411

Thank you for trying out KickC. I hope you like it, and would appreciate any feedback from you to improve it!

8 minutes ago, TomXP411 said:

The error on the initial compile is telling me there's a library or object file missing, but I can't figure out how to add it on the command line. Do I need to compile something else first to create the Commander X16 library file? What am I missing?

I have found out why you are getting the "Called procedure not found" error. It is an embarrassing forgotten include. If you add the following line at the top of lib/cx16-conio.c. then helloworld.c will compile as it should

#include <veralib.h>

In the other included CX16 example programs veralib.h is included in the main C-file, so the compiler is happy. 

Let me know, if this works for you. I will of course include the fix in the next release.

  • Like 1

Share this post


Link to post
Share on other sites
51 minutes ago, Jesper Gravgaard said:

Hi @TomXP411

Thank you for trying out KickC. I hope you like it, and would appreciate any feedback from you to improve it!

I have found out why you are getting the "Called procedure not found" error. It is an embarrassing forgotten include. If you add the following line at the top of lib/cx16-conio.c. then helloworld.c will compile as it should

#include <veralib.h>

In the other included CX16 example programs veralib.h is included in the main C-file, so the compiler is happy. 

Let me know, if this works for you. I will of course include the fix in the next release.

Yep, that worked! 

There are a couple of other minor nags, but they are not deal breakers: 

I'd like to be able to include -scale 2 in the command line to start the emulator, but when I tried to add that to the platform files, the emulator just didn't start at all... 

 printf seems to use conio.h, which appears to talk directly to VERA. This works fine when you need performance, but it loses a lot of CHROUT's functinality. No console sequences (ie: cursor movement, etc) and text can't be redirected to an output channel for saving to disk, printing, or RS-232. 

It might be nice if printf() used CHROUT to write to the screen, and cprintf used memory I/O to talk to the screen. This would let printf work as expected with PETSCII console sequences, and it would keep the BASIC screen in sync with the text from a C program. This is also, more or less, the standard behavior on PC compilers, and would make the transition easier for people used to that method of doing things. 

So my thought was to change the existing printf() definitions, with the VERA functions, to "cprintf" and introduce a new version of the printf functions that uses CHROUT. While this is slower, it will allow printf to be re-directed to a printer, to RS-232, or to a file - all things you can't do with cprintf().  I'd be happy to do the work; I need to brush up on my C, anyway - it's been a while since I've done any real work in ANSI C.

Also... dumb question, but I can't find any file I/O commands. Am I missing something, or do I need to implement the basic file I/O functions?

 

 

Edited by TomXP411

Share this post


Link to post
Share on other sites

@TomXP411 "-scale 2" works for me, when I add it to /target/cx16.tgt . Just make sure you do not add it at the end of the command string. This is because the "-prg" option at the end of the string expects the filename of the generated PRG-file to follow immediately after. 

This is my working emulator-setting  "emulator": "x16emu -debug -scale 2 -run -prg",

As for printf() you are completely right that it is implemented on top of conio.h. Printf itself calls only cputc and cputs.

If you would contribute a conio.h implementation written on top of CHROUT that would be awesome!

 I will gladly merge it into KickC's CX16 library.

I will have to read up a bit more on printf(), cprintf() and best practice on other platforms before deciding exactly how to do the merge. It might be the optimal solution to have some way of specifying which implementation to use. Maybe some #define or a #pragma.

 

 

Share this post


Link to post
Share on other sites
38 minutes ago, TomXP411 said:

Also... dumb question, but I can't find any file I/O commands. Am I missing something, or do I need to implement the basic file I/O functions?

I am afraid there is no basic file I/O functions currently included the library. 

I do not know the CX16 well enough yet. What type of storage does it support and  how does it read/write files?

 

Share this post


Link to post
Share on other sites
1 hour ago, Jesper Gravgaard said:

I am afraid there is no basic file I/O functions currently included the library. 

I do not know the CX16 well enough yet. What type of storage does it support and  how does it read/write files?

It uses the same APIs as the Commodore 64. My current assembly file read code looks like this:

    lda #filename_len-filename
    ldx #<filename
    ldy #>filename
    jsr SETNAM

    lda #FILE_CHANNEL
    ldx #DEVICE_NUMBER
    ldy #SECONDARY_ADDRSS_DIR
    jsr SETLFS
    jsr OPEN
 

To actually read from the file, you need to call CHKIN with the file channel in A:
    ldx #FILE_CHANNEL
    jsr CHKIN

And then you use GETIN to read a byte from the channel:
    jsr GETIN   ; reads a byte to A

To check for EOF:
    jsr READST or LDA $0286 
Reading from $286 is much faster, sine READST has some other overhead that's unnecessary for an EOF check. I plan to resolve that issue by actually reading into the READST routine and grabbing the operand of the LDA instruction.
 

Closing a channel is very simple:

    jsr CLRCHN
    lda #FILE_CHANNEL
    jsr CLOSE 

I'm looking at this right now, but I won't be able to spend too much more time on it today, I have to head out to work in about an hour. 

 

The problem I'm currently having is that this code is optimizing "ret" as a constant, and it's not allocating memory for the return variable:

char fgetc(byte channel)
{
    char ret=0;
    asm {
        ldx channel
        jsr CHKIN
        jsr GETIN
        sta ret
        jsr CLRCHN
    }
    return ret;
}
 

looks like this in the ASM file:
// fgetc(byte zp($13) channel)
fgetc: {
    .label ret = 0
    .label channel = $13
    ldx channel
    jsr CHKIN
    jsr GETIN
    sta ret
    jsr CLRCHN
    rts
}
 

I think the problem is that the optimizer isn't seeing RET being used anywhere, so it's optimizing out the storage for ret and treating it as a constant. If I do something like "ret=ret+1" above the asm block, then it gets storage... but there are other issues I'm still working out.

 

Edited by TomXP411

Share this post


Link to post
Share on other sites
1 hour ago, TomXP411 said:

 

The problem I'm currently having is that this code is optimizing "ret" as a constant, and it's not allocating memory for the return variable:

I think the problem is that the optimizer isn't seeing RET being used anywhere, so it's optimizing out the storage for ret and treating it as a constant. If I do something like "ret=ret+1" above the asm block, then it gets storage... but there are other issues I'm still working out.

 

If you add a volatile to ret it will not optimize it away. The compiler is not clever enough to recognize that you are modifying it inside the ASM.

It might be possible to examine the ASM instructions being applied to variables used inside ASM to detect if they can modify the variable. I will add that as a TODO.

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, TomXP411 said:

It uses the same APIs as the Commodore 64. 

You might want to have a look at the example program /examples/kernalload/kernalload.c 

It loads a file on the C64 using the C64 KERNAL. 

Share this post


Link to post
Share on other sites

Hmm... unfortunately, I'm hitting some roadblocks. I got my code with the assembly subroutines to compile, but I'm getting a disk error, but the DOS command resets the emulator. Looking at the generated assembly code, it looks like you're writing to zero page locations 0,1, and locations > $80. This is a problem, since BASIC and the KERNAL rely on $80 and up, and $00 and $01 are the bank switching addresses (or will be when the next version comes out.)

I checked the target files, and I didn't see any way to constrain Zero Page addresses, so I'm going to put this to bed until I can investigate more later. 

 

Share this post


Link to post
Share on other sites
1 hour ago, TomXP411 said:

Hmm... unfortunately, I'm hitting some roadblocks. I got my code with the assembly subroutines to compile, but I'm getting a disk error, but the DOS command resets the emulator. Looking at the generated assembly code, it looks like you're writing to zero page locations 0,1, and locations > $80. This is a problem, since BASIC and the KERNAL rely on $80 and up, and $00 and $01 are the bank switching addresses (or will be when the next version comes out.)

I checked the target files, and I didn't see any way to constrain Zero Page addresses, so I'm going to put this to bed until I can investigate more later. 

 

You can restrict the zeropages used through #pragma zp_reserve()

However, the compiler avoids 0 and 1 by default - and starts allocating from 2 upwards. So I believe there is something different causing the issue you are seeing.  

If you would share the program with me I can help find the issue.

 

Share this post


Link to post
Share on other sites

Okay,  it looks like my wounds were self inflicted. I used the load example you suggested without realizing that it explicitly uses the free ZP locations on the Commodore 64, which happen to be in the space you're not supposed to use on the Commander. This was overwriting something that was causing a BRK every time I tried to run any BASIC command.

Along the way, I discovered that the print routines use screen codes, rather than PETSCII codes, which was giving me a FILE NOT FOUND error. The filename "DEMO" was actually being encoded as 4,5,13,15. 

Now that I know that's an issue, I can work around it. If they're not already there, I'll add some PETSCII<>Screen code conversion routines, so that problem can be worked around in the future. 

Although it would be nice to have sigils or prefixes to encode strings as PETSCII/ASCII, instead of screen codes, so we could just write something like 
char* filename = "demo,r,s"p;   // encode the filename as PETSCII text. 

 

 

Share this post


Link to post
Share on other sites
45 minutes ago, TomXP411 said:

Now that I know that's an issue, I can work around it. If they're not already there, I'll add some PETSCII<>Screen code conversion routines, so that problem can be worked around in the future. 

Although it would be nice to have sigils or prefixes to encode strings as PETSCII/ASCII, instead of screen codes, so we could just write something like 
char* filename = "demo,r,s"p;   // encode the filename as PETSCII text. 

At compile-time you can change the string encoding of your literal strings in two ways. The pragma changes it for all strings following the pragma:

#pragma encoding(petscii_mixed)

If you just want a single string in petscii then you can use the magic suffix "pm" after your string - which is surprisingly close to your own proposal 😀

"abcABC1"pm

If you want ASCII instead, then the name is "ascii" and the magic suffix is "as"

There is currently no library code offering run-time conversion. 

Edited by Jesper Gravgaard

Share this post


Link to post
Share on other sites

Waoo !
That seems very very promising !
I'll give it a try.

Meanwhile, I was a bit curious looking at the source code you showed in the first post so I went to read the doc 😉

Quick remarks:
- The high low operator. Pretty obvious when you're in asm. In C, it looks really weird. Not to mention your highlighter didn't recognize it. I would rather go with the same kind of logic you're using for string with a postfix modifier.
addr.H and addr.L
or simply an explicit HIGH(addr)

- The floating point type
Could you have a placeholder type like float and some pre defined functions ?
Therefore the compiler would be able to compile those. The only thing is to add the library for the target system

- function calls
Is there a way to tell how the parms have to be passed (thru registers, thru stack....)? I didn't see anything on that matter.

- inline kickasm code:
Absolutely brilliant !!

Congratulations, Sir.

ps: any chance we have at some point other targets.... like the apple // ?

  • Like 1

Share this post


Link to post
Share on other sites
3 hours ago, Jesper Gravgaard said:

At compile-time you can change the string encoding of your literal strings in two ways. The pragma changes it for all strings following the pragma:


#pragma encoding(petscii_mixed)

If you just want a single string in petscii then you can use the magic suffix "pm" after your string - which is surprisingly close to your own proposal 😀


"abcABC1"pm

If you want ASCII instead, then the name is "ascii" and the magic suffix is "as"

There is currently no library code offering run-time conversion. 

Thanks. Yes, the magic suffixes are exactly what I was looking for. 
 

I think half of my trouble is that the PDF included in the release appears to be incomplete. I actually searched all references of “string” and could not find the magic suffixes, nor could I find a list of #pragmas. I did find your Google Doc, but I didn’t find the magic suffixes in there, either. (Although I will look again.)

I have plans to build an Orthodox File Manager (sort of Midnight Commander for the Commander X16), and I will need to build the conversion and output functions in question, so as I complete chunks of that, I’ll send over the more useful parts. 
 

Now that I have a working file reader, I’ve passed the biggest roadblock, so 🎉 

  • Like 1

Share this post


Link to post
Share on other sites

@kktos Thank you for the kind words and the feedback. If you have more feedback or suggestions please let me know.

Here are my short answers to your points

3 hours ago, kktos said:

Quick remarks:
- The high low operator. Pretty obvious when you're in asm. In C, it looks really weird. Not to mention your highlighter didn't recognize it. I would rather go with the same kind of logic you're using for string with a postfix modifier.
addr.H and addr.L
or simply an explicit HIGH(addr)

I agree with you! In the start the language has more ASM-features mixed in - but over time the compiler has gradually been improved and moved close and closer the standard C.

I believe it is important to have the feature of addressing individual bytes - and the current solution is not very readable when used on 16bit or 32bit data. Also as you point out it confuses IDEs. 

So I want to find a different solution along the line of your proposals.

My  TODO is here: https://gitlab.com/camelot/kickc/-/issues/221

4 hours ago, kktos said:

- The floating point type
Could you have a placeholder type like float and some pre defined functions ?
Therefore the compiler would be able to compile those. The only thing is to add the library for the target system

That would be a fine way to add support for floats. I have added a description here https://gitlab.com/camelot/kickc/-/issues/619

There is a bunch of stuff in the pipeline, so for now it will be put into the pile of ideas.

4 hours ago, kktos said:

- function calls
Is there a way to tell how the parms have to be passed (thru registers, thru stack....)? I didn't see anything on that matter.

There are keywords for changing the calling convention on a single function. Currently there are two supported calling conventions:

- __phicall - passes parameters through shared variables.
- __stackcall - passes parameters on the stack.

__phicall is the default and allows the optimizer to optimize parameter passing quite a lot. __stackcall works for the test programs I have created - but I have not done very extensive testing. 

There is also a pragma for changing it in the entire program #pragma calling(__stackcall)

I am working on adding the more advanced stuff, like calling conventions, to the documentation. 

4 hours ago, kktos said:

- inline kickasm code:
Absolutely brilliant !!

Thank you. It is extremely useful when loading images or generating tables.

4 hours ago, kktos said:

ps: any chance we have at some point other targets.... like the apple // ?

I have been steadily adding platforms. The last additions have been Atari XL/XE, MEGA65 and CX16. The next ones I am considering are C128,  Acorn Electron and PC Engine. 

I prefer to have the actual hardware myself so I can test that the compiler produces programs that run on the hardware. I do not have an Apple II yet. 

However, the Apple II would be an obvious addition at some point. 
 

  • Like 1

Share this post


Link to post
Share on other sites
3 hours ago, TomXP411 said:

I think half of my trouble is that the PDF included in the release appears to be incomplete. I actually searched all references of “string” and could not find the magic suffixes, nor could I find a list of #pragmas. I did find your Google Doc, but I didn’t find the magic suffixes in there, either. (Although I will look again.)

The documentation is a work in progress. I am working on getting it up-to-date with the features that have been added.

3 hours ago, TomXP411 said:

I have plans to build an Orthodox File Manager (sort of Midnight Commander for the Commander X16), and I will need to build the conversion and output functions in question, so as I complete chunks of that, I’ll send over the more useful parts. 

That sounds like a very useful tool! 

It would be great if you create some of the libraries that are missing for KickC. If they can be useful for others I will gladly merge it into my repository and include them in future releases.

Share this post


Link to post
Share on other sites
13 hours ago, Jesper Gravgaard said:

The documentation is a work in progress. I am working on getting it up-to-date with the features that have been added.

That sounds like a very useful tool! 

It would be great if you create some of the libraries that are missing for KickC. If they can be useful for others I will gladly merge it into my repository and include them in future releases.

I am working on it now... part of the problem is I really don't understand your assembly/c interface very well, and your calling convention is not something I've seen before. 

For example, I wanted to fill a buffer in the data segment, with the pointer stored in RO (address 2 in zero page). I'd expect something like this to work...

in my global section:

  __address($02) word R0;

char* buffer ..... 

...

R0 = (word) buffer;

asm {

  STA (R0),Y

...

}

}

But the compiler just completely removed the assignment to R0, then failed to assemble the STA line because R0 didn't exist. 

I ended up using a really obtuse syntax with a double pointer that I pulled out of your LOAD example, but I don't understand why the code above doesn't work. 

 

Share this post


Link to post
Share on other sites

Tom, I guess that's related to what Jesper told you already about the compiler no "seeing" the var in the asm.
Therefore, here, as it doesn't see any use of R0, it simply removes it.
You need to use volatile in the declaration so the complier won't optimize it

Share this post


Link to post
Share on other sites
38 minutes ago, kktos said:

Tom, I guess that's related to what Jesper told you already about the compiler no "seeing" the var in the asm.
Therefore, here, as it doesn't see any use of R0, it simply removes it.
You need to use volatile in the declaration so the complier won't optimize it

Yeah, I tried that... still no joy.  The error I get is "unknown addressing mode". The compiler doesn't understand that STA (R0),Y should be an indirect , Y indexed instruction with the address of R0. 

The compiler does generate code for STA R0, which generates STA $02, but not for STA ($02),Y

 

 

Edited by TomXP411

Share this post


Link to post
Share on other sites

Sounds like R0 is not getting recognized as an 8-bit value or there's a bug in cc65. Did you try just STA ($02),y?

For zero page addresses, you may need to have separate identifiers for zero page address values and usable pointers.

Share this post


Link to post
Share on other sites
12 hours ago, TomXP411 said:

I am working on it now... part of the problem is I really don't understand your assembly/c interface very well, and your calling convention is not something I've seen before. 

For example, I wanted to fill a buffer in the data segment, with the pointer stored in RO (address 2 in zero page). I'd expect something like this to work...

in my global section:

  __address($02) word R0;

char* buffer ..... 

...

R0 = (word) buffer;

asm {

  STA (R0),Y

...

}

}

But the compiler just completely removed the assignment to R0, then failed to assemble the STA line because R0 didn't exist. 

I ended up using a really obtuse syntax with a double pointer that I pulled out of your LOAD example, but I don't understand why the code above doesn't work. 

 

I have tried to create a minimal example re-creating your problem - but it seems my programs cannot reproduce your problem.

The following is my C-program  

__address($02) word R0;
 
void main() {
  char* buffer = 0x0400;
  R0 = (word) buffer;
  asm {
    sta (R0),y
  }
}

And this is the resulting ASM of the main function, when compiling with version 0.8.5:

main: {
  .label buffer = $400
  lda #<buffer
  sta.z R0
  lda #>buffer
  sta.z R0+1
  sta (R0),y
  rts
}

I believe this is what you wanted to see.

Could you show a little more of your code (or share the entire file). Then I will look into the problem you are facing.

/Jesper

Share this post


Link to post
Share on other sites
Quote

Does the assembler understand capital letters for the register names?

Thanks for the tip: that's exactly what it was. The compiler fails two different ways if you:

  1. Write the mnemonics in upper case. 
  2. Write register operands in upper case

Now that I know, I'll make sure to lower-case mnemonics and register operands. 

And now that I've done that, I can now read 256 bytes from a file directly into a memory buffer. I've actually got two more extensions to make to that method: stop on null, and stop on CR. 

On that note @Jesper Gravgaard, I'm considering the naming convention for the CMDR File I/O routines. 

I started using fopen, fgetc, and fgets, but the signature of all those does not match the conventions in the ANSI C stdio library.

for example, my fopen looks like this:
void fopen(byte channel, byte device, byte secondaryAddress, char *filename)

while ANSI's stdio looks like

FILE * fopen ( const char * filename, const char * mode );

Would it be better to rename the routines, or just overload the standard names with the platform-specific signatures? 

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. 

 

 

    

Edited by TomXP411

Share this post


Link to post
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.


×
×
  • Create New...

Important Information

Please review our Terms of Use