cc65 - Are % and * supported?

Are you new to 6502, NES, or even programming in general? Post any of your questions here. Remember - the only dumb question is the question that remains unasked.

Moderator: Moderators

User avatar
DRW
Posts: 1982
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65 - Are % and * supported?

Post by DRW » Mon Aug 17, 2020 5:22 pm

Can anybody tell those authors that even if you don't want to use enum types, there's still no reason to replace the comfortable, automatically-incrementing enum constants with manual defines?
On 6502 use of constant values is faster than use of variables as it does not require reading values from memory.
Enum values aren't items that are stored in memory. Accessing an enum value is like accessing the number value directly, not like accessing const int x = 5;.
My game "City Trouble": www.denny-r-walter.de/city.htm

User avatar
Banshaku
Posts: 2393
Joined: Tue Jun 24, 2008 8:38 pm
Location: Japan
Contact:

Re: cc65 - Are % and * supported?

Post by Banshaku » Mon Aug 17, 2020 5:31 pm

DRW wrote:
Mon Aug 17, 2020 5:22 pm
Can anybody tell those authors that even if you don't want to use enum types, there's still no reason to replace the comfortable, automatically-incrementing enum constants with manual defines?
Maybe there is a reason behind is logic. For example, when using enum with gcc, the C standard says that an enum is promoted to an int. If cc65 does the same thing (hopefully not), then using enum is not a good thing. I got bitten by that while doing code on the megadrive trying to put them in char like on the nes.

But I need to reread his optimization guide thing, I did a quick reading a long time ago.

turboxray
Posts: 104
Joined: Thu Oct 31, 2019 12:56 am

Re: cc65 - Are % and * supported?

Post by turboxray » Mon Aug 17, 2020 8:28 pm

DRW wrote:
Mon Aug 17, 2020 5:22 pm
Can anybody tell those authors that even if you don't want to use enum types, there's still no reason to replace the comfortable, automatically-incrementing enum constants with manual defines?
What exactly are you going to use instead then? If not enums, then defines.
On 6502 use of constant values is faster than use of variables as it does not require reading values from memory.
Enum values aren't items that are stored in memory. Accessing an enum value is like accessing the number value directly, not like accessing const int x = 5;.
They never said enums were variables stored in memory. And their statement is correct.

User avatar
DRW
Posts: 1982
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65 - Are % and * supported?

Post by DRW » Tue Aug 18, 2020 4:56 am

Banshaku wrote:
Mon Aug 17, 2020 5:31 pm
Maybe there is a reason behind is logic. For example, when using enum with gcc, the C standard says that an enum is promoted to an int.
You can still use

Code: Select all

enum
{
    A,
    B,
    C,
};
and apply this to an unsigned char and it isn't any different as if you applied a number.
There's no need to define A, B and C as manual macros. Assigning an enum constant to a char variable will be the same Assembly code as if you assigned the number literal itself.
(Unless you disable optimization.)
turboxray wrote:
Mon Aug 17, 2020 8:28 pm
What exactly are you going to use instead then? If not enums, then defines.
There's no reason to avoid enums in the first place. For the NES, you should just not use the enum type:

Don't do this:

Code: Select all

enum MyEnum
{
   A, B, C
};

MyEnum MyVariable = A;
But it won't hurt your performance if you do this:

Code: Select all

enum
{
   A, B, C
};

unsigned char MyVariable = A;
turboxray wrote:
Mon Aug 17, 2020 8:28 pm
They never said enums were variables stored in memory. And their statement is correct.
They literally said:
On 6502 use of constant values is faster than use of variables as it does not require reading values from memory.
So, if they contrast enum { A = 0 }; with #define A 0 and call the former "reading values from memory", they might be under the impression that the enum value is identical to const int A = 0;, which it isn't.
If they don't mean that, then what are they referring to when they talk about "reading values from memory" in regards to enums?
My game "City Trouble": www.denny-r-walter.de/city.htm

turboxray
Posts: 104
Joined: Thu Oct 31, 2019 12:56 am

Re: cc65 - Are % and * supported?

Post by turboxray » Tue Aug 18, 2020 9:24 am

DRW wrote:
Tue Aug 18, 2020 4:56 am

But it won't hurt your performance if you do this:

Code: Select all

enum
{
   A, B, C
};

unsigned char MyVariable = A;
Not only is that bad form (and that should generate a warning), but It will technically hurt performance if you do something like if (myVariable == A) as CC65 like most compilers auto promotes to INT in some situations - even if A is something smaller than 256 in value and myVariable is an unsigned char. A #define would otherwise be optimized if the operation doesn't need to be promoted in the same situation.
If they don't mean that, then what are they referring to when they talk about "reading values from memory" in regards to enums?
Well, that's you making an implicit assumption. It's doesn't have any linking in the language that directly implies enums are variables. There's not "So," or "Alternatively,", etc. But yeah they could have made the point a little more clear (to me, it's just a reiteration to the point that constants are faster - and both enums and defines are constants).

User avatar
DRW
Posts: 1982
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65 - Are % and * supported?

Post by DRW » Tue Aug 18, 2020 1:20 pm

turboxray wrote:
Tue Aug 18, 2020 9:24 am
Not only is that bad form (and that should generate a warning)
Why should it be bad form? And which compiler ever created a warning here?

Having auto-incrementing values can be quite useful, even if you don't have a dedicated type for the enums.

turboxray wrote:
Tue Aug 18, 2020 9:24 am
but It will technically hurt performance if you do something like if (myVariable == A) as CC65 like most compilers auto promotes to INT in some situations
It is a comparison to a constant number literal. It won't promote to anything (unless you disable optimizations).
If A is, for example, 5, the generated Assembly code will convert this directly to CMP #5. It doesn't do the comparison like when you have const int A = 5;.
Enum assignments behave exactly like assignng the number literal directly, not like assigning a constant value from the RODATA section.

If you don't believe me, just compile an NES ROM. Then change some defines into enums. Compile again. And check whether even a single byte is different in your ROM.

turboxray wrote:
Tue Aug 18, 2020 9:24 am
Well, that's you making an implicit assumption.
(to me, it's just a reiteration to the point that constants are faster - and both enums and defines are constants).
Why should they state this under the topic "enums vs. defines"?
Change enums to "unsigned char" and enum values to constant #DEFINE. On 6502 use of constant values is faster than use of variables as it does not require reading values from memory.
To me, the line of though here is clear:
"Change enum types to unsigned char because enums are two bytes." --> Correct.
"Change enum constants to defines because defines are direct values and enums are locations in memory." --> Wrong.

Otherwise, if they weren't under a wrong assumption, why should they even mention the access of values from memory vs. the access of direct values here at all? Whether you access a memory location or a number literal has neither to do with the data type size, so why even mention this contrast?
My game "City Trouble": www.denny-r-walter.de/city.htm

turboxray
Posts: 104
Joined: Thu Oct 31, 2019 12:56 am

Re: cc65 - Are % and * supported?

Post by turboxray » Tue Aug 18, 2020 7:09 pm

DRW wrote:
Tue Aug 18, 2020 1:20 pm
turboxray wrote:
Tue Aug 18, 2020 9:24 am
Not only is that bad form (and that should generate a warning)
Why should it be bad form? And which compiler ever created a warning here?

Having auto-incrementing values can be quite useful, even if you don't have a dedicated type for the enums.
Assigning an INT to a CHAR should always be a warning (enums are INTs). Just because you can, doesn't mean you should. Unless you have a very good reason not to, be explicit. Cast down/up for conversion of types, always assign direct values to all enums elements, etc. In all the places I've worked, those two things would be immediately rejected in a PR/code review.

turboxray wrote:
Tue Aug 18, 2020 9:24 am
but It will technically hurt performance if you do something like if (myVariable == A) as CC65 like most compilers auto promotes to INT in some situations
It is a comparison to a constant number literal. It won't promote to anything (unless you disable optimizations).
If A is, for example, 5, the generated Assembly code will convert this directly to CMP #5. It doesn't do the comparison like when you have const int A = 5;.
Enum assignments behave exactly like assignng the number literal directly, not like assigning a constant value from the RODATA section.

If you don't believe me, just compile an NES ROM. Then change some defines into enums. Compile again. And check whether even a single byte is different in your ROM.
I didn't say the enums are stored as rodata. I said enums are INTs in CC65. And they are. You're not going to change that fact. So you're going to rely on the fact the every combination of its use will demote the enum to a byte for operations by the optimizer, for values is less than 256? You can make that assumption, or you can explicitly declare the value with a define and not have to worry about it. I've worked on C compilers for smaller systems. I've seen what optimizations do and don't do. I mean according to the optimization article, CC65 doesn't even optimize or have fastcall functionality for function signatures - so not sure how much faith I'd put in the optimizer.

I'm not sure what your problem is with their advice. It definitely has value and is good knowledge to know.


To me, the line of though here is clear
So specifically and unequivocally clear? Maybe it's a language thing.

User avatar
DRW
Posts: 1982
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65 - Are % and * supported?

Post by DRW » Wed Aug 19, 2020 12:00 am

turboxray wrote:
Tue Aug 18, 2020 7:09 pm
Assigning an INT to a CHAR should always be a warning (enums are INTs). Just because you can, doesn't mean you should.
If we're talking about variables, then yes, you're right: Always use an explicit cast when converting from larger to smaller.

But we're talking about constants here. Compile-time values. Not even const int constants, but constants that are directly converted to their number litreal.

If you think you need to cast a constant value when converting to a smaller data type, then I hope you never do stuff like this:

Code: Select all

unsigned char c = 5;
Because 5 is an integer as well. sizeof(5) will return a value greather than 1.
So, don't forget to write

Code: Select all

unsigned char c = (unsigned char)5;
everytime you assign a number to a char.
Or write

Code: Select all

unsigned char c = '\5';
I hope you don't trust the compiler to optimize this for you.

turboxray wrote:
Tue Aug 18, 2020 7:09 pm
I'm not sure what your problem is with their advice.
My problem is that enums can be used to write safer code because the values are automatically assigned. You don't have that luxury with defines. Have fun programming a game where every character type has an ID and you have about 80 characters and want to shift them around a bit.

And who cares what some random compiler would do here? That's a site specifically for optimizations for cc65, not a general C tutorial. So the fact that cc65 optimizes this should be taken into account. Why prevent yourself from using useful features when using cc65 just because some microcontroller compiler for a washing machine might do it differently?

Especially, as I want to say again, since they seem to be under an incorrect assumption. You don't believe me, but you still cannot explain what the topic "reading constant values vs. reading values from memory" has to do with enums vs. defines. So, either they are under an incorrect assumption.
Or they intended to include a totally off-topic random trivia fact that has literally nothing to do with anything else in the paragraph.
By the way, do you know that you can use cout in C++ instead if C's printf?
My game "City Trouble": www.denny-r-walter.de/city.htm

turboxray
Posts: 104
Joined: Thu Oct 31, 2019 12:56 am

Re: cc65 - Are % and * supported?

Post by turboxray » Thu Aug 20, 2020 4:38 pm

DRW wrote:
Wed Aug 19, 2020 12:00 am
turboxray wrote:
Tue Aug 18, 2020 7:09 pm
Assigning an INT to a CHAR should always be a warning (enums are INTs). Just because you can, doesn't mean you should.
If we're talking about variables, then yes, you're right: Always use an explicit cast when converting from larger to smaller.

But we're talking about constants here. Compile-time values. Not even const int constants, but constants that are directly converted to their number litreal.

If you think you need to cast a constant value when converting to a smaller data type, then I hope you never do stuff like this:

Code: Select all

unsigned char c = 5;
Because 5 is an integer as well. sizeof(5) will return a value greather than 1.
So, don't forget to write

Code: Select all

unsigned char c = (unsigned char)5;
everytime you assign a number to a char.
Or write

Code: Select all

unsigned char c = '\5';
I hope you don't trust the compiler to optimize this for you.
Really? You know I'm just gonna take it that your snotty attitude means you really don't understand any of the points being made, and that the guide was about optimizations - which the theme is to be explicit and not directly rely on the optimizer.. but all this probably just goes over your head. Congrats.

User avatar
DRW
Posts: 1982
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65 - Are % and * supported?

Post by DRW » Sat Aug 22, 2020 8:49 am

Yeah, yeah, I don't "get" it and it all "goes over my head" etc. etc. blah blah blah.

Have fun restricting yourself from a very useful language feature just because of some phantom optimization that some imaginary compiler which only exists in your head might not optimize.

After all, we're not programming actual NES games with an actual existing compiler whose quirks we have to keep in mind and whose advantages we can use, right?
Oh no, our codes are of course supposed to be snippets in a doctoral thesis, so we have to write them based on purely hypothetical thought experiments.


For everybody who is interested in actual application of enum values vs. defines in the real world of programming actual NES games: I tried it out.

Code: Select all

#if 1 /* Change to #if 0 for defines. */
enum
{
	aaa,
	bbb,
	ccc,
};
#else
#define aaa 0
#define bbb 1
#define ccc 2
#endif

unsigned char variable;

void __fastcall__ function(void)
{
	if (variable == ccc)
		variable = bbb;

	if (variable < bbb)
		variable = aaa;

	if (variable > ccc)
		variable += ccc;
}
Whether you optimize or not, the enum vs. define version produces exactly the same output.
With unoptimized code, even the macro constants are treated as integer values:

Code: Select all

	ldx     #$00
	lda     #$01
	sta     _variable
And with optimization, both are treated as byte values if you compare or assign them to byte variables:

Code: Select all

	lda     #$01
	sta     _variable
Whether it's an enum constant or a define makes no difference.

So, there's no reason why you shouldn't use automatically-incremented values, for example for your character or item or level IDs, as long as your variable doesn't use an enum type and you're merely using a type-less enum collection of constant values to assign to your unsigned char variables.


If you are afraid that the compiler might promote a comparison between a byte variable and an enum constant into a full integer comparison, which it does if you disable optimization, then be aware that the same is done for comparisons with macro constants as well.

Even this:

Code: Select all

unsigned char variable;

void __fastcall__ function(void)
{
	if (variable == '\2')
		variable = '\1';
}
is promoted to integer operations if you disable optimizations.

So, changing a bunch of enums to a bunch of defines alters not a single operation in the entire Assembly code in cc65 and is therefore 100 % unnecessary if you really want incrementing values.

But using enums can make the code much, much safer if you're actually working on real code with the currently available technology instead of losing yourself in pity restrictions of theoretical nature.


And here's a practical applicaton of enums that cannot be easily replicated with defines:

Code: Select all

#define ALL_ENEMY_NAMES(postfix) \
	Bat##postfix, \
	Oger##postfix, \
	Dragon##postfix

enum { ALL_ENEMY_NAMES(Id) };

#define BatMaxEnergy 1
#define OgerMaxEnergy 5
#define DragonMaxEnergy 100

const unsigned char EnemyMaxEnergies[] = { ALL_ENEMY_NAMES(MaxEnergy) };
Now you don't need to worry about the number or order of the enemies.

This will always work:

Code: Select all

CurrentEnemyId = GetIdFromWhateverSource();

CurrentMaxEnergy = CurrentMaxEnergies[CurrentEnemyId];
If you add an enemy in the ALL_ENEMY_NAMES macro, for example Demon, even if you put it in the middle of the other enemies and not at the end, the compiler will automatically:

1. Tell you that it doesn't know a constant named DemonMaxEnergy. So, the compiler will tell you every missing enemy property. (To be fair, this is done regardless, independent from the enum vs. define question.)
(By the way, the enemy properties themselves (BatMaxEnergy etc.) don't need to be declared in a specific order.)

2. Update all enemy IDs to the new correct values. (This is the advantage of enums.)


If you avoid enums, your IDs would have to be defined manually and you would have to keep the order in ALL_ENEMY_NAMES in sync with the manually declared ID values:

Code: Select all

#define ALL_ENEMY_NAMES(postfix) \
	Bat##postfix, \
	Oger##postfix, \
	Dragon##postfix

#define BatId 0
#define OgerId 1
#define DragonId 2
Do you really want this to be a manual task? Making sure that 80 enemy types in the macro are always in the same order as their manually-declared ID values?

Furthermore, imagine if you have 100 character types and you want to order them logically. Maybe your list requires the 20 friendly NPCs to come first and then the 80 enemies:
Have fun adding a new NPC somewhere at the top and having to redefine the ID values of all 80 enemies. Have fun doing this a few times during development.

With enums, those IDs are always guaranteed to be unique and in the same order as the values in the property arrays.


And that's all I have to say about it.

From here on, if someone thinks enum values have to be avoided because of...reasons, what do I care if he makes development harder for himself, just to satisfy some abstract restriction that doesn't improve his ROM output by one single byte, but that can potentially break his game logic if he accidentally sets a wrong value in a manually organized code block that could have been automated.
My game "City Trouble": www.denny-r-walter.de/city.htm

tepples
Posts: 22049
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: cc65 - Are % and * supported?

Post by tepples » Sat Aug 22, 2020 12:21 pm

It's not just theoretical. Some compilers other than cc65 indeed optimize access to const unsigned char and const int values whose definition is in the same translation unit. From ravi's answer to "What kind of optimization does const offer in C/C++?" (license: CC BY-SA 3.0):
When you declare a const in your program,

Code: Select all

int const x = 2;
Compiler can optimize away this const by not providing storage to this variable rather add it in symbol table. So, subsequent read just need indirection into the symbol table rather than instructions to fetch value from memory.

Note: If you do something like:

Code: Select all

const int x = 1;
const int* y = &x;
Then this would force compiler to allocate space for x. So, that degree of optimization is not possible for this case.
Let's see what I can settle in Matt Godbolt's Compiler Explorer. Starting with this code:

Code: Select all

const int optme = 3;

int square(int num) {
    return num * num;
}

int prsquare(void) {
  return square(optme);
}
When I compile this to x86-64 code using either GCC 9.2 (link) or Clang 10.0.1, the compiler optimizes out the variable optme as if the line were return square(3);. Likewise when I compile to x86 code using DJGPP 7.2. Compiling to 68000 using the franke.ms fork of Compiler Explorer (link) has the same behavior. And this is with the optimizer off; enabling even minimal optimizations (-O) just turns the whole thing into return 9;.

Unfortunately cc65 does not perform this optimization (link). Adding -O does a tail call optimization, but it still generates and reads optme. -Oi inlines a jsr ldaxysp, and -Oirs doesn't do anything additional in this case. So blunt language aside, DRW is right about using enum in cc65.

User avatar
DRW
Posts: 1982
Joined: Sat Sep 07, 2013 2:59 pm

Re: cc65 - Are % and * supported?

Post by DRW » Sat Aug 22, 2020 1:30 pm

tepples wrote:
Sat Aug 22, 2020 12:21 pm
It's not just theoretical. Some compilers other than cc65 indeed optimize access to const unsigned char and const int values whose definition is in the same translation unit.
That some compilers can provide more optimization than others is of course a given and you should always check which compiler does what.
When I said it's a purely theoretical issue, I was exclusively talking about the specific sentinent about enums vs. defines in regards to the cc65 compiler, not about optimization in general.

You're correct: When it comes to const int or const unsigned char stuff, you do need to keep in mind that the cc65 compiler doesn't optimize this away, so you're better off using the value in a define (or an enum).
My game "City Trouble": www.denny-r-walter.de/city.htm

turboxray
Posts: 104
Joined: Thu Oct 31, 2019 12:56 am

Re: cc65 - Are % and * supported?

Post by turboxray » Sat Aug 22, 2020 7:47 pm

DRW wrote:
Sat Aug 22, 2020 8:49 am
And that's all I have to say about it.
You still completely miss the point. Wow. hahah.

Post Reply