CA65: zp -> abs when using nested .procs

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
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

CA65: zp -> abs when using nested .procs

Post by GradualGames »

In the CA65 wiki, there's an article on using symbols in local scopes. In some cases, using local scope makes the assembler assume absolute mode when you really wanted zp. If you assemble the following nonsense program (no flags required just ca65 the_below_code.asm), you'll get a range error because the sta b0 instructions all assemble to absolute mode:

Code: Select all

.globalzp b0

.proc test
  
  lda b0
  
.proc nested_proc

  beq label
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  sta b0
  .repeat 86
  nop
  .endrepeat
  
label:

  rts

.endproc
    
.endproc
To correct this, it appears you can do a couple of things. One, suggested by the wiki, is to add the global scope specifier in front of the use of your symbol:

Code: Select all

.globalzp b0

.proc test
  
  lda ::b0

....
What I find odd is, I only have to do this once (such as the first time the symbol is used). Another way to correct the issue is to remove that nested .proc and just use a normal label.

What's further odd is, I remember using nested .procs for scope in the Nomolos codebase. Yet, I haven't yet found a place where CA65 is assembling to absolute mode where I'm expecting zeropage instructions.

Anyone understand this quirk of CA65 scope better than I do? Posting this will probably just make nesasm and asm6 adherants happy that they never made the switch :)
Last edited by GradualGames on Sun Aug 06, 2017 5:43 pm, edited 1 time in total.
User avatar
Dwedit
Posts: 4922
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

Sounds like a bug to me. I'm surprised that it would have those kind of problems.

ASM6 is aggressive about using zeropage mode whenever possible. It also uses a 3-pass system just in case it can't decide whether or not to use zeropage mode at the appropriate times. Deciding whether or not to use zeropage mode instructions is usually obvious, but sometimes it isn't. Especially once you support external symbols.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Post by thefox »

The reason this happens is that CA65 doesn't know whether "b0" is referring to the global symbol, or a symbol of the same name in the current scope which could be defined later. This has been discussed several times in the CC65 mailing list: http://www.cc65.org/mailarchive/2011-07/9539.html

I agree it's kind of unfortunate that it behaves like this, especially because most of the time it will not give a warning when it ends up using absolute addressing for zeropage variables. This is actually the reason that I added warning messages to NintendulatorDX whenever a game uses absolute addressing when zeropage could be used.

A case could be made that it would make more sense for CA65 to just use the global symbol, and if a symbol with the same name is defined later in the scope, just error out, because the case that CA65 is trying to protect against (having a global and local symbol with the same name) is so uncommon.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by GradualGames »

This old thread is making me wish asm6 and ca65 would have a baby together. It'd be the best assembler ever.

It looks like there are some funny edge cases ca65 has from being a one pass assembler, that a multi pass assembler can (apparently) easily resolve. Amusing that the author states they made ca65 a one pass assembler because it would be "more fun." Haha. Yeah maybe, but also more annoying to use perhaps :lol:
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by rainwarrior »

Just to clarify, because this thread looks slightly scarier until I checked this for myself, but the problem is restricted to nested scopes. A top level .proc or .scope does not suffer from this problem, so far as I can tell.
User avatar
tokumaru
Posts: 12427
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: CA65: zp -> abs when using nested .procs

Post by tokumaru »

When I first got started with ca65 I was very excited and made heavy use of scopes in attempts to encapsulate stuff and make my code more organized. I soon ran into all kinds of annoying little issues that made me revert to using only one level of scoping.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by GradualGames »

tokumaru wrote:When I first got started with ca65 I was very excited and made heavy use of scopes in attempts to encapsulate stuff and make my code more organized. I soon ran into all kinds of annoying little issues that made me revert to using only one level of scoping.
Yeah I think I'm headed that direction. I'm currently using .scope ... .endscope rather liberally just for local labels, but I think that's probably been overkill when I can just use cheap locals or :.

To be honest though I think I'm mainly suffering from OCD today, wishing I could have *absolute* (heh) control over whether the assembler is emitting zp or absolute in all cases. With the workarounds I've come up with for the few instances where this is happening, I'm seeing very little performance gain. And not that huge of a savings in code size either.

Despite its flaws, I can't imagine working without ca65's benefits, still, particularly the clunky but very useful macro system.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by thefox »

rainwarrior wrote:Just to clarify, because this thread looks slightly scarier until I checked this for myself, but the problem is restricted to nested scopes. A top level .proc or .scope does not suffer from this problem, so far as I can tell.
Hmm, might be that I'm too out of touch with ca65 (haven't coded much 6502 in a long time), but that seems really strange.

I tried a few things:

Code: Select all

.globalzp foo
.proc bar
    lda foo ; 2 bytes
.endproc

Code: Select all

.globalzp foo
.proc bar
    lda foo ; 2 bytes
    .proc baz
        lda foo ; 3 bytes
    .endproc
.endproc

Code: Select all

.globalzp foo
.proc bar
    .proc baz
        lda foo ; 2 bytes
    .endproc
.endproc
I can kind of understand the logic for why it sometimes pessimistically produces absolute addressing (due to the reasoning in my previous post in this thread), but why would the same logic not affect top-level scopes?
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by rainwarrior »

GradualGames wrote:To be honest though I think I'm mainly suffering from OCD today, wishing I could have *absolute* (heh) control over whether the assembler is emitting zp or absolute in all cases.
You can:

Code: Select all

	lda z:var ; generates ZP instruction, generates an error if var is >= $100
	lda a:var ; generates ABS instruction
And a really weird kicker to this: if you use z: in a nested scope it will manage to generate the ZP instruction.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by GradualGames »

rainwarrior wrote:
GradualGames wrote:To be honest though I think I'm mainly suffering from OCD today, wishing I could have *absolute* (heh) control over whether the assembler is emitting zp or absolute in all cases.
You can:

Code: Select all

	lda z:var ; generates ZP instruction, generates an error if var is >= $100
	lda a:var ; generates ABS instruction
And a really weird kicker to this: if you use z: in a nested scope it will manage to generate the ZP instruction.
I was experimenting with the a: and z: prefixes today, in combination with the .addrsize function. However I get "address size unknown" warnings. And, with my macro system I might be passing in zp vars or ram vars, it's not guaranteed to be zp.

One thing I'm considering is simply prefixing or suffixing ALL zp symbols throughout my entire game with z_ and detecting this with the string macro technique in this thread, to determine whether to use z: or a:. That just feels like overkill though to be honest. I think I can live with imperfect assembly given how little performance or code size improvement I seem to be getting with workarounds in place anyhow. The productivity benefits from the macro system I've come up with have been utterly huge (for me, no idea if they seem like a hacky mess to others lol).
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by rainwarrior »

Ah, I think the "rule" it's applying is that it will only search for existing symbols one level higher.

Code: Select all

	var0 = $33
	.proc first
		lda var0 ; assumes ZP
		var1 = $55
		.proc second
			lda var0 ; assumes ABS
			lda var1 ; assumes ZP
		.endproc
	.endproc
So, that at least makes it a consistent behaviour, but still unintuitive. Trying to think what I'd want... a recursive search instead of a one-level search would seem OK to me, I don't see how that's logistically much different (i.e. still "importing" stuff from higher scopes). It already gives a range error if you redefine a symbol locally with a larger size, so that's not really a problem either.

Can anyone think of any new problems a recursive search would cause over the existing one-level search?
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by rainwarrior »

GradualGames wrote:One thing I'm considering is simply prefixing or suffixing ALL zp symbols throughout my entire game with z_ and detecting this with the string macro technique in this thread, to determine whether to use z: or a:.
I don't really understand how typing z_ every time you use a variable is going to be more convenient than typing z: every time you use it? (Especially if you're going to encumber it with complicated macros...)
GradualGames wrote:That just feels like overkill though to be honest. I think I can live with imperfect assembly given how little performance or code size improvement I seem to be getting with workarounds in place anyhow.
Well, I'd tend to think of the uses without z: or a: prefix as an "assembler's choice" option, i.e. I don't really care, and might want to move variables around later anyway. That's generally my default position, because most ZP use isn't mission critical.

In general, though, I end up looking directly at the generated code often enough when debugging, so I'm usually checking up on the assembler enough to know where it's succeeding or failing at stuff like this. That's enough to inform me of various do's and don't for keeping the output like I expect it to be.

...and when I'm looking at some really performance critical code, I look at the output closely. Focused attention where it's needed.


I never knew about this nested scopes problem because I've only ever used .scope or .proc but never wanted to put a .proc inside a .scope. TBH I think the benefits of .proc are somewhat marginal anyway, and .scope I mostly used when I was dealing with .include of lots of files into one big assemble, but normally these days I encapsulate things by assembling indvidual files instead.
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by GradualGames »

rainwarrior wrote:
GradualGames wrote:One thing I'm considering is simply prefixing or suffixing ALL zp symbols throughout my entire game with z_ and detecting this with the string macro technique in this thread, to determine whether to use z: or a:.
I don't really understand how typing z_ every time you use a variable is going to be more convenient than typing z: every time you use it? (Especially if you're going to encumber it with complicated macros...)
[
I attempted to pass in z: to my macros but this resulted in errors...not sure why yet. If I can resolve those then my proposed hack with z_ would be unnecessary.
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by thefox »

rainwarrior wrote:Ah, I think the "rule" it's applying is that it will only search for existing symbols one level higher.
I tried to debug this a little bit, and I think that may not be the case.

It seems like the reference to var0 in the scope first creates a symbol with flag SF_REFERENCED (in that scope), however that symbol doesn't have an address size associated to it. So it finds the symbol (reference), but later notices it doesn't have an address size and so uses the default (abs).

As a quick hack I tried to make it ignore SF_REFERENCED symbols (in addition to SF_UNUSED that's there already), and it seems to do the "right thing", but I'm not familiar enough with the code to know whether this change might have some adverse effects.

The relevant code:
https://github.com/cc65/cc65/blob/maste ... xpr.c#L580 This calls SymFindAny
https://github.com/cc65/cc65/blob/maste ... tab.c#L428 SymFindAny does search recursively.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: CA65: zp -> abs when using nested .procs

Post by tepples »

GradualGames wrote:I was experimenting with the a: and z: prefixes today, in combination with the .addrsize function. However I get "address size unknown" warnings. And, with my macro system I might be passing in zp vars or ram vars, it's not guaranteed to be zp.
In your macros, try .assert arg < $0100, error, "address must be direct page" to ensure that the address is suitable and then use <arg to short-circuit range checking.


EDIT: Removed a misguided reply to rainwarrior based on reading his comment too fast
Post Reply