VBCC Optimizing C-compiler now supports NES

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

yaros
Posts: 14
Joined: Mon Jul 27, 2020 1:14 pm

Re: VBCC Optimizing C-compiler now supports NES

Post by yaros » Tue Aug 25, 2020 9:19 pm

vbc wrote:
Tue Aug 25, 2020 3:53 pm
This warning is produced by a global optimizer pass and can not be disabled locally. You can only disable it globally.
I wouldn't want to disable it completely. It seems very useful in general. I guess I'll just leave with it. Thank you for explanations.

vbc
Posts: 52
Joined: Sun Jun 21, 2020 5:03 pm

Re: VBCC Optimizing C-compiler now supports NES

Post by vbc » Wed Aug 26, 2020 12:50 pm

yaros wrote:
Tue Aug 25, 2020 9:19 pm
I wouldn't want to disable it completely. It seems very useful in general. I guess I'll just leave with it. Thank you for explanations.
If you do not mind to waste a few bytes and cycles you can prevent the warning for one loop like that:

Code: Select all

volatile const char v_one=1;
...
   while(v_one)
   {
     ...
   }

User avatar
Memblers
Site Admin
Posts: 3877
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: VBCC Optimizing C-compiler now supports NES

Post by Memblers » Sat Aug 29, 2020 6:34 am

vbc wrote:
Wed Aug 05, 2020 11:29 pm
rox_midge wrote:
Wed Aug 05, 2020 5:56 pm
Does sprintf (or any other function needing vargs) not work at the moment / not work at all? It seems to copy the format string if there are no placeholders and no vargs supplied, but otherwise appears to do nothing at all.

I really don't even need sprintf, I just need a quick way to convert an int / unsigned char to a string - if there's another way (itoa, etc.), I'm glad to skip sprintf entirely.
It should work just fine. I just verified this example:

Code: Select all

#include <stdio.h>

char buf[100];

main()
{
 sprintf(buf,"test %d %c %s",123,'X',"test");
 puts(buf);
}
I've ran into something like this, too, while printing some debug strings to RAM. I've narrowed it down to using uint32_t from stdint.h. The compiler gives warning 214 suspicious format string. I've reproduced it in this example, but the effect isn't the same as it was in my program.

Code: Select all

#include <stdio.h>
#include <stdint.h>

char buf[100];

unsigned int aa = 0xAA;
unsigned short bb = 0xBB;
unsigned char cc = 0xCC;

uint32_t dd = 0xDD;
uint16_t ee = 0xEE;
uint8_t ff = 0xFF;

int main()
{
	sprintf(buf,"test %X %X %X ",aa,bb,cc);
	puts(buf);
	sprintf(buf,"test %X %X %X ",dd,ee,ff);
	puts(buf);
	sprintf(buf,"test %X %X %X %X %X %X",dd,cc,dd,dd,aa,dd);
	puts(buf);
}
In this example, the value printed after uint32_t type is always zero. If I change the type to "unsigned int", or cast it as such, the warning and the problem goes away.

vbc
Posts: 52
Joined: Sun Jun 21, 2020 5:03 pm

Re: VBCC Optimizing C-compiler now supports NES

Post by vbc » Sun Aug 30, 2020 5:46 am

Memblers wrote:
Sat Aug 29, 2020 6:34 am
I've ran into something like this, too, while printing some debug strings to RAM. I've narrowed it down to using uint32_t from stdint.h. The compiler gives warning 214 suspicious format string. I've reproduced it in this example, but the effect isn't the same as it was in my program.

Code: Select all

#include <stdio.h>
#include <stdint.h>

char buf[100];

unsigned int aa = 0xAA;
unsigned short bb = 0xBB;
unsigned char cc = 0xCC;

uint32_t dd = 0xDD;
uint16_t ee = 0xEE;
uint8_t ff = 0xFF;

int main()
{
	sprintf(buf,"test %X %X %X ",aa,bb,cc);
	puts(buf);
	sprintf(buf,"test %X %X %X ",dd,ee,ff);
	puts(buf);
	sprintf(buf,"test %X %X %X %X %X %X",dd,cc,dd,dd,aa,dd);
	puts(buf);
}
In this example, the value printed after uint32_t type is always zero. If I change the type to "unsigned int", or cast it as such, the warning and the problem goes away.
%X expects an int (i.e. 16 bits on 6502). If you pass a long int, you have to use %lX.

User avatar
Memblers
Site Admin
Posts: 3877
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: VBCC Optimizing C-compiler now supports NES

Post by Memblers » Fri Sep 18, 2020 10:55 am

I've had the uncanny experience of "getting beaten" by this compiler. I have a function that is simple but running very often, so I tried rewriting the main part of it in asm. It turned out slower than the C version, by more than just the JSR/RTS penalty from calling the asm subroutine. The asm routine was then deleted in shame. :lol: This is with -o2, I haven't really used -o3 or above with this project yet because it makes the code too large to link.

Probably a special case because I was being lazy and leaving the more complex part in C, but it was still kinda fun to see that happen. That wasn't the case for the few other small functions I (fully) rewrote in asm.

Development has gone well with my project, I've made an arcade game emulator for NES (only playable game currently is Side Track by Exidy). It uses dynamic recompilation, but it's very slow due to lack of RAM. For now it's still using NROM, but with WRAM added at $6000-$7FFF. It's intended to eventually run on a Flash+WRAM cart, and could potentially perform better there.

There is one limitation I've found with the assembler, that it won't accept "align 256". I think it should output a warning, but still do the alignment. It helps with page-crossing penalties, but there's some things like this that can't be done without alignment. For now I suppose I can use the linker to make a segment for tables, if I need to. I haven't fully come to grips with the linker config stuff (even after going the full 15 hyperlinks deep in those GNU linker docs, haha), but the examples posted in this thread have been helpful.

I have a question about linking, I need to figure out how to specify multiple RAM segments. bss and data have been redirected to "wram" instead of "ram", but I also want to use ram. Here's what I started with:

Code: Select all

MEMORY
{
  out:     org=0x7FF0, len=0xffffff
  zero:    org=0, len=0x0100
  b0:      org=0x8000, len=0x8000
  b1:      org=0x8000, len=0x8000
  chr:     org=0x0000, len=0x2000  
  ram:     org=0x0300, len=0x0500
  wram:	   org=0x6000, len=0x2000
}

NESMAPPER    = 0 ; /* mapper number */
NESPRG_BANKS = 2 ; /* number of 16K PRG banks, change to 2 for NROM256 */
NESCHR_BANKS = 0 ; /* number of 8K CHR banks */
NESMIRRORING = 0 ; /* 0 horizontal, 1 vertical, 8 four screen */

SECTIONS
{
  header: {BYTE(0x4e);BYTE(0x45);BYTE(0x53);BYTE(0x1a);
           BYTE(NESPRG_BANKS);
           BYTE(NESCHR_BANKS);
           BYTE(NESMIRRORING|(NESMAPPER<<4));
           BYTE(NESMAPPER&0xf0);
           LONG(0);LONG(0);
          } >out

  text:   {*(text)} >b0 AT>out
  .dtors: { *(.dtors) } >b0 AT>out
  .ctors: { *(.ctors) } >b0 AT>out
  rodata: {*(rodata)}  >b0 AT>out
  init:   {*(init)}  >b0 AT>out
  data:   {*(data)} >wram AT>out

  /* fill program bank */
  fill: { .=.+0x10000-6-ADDR(init)-SIZEOF(init)-SIZEOF(data);} >b0 AT>out
  vectors:{ *(vectors)} >b0 AT>out

/*
  chars:  {
     *(chars);
     RESERVE(8192-.-ADDR(chars));
  } >chr AT>out
*/

  zpage (NOLOAD) : {*(zpage) *(zp1) *(zp2)} >zero
  bss (NOLOAD): {*(bss)} >wram

  __DS = ADDR(data);
  __DE = ADDR(data) + SIZEOF(data);
  __DC = LOADADDR(data);

  __STACK = 0x8000;

  ___heap = ADDR(bss) + SIZEOF(bss);
  ___heapend = __STACK;
}
Now I've added this line into the linker config, and trying to use it my code as such:

Code: Select all

nesram (NOLOAD): {*(nesram)} >ram

#pragma section nesram
uint8_t RAM_BASE[0x400];
#pragma section default
But it always ends up in the bss section, so I must be doing something wrong.

The compiler has been working great, only problems have been user error, haha. I did run into one strange situation with some (really ugly) code like this:

Code: Select all

cache_code[cache_index][code_index++] = (uint8_t) (((uint16_t) &cache_code[cache_index][code_index+9]) & 0xFF);
It's doing post-increment on code_index, but also using code_index+9. Whats weird is that it worked OK when this code was in main(), but it seemed like (could be a coincidence) after I moved it into a function (not recursive, and no parameters) it stopped working and my program was doing odd things. Defining a totally unrelated (and in fact, unused) variable would cause it to do different wrong behaviours, depending on the size of that reserved variable. Changing that variable into an array, the effects would go away (because then it was being placed higher in memory, after cache_code[]). The fix was simply rewriting it to only use code_index+offset, and drop the post-increment. I don't know if that's really a bug, or just a case of me writing very bad code. But it did surprise that it stopped working when I did something that I believed was unrelated. I could try to reproduce this, if this sounds like something worth looking into. (edit - note those variables and arrays are all global)

If missing optimization stuff is helpful, I could start saving cases of those. Sometimes I've seen stuff getting loaded then discarded. I'm seeing "and #255" often in the output, and that's essentially a NOP.

That Mesen label converter has been very useful.

edit:
I have a couple C/asm question. Is there a way I can JMP to a label in the C code, from assembly? At first I tried using the labels goto uses, but I think the assembly code can't see those. And conversely, maybe I can JMP from C to asm using in-line assembly, but I haven't quite figured out how to format inline asm. Is there an example of it?

vbc
Posts: 52
Joined: Sun Jun 21, 2020 5:03 pm

Re: VBCC Optimizing C-compiler now supports NES

Post by vbc » Sat Sep 19, 2020 1:46 am

Memblers wrote:
Fri Sep 18, 2020 10:55 am
Development has gone well with my project, I've made an arcade game emulator for NES (only playable game currently is Side Track by Exidy). It uses dynamic recompilation, but it's very slow due to lack of RAM. For now it's still using NROM, but with WRAM added at $6000-$7FFF. It's intended to eventually run on a Flash+WRAM cart, and could potentially perform better there.
Nice to hear.
There is one limitation I've found with the assembler, that it won't accept "align 256".
With the oldstyle syntax module, the argument of align is the number of bits to clear. You probably want "align 8".
I have a question about linking, I need to figure out how to specify multiple RAM segments. bss and data have been redirected to "wram" instead of "ram", but I also want to use ram. Here's what I started with:

Code: Select all

MEMORY
{
  out:     org=0x7FF0, len=0xffffff
  zero:    org=0, len=0x0100
  b0:      org=0x8000, len=0x8000
  b1:      org=0x8000, len=0x8000
  chr:     org=0x0000, len=0x2000  
  ram:     org=0x0300, len=0x0500
  wram:	   org=0x6000, len=0x2000
}

NESMAPPER    = 0 ; /* mapper number */
NESPRG_BANKS = 2 ; /* number of 16K PRG banks, change to 2 for NROM256 */
NESCHR_BANKS = 0 ; /* number of 8K CHR banks */
NESMIRRORING = 0 ; /* 0 horizontal, 1 vertical, 8 four screen */

SECTIONS
{
  header: {BYTE(0x4e);BYTE(0x45);BYTE(0x53);BYTE(0x1a);
           BYTE(NESPRG_BANKS);
           BYTE(NESCHR_BANKS);
           BYTE(NESMIRRORING|(NESMAPPER<<4));
           BYTE(NESMAPPER&0xf0);
           LONG(0);LONG(0);
          } >out

  text:   {*(text)} >b0 AT>out
  .dtors: { *(.dtors) } >b0 AT>out
  .ctors: { *(.ctors) } >b0 AT>out
  rodata: {*(rodata)}  >b0 AT>out
  init:   {*(init)}  >b0 AT>out
  data:   {*(data)} >wram AT>out

  /* fill program bank */
  fill: { .=.+0x10000-6-ADDR(init)-SIZEOF(init)-SIZEOF(data);} >b0 AT>out
  vectors:{ *(vectors)} >b0 AT>out

/*
  chars:  {
     *(chars);
     RESERVE(8192-.-ADDR(chars));
  } >chr AT>out
*/

  zpage (NOLOAD) : {*(zpage) *(zp1) *(zp2)} >zero
  bss (NOLOAD): {*(bss)} >wram

  __DS = ADDR(data);
  __DE = ADDR(data) + SIZEOF(data);
  __DC = LOADADDR(data);

  __STACK = 0x8000;

  ___heap = ADDR(bss) + SIZEOF(bss);
  ___heapend = __STACK;
}
Now I've added this line into the linker config, and trying to use it my code as such:

Code: Select all

nesram (NOLOAD): {*(nesram)} >ram

#pragma section nesram
uint8_t RAM_BASE[0x400];
#pragma section default
But it always ends up in the bss section, so I must be doing something wrong.
I am not at home currently, so I cannot test it. But it looks ok. Where exactly did you put the additional linker line? Did you get any warning from the linker?
The compiler has been working great, only problems have been user error, haha. I did run into one strange situation with some (really ugly) code like this:

Code: Select all

cache_code[cache_index][code_index++] = (uint8_t) (((uint16_t) &cache_code[cache_index][code_index+9]) & 0xFF);
It's doing post-increment on code_index, but also using code_index+9.
C does not specify evaluation order. Code like this is explicitly forbidden in C and will cause undefined behaviour. Basically, if you modify an object, you must not use it anywhere else in the same statement. For the gritty details, look up on sequence points in C.
If missing optimization stuff is helpful, I could start saving cases of those. Sometimes I've seen stuff getting loaded then discarded. I'm seeing "and #255" often in the output, and that's essentially a NOP.
Optimization hints are appreciated. I will not make promises on what I can add, though.
I have a couple C/asm question. Is there a way I can JMP to a label in the C code, from assembly? At first I tried using the labels goto uses, but I think the assembly code can't see those.
No, vbcc will generate labels with numbers that are only known to the compiler.
And conversely, maybe I can JMP from C to asm using in-line assembly, but I haven't quite figured out how to format inline asm. Is there an example of it?
An example of jumping to a label or a general example of inline-assembly?

Regarding jumping to labels, you have to be very careful. vbcc will assume that inline assembly behaves similar to a function call. If the execution flow continues anywhere else, the code may not work.

Post Reply