ca65: cheap local labels weirdness

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

Post Reply
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

ca65: cheap local labels weirdness

Post by tokumaru »

Yesterday I was trying to do something using cheap local labels, and ran into something really weird. Here's a piece of code using cheap local labels:

Code: Select all

BlockStart:

@LocalLabel:
	.byte $47

BlockEnd:

	lda @LocalLabel
As expected, an error occurs when trying to lda @LocalLabel, because the label is no longer visible.

However, I was writing the global labels from within macros, where I was doing other tasks, and mysteriously enough, the local label became visible outside of the block! After hours of debugging, I found the culprit, a symbol assignment using .set. Then I reproduced the issue in a more concise way:

Code: Select all

BlockStart:
SomeSymbol .set 4

@LocalLabel:
	.byte $47

BlockEnd:
SomeSymbol .set 5

	lda @LocalLabel
In this case, the local label is accessible outside of the "block" where it was defined! Weird as fuck! I can only guess it's because the local label was created under "SomeSymbol", so when "SomeSymbol" is used again, the assembler thinks it's still the same block.

Sounds like a bug to me, because the "BlockEnd" label should be ending the scope where "@LocalLabel" is accessible, and you shouldn't be able to go back to that scope. What do you think? Is this a bug or a feature?

Another thing that's bothering me that also involves cheap local labels is that you can't "copy" a cheap local label before it's defined, like you can with other types of labels. For example, this works:

Code: Select all

.scope

GlobalCopy := LocalLabel

LocalLabel:
	.byte $47

.endscope

	lda GlobalCopy
But this doesn't:

Code: Select all

BlockStart:

GlobalCopy := @LocalLabel

@LocalLabel:
	.byte $47

BlockEnd:

	lda GlobalCopy
It only works if you make the copy after the label is defined:

Code: Select all

BlockStart:

@LocalLabel:
	.byte $47

GlobalCopy := @LocalLabel

BlockEnd:

	lda GlobalCopy
I wonder if the GlobalCopy := @LocalLabel line is also acting as a "cheap local scope" breaker, so that when used at the top it tries to access the scope created by "BlockStart", which is prematurely ended by the use of "GlobalCopy", so "@LocalLabel" actually exists in the next scope. I gotta do some tests to verify this.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: ca65: cheap local labels weirdness

Post by tokumaru »

I'm still profoundly weirded out by the first problem, and believe it's indeed a bug, but the second issue appears to be caused by any assignments to symbols that are not cheap locals causing a cheap local scope change. I used to think that only traditional labels (i.e. name followed by colon) did it, but assignments done via =, := and .set do it too. The documentation did say that "As soon as a standard symbol is encountered, the cheap local symbol goes out of scope.", but I wasn't sure what they meant by "encountered" in this context... Now I see that standard symbols can be used as arguments, but assignments will cause a scope change.
dullahan
Posts: 96
Joined: Mon Dec 07, 2009 11:08 am
Location: USA

Re: ca65: cheap local labels weirdness

Post by dullahan »

This is interesting. I've looked into the ca65 source a bit to figure out what's going on, but don't know for sure yet. The variable assignments (=, := and .set) are unique in that the accompanying symbol is replaced by the symbol's constant expression during assembly as I understand it (https://github.com/cc65/cc65/blob/709ee ... try.c#L263) and then defined here (https://github.com/cc65/cc65/blob/709ee ... try.c#L179), but why the variable assignment changes the current scope I don't understand...
Post Reply