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
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 »

tepples wrote:And unfortunately no "I care, but I'd like to be able to move the most commonly accessed variables into zero page and less commonly used variables out as part of a size optimization effort late in development once I start seeing MEMORY area overflow errors from ld65, and I don't know yet which variables those will be" option.
??? In my quote that you truncated I said I think of it as useful for that kind of thing like a couple words after you truncated it. ???
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 »

I'm trying to understand why my code is now successfully assembling zp instructions with nested scopes. Using the old example in this thread, I found if I declare an equate outside the nested scope and assign the zp variable in question to it, then I get zp instructions as inspected.

Coincidentally, the macro system I came up with for declaring zp equates here puts most of my code into this pattern, so I get the code I expect. Does anyone understand why this works around the issue?

This example will compile just fine, whereas the OP generates absolute instructions and a range error.
*edit* Perhaps the reason it works is that it ultimately works the same as though I typed ::b0 instead of using the equate my_b0. (the wiki article linked in the op refers to the :: operator to search the global scope)

Code: Select all

.globalzp b0

.proc test
  
  my_b0 = b0
  
  lda b0
  
.proc nested_proc

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

  rts

.endproc
    
.endproc
Last edited by GradualGames on Sun Aug 06, 2017 5:44 pm, edited 1 time in total.
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 »

I know that you're using all those nops to generate a range error, but it really hurts my ability to try and read this thread. A comment that says "generates a ZP instruction" or something would be sufficient, or even a .repeat to make it shorter.


In your new example "lda b0" is referencing b0, known from one scope above, and known to be ZP, so it's ZP.

Secondly "sta my_b0" is referencing my_b0, also known from one scope above. Since you're saying it correctly realizes it's ZP, I would guess it's inheriting the ZP property from b0 from that = assignment.

At least, that's presuming my belief that it looks for matching symbols from one scope above (but no deeper) when determining operand size is correct.

Edit: It's not correct, see thefox's explanation below.
Last edited by rainwarrior on Mon Aug 07, 2017 5:25 am, edited 1 time in total.
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:I know that you're using all those nops to generate a range error, but it really hurts my ability to try and read this thread. A comment that says "generates a ZP instruction" or something would be sufficient, or even a .repeat to make it shorter.


In your new example "lda b0" is referencing b0, known from one scope above, and known to be ZP, so it's ZP.

Secondly "sta my_b0" is referencing my_b0, also known from one scope above. Since you're saying it correctly realizes it's ZP, I would guess it's inheriting the ZP property from b0 from that = assignment.

At least, that's presuming my belief that it looks for matching symbols from one scope above (but no deeper) when determining operand size is correct.
Interestingly, I'm getting zp instructions as expected at least 2 scopes deep in cases I've inspected (been looking at one routine where I used a ton of scopes), using this pattern. You can even add further nested scopes in this example and it will still work.

*edit* Nevermind, I found a counterexample (using the example code. In my game's code, I'm still getting zp where I expect, for some reason.)

Code: Select all

000004r 1                 .scope
000004r 1  85 rr          sta my_b0
000006r 1  85 rr          sta my_b0
000008r 1  85 rr          sta my_b0
00000Ar 1  85 rr          sta my_b0
00000Cr 1  85 rr          sta my_b0
00000Er 1  85 rr          sta my_b0
000010r 1                 .scope
000010r 1  8D rr rr       sta my_b0
000013r 1  8D rr rr       sta my_b0
000016r 1  8D rr rr       sta my_b0
000019r 1  8D rr rr       sta my_b0
00001Cr 1                 .endscope
00001Cr 1  85 rr          sta my_b0
00001Er 1  85 rr          sta my_b0
000020r 1  85 rr          sta my_b0
000022r 1  85 rr          sta my_b0
000024r 1                 .endscope
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:Secondly "sta my_b0" is referencing my_b0, also known from one scope above. Since you're saying it correctly realizes it's ZP, I would guess it's inheriting the ZP property from b0 from that = assignment.

At least, that's presuming my belief that it looks for matching symbols from one scope above (but no deeper) when determining operand size is correct.
I think the first part is true (my_b0 gets flagged SF_DEFINED and the address size is derived from the expression), but the second part is not.

It's easy to disprove the theory that it only looks one scope above:

Code: Select all

.globalzp foo
start:
.scope
  .scope
    .scope
      .scope
        lda foo ; uses zp
      .endscope
    .endscope
  .endscope
.endscope
end:
.out .sprintf("size=%d", end-start)
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 »

Well, I've got no clue then. :(

Is nesting depths a red herring? Why does it happen in the case I wrote but not this one? Does the problem ever happen when scopes are just a single level?
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:Is nesting depths a red herring? Why does it happen in the case I wrote but not this one? Does the problem ever happen when scopes are just a single level?
In the case you wrote var0 was referenced in the first scope, so the reference itself gets an internal new symbol (with name var0) in the symbol table of that scope. Then in the inner scope second when it starts recursively looking for var0 in parent scopes, it finds that symbol reference, and uses it. But the reference doesn't have a size assigned to it, so it defaults to abs.

It seems like the fact that it even considers symbol references here could be a bug, but what do I know. At the very least the behavior is very inconsistent. The comment at https://github.com/cc65/cc65/blob/maste ... tab.c#L440 says that it ignores symbols with type SF_UNUSED because for such symbols "there is a real entry in one of the parent scopes". It seems that the same should be true for SF_REFERENCED. (That said, it's certainly possible to reference symbols which have not been defined yet...)

I don't think the problem can happen with single-level scopes:

Code: Select all

.globalzp foo ; "foo" gets added in the symbol table with SF_GLOBAL and address size=1.
lda foo ; Uses zp. SF_REFERENCED flag is added to the existing symbol foo (NOTE: SF_xxx is a bitmask).
.scope s
  lda foo ; Finds "foo" in the parent scope with proper size, so uses zp. A new symbol named "foo" with SF_REFERENCED is added in this scope.
.endscope
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 »

Oh! So the problem is that using a symbol from a parent scope creates some kind of "dummy" version of that symbol that might block the ZP property from propagating to any nested scopes?
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 »

It seems if I have an equate set to the global zp value at the top level or just one level down, it propagates as far as one would like. but if I put the equate itself inside any of the nested scopes, then I get abs.

Example with equate in inner scope (abs instruction generated)

Code: Select all

.globalzp b0

.scope

    lda b0

    .scope
        my_b0 = b0
        .scope
            .scope
                lda my_b0
            .endscope
        .endscope
    .endscope

.endscope

*****LISTING*****

000000r 1               .globalzp b0
000000r 1
000000r 1               .scope
000000r 1
000000r 1  A5 rr            lda b0
000002r 1
000002r 1                   .scope
000002r 1                       my_b0 = b0
000002r 1                       .scope
000002r 1                           .scope
000002r 1  AD rr rr                     lda my_b0
000005r 1                           .endscope
000005r 1                       .endscope
000005r 1                   .endscope
000005r 1
000005r 1               .endscope
000005r 1
000005r 1


Example with equate at top level scope (zp generated). *edit* Noticed moving "my_b0 = b0" to global scope also generates zp. Maybe the point is that .globalzp doesn't define a symbol but only a property a symbol should have ONCE defined. So if you define them at the top level or global scope, you're fine. This makes me quite happy with my zp_equates macros, as I apparently accidentally solved this problem for myself, just didn't realize it was this thoroughly solved til now. Haha!

Code: Select all

.globalzp b0

.scope

    my_b0 = b0

    lda b0

    .scope
        .scope
            .scope
                lda my_b0
            .endscope
        .endscope
    .endscope

.endscope

*****LISTING*****

000000r 1               .globalzp b0
000000r 1
000000r 1               .scope
000000r 1
000000r 1                   my_b0 = b0
000000r 1
000000r 1  A5 rr            lda b0
000002r 1
000002r 1                   .scope
000002r 1                       .scope
000002r 1                           .scope
000002r 1  A5 rr                        lda my_b0
000004r 1                           .endscope
000004r 1                       .endscope
000004r 1                   .endscope
000004r 1
000004r 1               .endscope
000004r 1
000004r 1


*edit* Amusingly the documentation pretty much spells this out. "Of course the most simple solution for the problem is to move the definition of foo in scope inner upwards, so it precedes its use." But, one doesn't think about this normally. One would like to just use an include with a .globalzp my_symbol and just use it and know it is zp. But re-creating an equate seems like a fine workaround to me.
Last edited by GradualGames on Mon Aug 07, 2017 5:55 am, edited 3 times in total.
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:Oh! So the problem is that using a symbol from a parent scope creates some kind of "dummy" version of that symbol that might block the ZP property from propagating to any nested scopes?
Yeah, it kind of makes sense and kind of doesn't. In the single-level scope example in my previous post, I guess it's adding the "dummy" reference symbol in case a symbol named "foo" gets defined later in the same scope (if it does get defined, SF_DEFINED would be added to the "dummy" symbol). What doesn't make so much sense is the fact it still uses the address size from "foo" from the parent scope. So it's kind of pessimistic by making the assumption that it might get redefined, but then still uses information from the parent scope's symbol.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
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 »

Since creating an equate and assigning a .globalzp'd symbol seems to propagate "this is zp" as far down in scopes as one would like, does anyone think it might be worthwhile to propose adding something like this to ca65?

Code: Select all

.globaldefzp b0    ;when used as an import, will import the symbol AND re-define it at the global scope of the current module
since going:

Code: Select all

.globalzp b0
my_b0 = b0  ;at global scope
seems to allow my_b0 to generate zp instructions as deep as one likes?
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 »

GradualGames wrote:

Code: Select all

.globalzp b0
my_b0 = b0  ;at global scope
seems to allow my_b0 to generate zp instructions as deep as one likes?
It doesn't work like that. As soon as you reference my_b0 in one of the scopes, the further nested scopes will find the reference (not the global-level my_b0), and not use the correct size.

Code: Select all

.globalzp b0
my_b0 = b0  ;at global scope
.scope s1
  start1:
  lda my_b0 ; size=2
  end1:
  .scope s2
    start2:
    lda my_b0 ; size=3
    end2:
  .endscope
.endscope
.out .sprintf("size=%d", s1::end1 - s1::start1)
.out .sprintf("size=%d", s1::s2::end2 - s1::s2::start2)
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
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 »

So...if I only use these equates ONCE anywhere in my nested scopes, it's working by coincidence for me? I think that may be what's really going on, given what you're saying.

It appears one can work around this with the :: operator and redefine the symbol with the same name. Perhaps we could propose adding a directive that modifies all nested scopes' symbol tables to behave as though one did this manually. or a .feature or something.

Code: Select all

.globalzp b0
my_b0 = b0  ;at global scope
.scope s1
  start1:
  lda my_b0 ; size=2
  end1:
  .scope s2
    my_b0 = ::my_b0
    start2:
    lda my_b0 ; size=2 because we used :: to redefine the symbol for this scope
    end2:
  .endscope
.endscope
.out .sprintf("size=%d", s1::end1 - s1::start1)
.out .sprintf("size=%d", s1::s2::end2 - s1::s2::start2)

****OUTPUT****

$ ca65 scope_test.asm -llisting.lst
size=2
size=2

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:Perhaps we could propose adding a directive that modifies all nested scopes' symbol tables to behave as though one did this manually. or a .feature or something.
I think it's just flat out a bug that should be fixed, not a new feature that should be added.
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:Perhaps we could propose adding a directive that modifies all nested scopes' symbol tables to behave as though one did this manually. or a .feature or something.
I think it's just flat out a bug that should be fixed, not a new feature that should be added.
It is really weird isn't it. Like, how many times is somebody going to import a global variable from zp and then want to use the exact same name afresh in some inner scope AND generate a non zp instruction from it? I'm guessing pretty much never. :lol: I can't think of anything except bad naming habits that would get one into that situation normally.
Post Reply