cc65: Unnecessary code when accessing pointers

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
rainwarrior
Posts: 8732
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: cc65: Unnecessary code when accessing pointers

Post by rainwarrior »

DRW wrote:Is there any way I can force the compiler to treat this as a byte?
Only the result of (src+3) matters, you don't have to cast everything else. Cast has higher precedence than >> or most operators so you don't need extra parentheses either (unless you prefer them to make the order clear.)

This works for me:

Code: Select all

;
; i = (unsigned char)(j+3) >> 2;
;
	lda     _j
	clc
	adc     #$03
	lsr     a
	lsr     a
	sta     _i
;
(i and j as zpsym)
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65: Unnecessary code when accessing pointers

Post by DRW »

Thanks. Yeah, this seems to work.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: cc65: Unnecessary code when accessing pointers

Post by Banshaku »

Since we are talking about unnecessary code for pointers, I could talk about one of the result of my tests. One of my function that is processing intensive data requires to be done in asm. The parameters are for an entity that we put in a buffered list so it will be set later in the OAM when parsing is over. Instead of passing the parameters to the function or one my one with some with some ZP variables, what I do is I share a block of memory from the asm side and map it on the C side with a struct. This way all parameters, pointer included, are accessed with an indexer and cc65 doesn't use it's ptr1 thingy.

The c code would look that way when imported:

Code: Select all

someheader.h

typedef struct {
    char x;
    char y;
    char foo;
    const char* data;
} myParmeters_t;

extern myParameters_t addToBufferParams;
#pragma zpsym("adToBufferParams");

void __fastcall__ addToBuffer(void);
when used:

Code: Select all

   mycode.c

   // ---- begin addToBuffer -----
   addToBufferParams.x = actor.x;
   addToBufferParams.y = actor.y;
   addToBufferParams.foo = actor.foo;
   addToBufferParams.data = actor.currentFrame;
   addToBuffer();
   // ----- end addToBuffer ---------
Finally, the assembler would look something like this:

Code: Select all


.export _addToBuffer := subAddToBuffer

.exportzp _addToBufferParams := zpParams


.segment "ZEROPAGE"
zpPrams:    .res 10   ; this is a shared buffer for parameters

.segment "BSS"
bufferedList  .res 60 ; some list of entitites

.segment "CODE"

;->BEGIN----------------------------------------------------------------------
; add to entity buffer
;
; Note: uses shared parameters (etc etc)
;
.proc subAddToBuffer
;---------------- Parameters definitions ----------------
.scope local
     posX = zpParams
     posY = zpParams+1
     foo  = zpParams+2
     data = zpParams+3
.endscope
;---------------------------------------------------------

     ; ... some code before
     lda local::posY
     ; do some processing
     sta bufferedList,x
     ; .... mode code here

     rts
.endproc
;-<END------------------------------------------------------------------------
The code generated on the C side access the shared buffer with indexers (_addToBufferParams,x) for all parameters so it should be fast enough. But, it may be possible that different structs may generate different access code so more testing would be required.

One thing that may not be relevant to this conversation but found interesting is that cc65 doesn't use signed char by default: everything is unsigned unless you pass a parameter to the compiler. Which means, unless you want to write portable code (which is maybe not that possible with the nes), you don't have to write unsigned char since char is unsigned. My definition of variable were becoming long with all those const that I may decide to remove the unecessary unsigned for now. I'm just used to write it out of habit.

edit:
I confused it with some other tests, it was not with an indexer but like this:

Code: Select all

; addTobufferedListParams.data = hero.frame.current;
518 ;
519         .dbg    line, "src/example1.c", 201
520         lda     _hero+7+1
521         sta     _addTobufferedListParams+3+1
522         lda     _hero+7
523         sta     _addTobufferedListParams+3
edit2:
Updated the asm code since it did something else. This code is written by end for example only.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65: Unnecessary code when accessing pointers

Post by DRW »

Yeah, I immediately wondered about your statement that simple struct members are accessed with an indexer. Since the location of the member is known at compile time, it's of course simply a +.


I didn't know that char was always unsigned in cc65. I would say this goes against the C standard.

In my case, it doesn't really make a difference. One of the first things that I included into my C code was:

Code: Select all

typedef unsigned char byte;
typedef signed char sbyte;
typedef byte bool;
#define false 0
#define true 1
By the way, you should be really careful about sharing struct data with Assembly code. Because you always need to make sure that the data actually matches between the two.

I've had a similar issue where I wanted to use my struct for the current character data in Assembly, but it's not possible to actually export the member names as constants, so that they can be included in Assembly.

That's why I'm using inline assembly whenever I need the performance of Assembly with the data of a struct:

Code: Select all

/* C version: */
En = Player.Energy;

/* Inline Assembly in C: */
__asm__("LDA %v + %b", Player, offsetof(struct GameCharacter, Energy));
__asm__("STA %v", En);
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Jarhmander
Formerly ~J-@D!~
Posts: 569
Joined: Sun Mar 12, 2006 12:36 am
Location: Rive nord de Montréal

Re: cc65: Unnecessary code when accessing pointers

Post by Jarhmander »

DRW wrote:I didn't know that char was always unsigned in cc65. I would say this goes against the C standard.
The signedness of the char type is implementation-dependent; that means it can be either signed or unsigned, and the implementer of the compiler is free to choose one. Oddly enough, whatever the signedness, it is not equivalent to the corresponding signed/unsigned char. That is, char, unsigned char and signed char are always distinct types.

You may want to use types in the stdint.h header, if you want portable integers of known sizes you can use these handy typedefs: int8_t, uint8_t, uint16_t etc.
((λ (x) (x x)) (λ (x) (x x)))
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65: Unnecessary code when accessing pointers

Post by DRW »

https://en.cppreference.com/w/c/language/arithmetic_types wrote:char - type for character representation. Equivalent to either signed char or unsigned char (which one is implementation-defined and may be controlled by a compiler commandline switch), but char is a distinct type, different from both signed char both unsigned char
So which one is it? Equivalent to either one of the two or always distinct from both?
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
Banshaku
Posts: 2417
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: cc65: Unnecessary code when accessing pointers

Post by Banshaku »

Regarding c65, I think it is mentioned here:

http://cc65.github.io/doc/cc65.html#toc7.15
7.15 #pragma signed-chars ([push,] on|off)

Changes the signedness of the default character type. If the argument is "on", default characters are signed, otherwise characters are unsigned. The compiler default is to make characters unsigned since this creates a lot better code. This default may be overridden by the --signed-chars command line option.

The #pragma understands the push and pop parameters as explained above.
As for standards, is seems to be c89 with a few things from c99:
--standard std

This option allows to set the language standard supported. The argument is one of

c89

This disables anything that is illegal in C89/C90. Among those things are // comments and the non-standard keywords without underscores. Please note that cc65 is not a fully C89 compliant compiler despite this option. A few more things (like floats) are missing.
c99

This enables a few features from the C99 standard. With this option, // comments are allowed. It will also cause warnings and even errors in a few situations that are allowed with --standard c89. For example, a call to a function without a prototype is an error in this mode.
cc65

This is the default mode. It is like c99 mode, but additional features are enabled. Among these are "void data", non-standard keywords without the underlines, unnamed function parameters and the requirement for main() to return an int.

Please note that the compiler does not support the C99 standard and never will. c99 mode is actually c89 mode with a few selected C99 extensions.
edit:

I forgot about the main point talked, using some mapped values with struct. Yes, there is some risks but it simplify a lot of access so I guess if the person is organized it should be usable. Of course, when there is a bug with overlapping values then the fun begins .. ^^;;; I guess it a compromise between speed, usability and risk of weird bugs.
User avatar
slembcke
Posts: 172
Joined: Fri Nov 24, 2017 2:40 pm
Location: Minnesota

Re: cc65: Unnecessary code when accessing pointers

Post by slembcke »

I'm a bit late to the party, but register variables can be a good solution to the original problem.

For instance, this function avoids copying the pointer into a scratch location. You can mark function arguments as register too, and it generates slightly different code.

Code: Select all

void bar(void){
	register u8 *foo;
	*foo = 5;
}
Accessing the pointer is cheaper now, but the downside is there is kind of a lot of code (like 20 bytes) associated with saving and restoring register variables. So use them carefully.

Code: Select all

;
; register u8 *foo;
;
	lda     regbank+14
	ldx     regbank+15
	jsr     pushax
;
; *foo = 5;
;
	lda     #$05
	ldy     #$00
	sta     (regbank+14),y
;
; }
;
	ldy     #$00
	lda     (sp),y
	sta     regbank+14
	iny
	lda     (sp),y
	sta     regbank+15
	jmp     incsp2
Post Reply