Page 1 of 1

Local Labels

Posted: Tue Dec 16, 2025 3:44 am
by sburrow
Hey fellow 6502 fans :)

I got a question for y'all. I want to know what y'all do.

Here is my dilemma: I want to have local labels. What does that mean? First, let's do a coding example (from TobuNES):

Code: Select all

compute_patrol_one
	; move enemies horizontally
	LDA anim_cnt
	AND #$01
	BNE compute_patrol_seven
	LDX #$00
compute_patrol_two
	LDA enem_page+1,X
	CLC
	ADC enem_page+6,X
	STA enem_page+1,X
	CMP #$B0 ; right border
	BCC compute_patrol_three
	LDA #$FF
	STA enem_page+4,X
	STA enem_page+6,X
compute_patrol_three
	CMP #$20 ; left border
	BCS compute_patrol_four
	LDA #$01
	STA enem_page+4,X
	STA enem_page+6,X

compute_patrol_four
	; check for sway
	LDA enem_page+7,X
	BEQ compute_patrol_six
	LDA enem_page+7,X
	CLC
	ADC enem_page+6,X
	STA enem_page+7,X
	CMP #$88 ; one block right
	BCC compute_patrol_five 
	LDA #$FF
	STA enem_page+4,X
	STA enem_page+6,X
compute_patrol_five
	CMP #$78 ; one block left
	BCS compute_patrol_six
	LDA #$01
	STA enem_page+4,X
	STA enem_page+6,X

compute_patrol_six
	; repeat for all enemies
	TXA
	CLC
	ADC #$08
	TAX
	CMP #$80
	BNE compute_patrol_two

compute_patrol_seven
	RTS
This is inside my 'compute' sub-routine, under the 'patrol' section. See how I'm labeling them _one, _two, etc? What if I wanted to add code with another label between _four and _five? I would then have to rename all of the _five to _six, and then _six to _seven, all the way down, and then change all the branch locations likewise. Very annoying!

I know that I could use +, ++, -, --, and so on. But to me, that can get confusing very quickly. And if I insert something in between + and ++ then I have to make ++ into +++ and change the branches accordingly. Honestly I'd rather change _five to _six instead, as it is a bit less error prone, seeing that there might be another + or ++ ahead in code and it all seems to blend together.

Also I could use more sub-routines. That would make the main section more clean to look at, and I wouldn't have to worry so much about all of those labels. However, I am also programming sequentially, and I want to keep it all together as much as possible. When I'm trying to see the logic, having it all in the same place is very important.

What I would WANT to do is something with { }. Let's do a proto-code example for what I'm talking about:

Code: Select all

; first loop
{
LDX #$00
LDA #$00
loop:
STA page_one,X
INX
BNE loop
}

; second loop
{
LDX #$00
LDA #$80
loop:
STA page_two,X
INX
BNE loop
}
These are not exhaustive examples, just wanted to show you the concept. Any label inside of { } does not come outside of it. I couldn't "JMP loop" from outside of those { } because it wouldn't see the "loop" label. I think (though I am not entirely sure) that that would help me with my labels issue. Maybe. It would work similar to + and ++, but it would be in English, so that I can read it logically still.

What do y'all do? How would you tackle a problem like this? I want to know what strategies you all use. After this current project I'm on, I am *highly* considering using CC65 so that I can code 'normally' (aka in C) again. I absolutely love programming in 6502 assembly, but this label issue just makes my code start looking like spaghetti over time.

Thoughts? Ideas? Hints?

Thank you everyone. Hopefully my question is clear enough.

Chad

Re: Local Labels

Posted: Tue Dec 16, 2025 5:37 am
by barnacle
You put your finger on my biggest annoyance with assembly: the lack of local labels (and incidentally, local storage for variables). I like the C version where whatever is outside { } is global and everything inside { } is local and were I to write an assembler this is something I would try and implement.

The assembler I use allows local labels within macros:

Code: Select all

INC16		macro address	; increment a 16-bit pointer
			local skip
			inc address
			bne skip
			inc address+1
skip:
			endm
but does not extend that courtesy to inline code.

I've used assemblers in the past - and I can't remember which, perhaps TASS? - which allowed local labels, but I can't recall how they did it.

Code: Select all

f_create:
	; step one: check it doesn't exist
	pha
	phy
	jsr puts
	ply
	pla
	jsr crlf

	jsr str_to_83			; get the 8.3 version	
	jsr fs_find_first		; seek it
f_cr_01:
		lda (fs_dir_ptr)	; check the first character of name
		beq f_cr_03			; quit if it's zero
		jsr fs_match_83
		beq f_cr_02			; found it!
		jsr fs_find_next
		bra f_cr_01			; until no more records
f_cr_02:
	; the filename exists: quit with zero flag set
	rts						; FIXME one entry point one exit...
f_cr_03:
	; if we get here, the file does not exist so we can progress
	; step two: increment the pointer in FSInfo sector
	
	SHOWTRANS
so all my labels comprise an abbreviation of the function name and an incrementing number (here it's just by one, but more often it's by five or ten, like BASIC line numbering).

Neil

Re: Local Labels

Posted: Tue Dec 16, 2025 5:46 am
by BigDumbDinosaur
barnacle wrote:
You put your finger on my biggest annoyance with assembly: the lack of local labels (and incidentally, local storage for variables).
The Kolwalski assembler supports local labels and by extension, local symbols and local variables.

Re: Local Labels

Posted: Tue Dec 16, 2025 7:17 am
by JohanFr
Kick assembler supports local labels as well as scopes. It's very powerful.

(As a side note, I found its segments feature to be useful for building cartridges)

Code: Select all

  lda #42 
  sta routine1.xx
  jsr routine1
  jsr routine2
  jsr routine3
  jmp *

routine1: {
   lda xx
   beq !dostuff+
   sta yy
!dostuff:
   lda yy
   ...
!loop:
   ...
   iny
   bne !loop-
   rts

xx:  .byte 0
yy: .byte 0
}

routine2:
   ldx #<!msg+
   ldy #>!msg+
   jsr OS_PRINT
   beq !dostuff+
   ...
!dostuff:
   ...
   rts
!msg: 
  .text "HELLO WORLD"

routine3:
   ldx #<!msg+
   ldy #>!msg+
   jsr OS_PRINT
   ...
!msg:
  .text "MORE TEXT"

Re: Local Labels

Posted: Tue Dec 16, 2025 7:20 am
by John West
I use { } to introduce new scopes, as you suggest. It works very well. I can have meaningful labels that don't have to change as code is added, and those labels don't need ugly (to me) annotations to say that they're local. Most labels are local, and I don't like having to do extra for the common case.

Scopes can be nested. In the file that implements some module, I'll have the whole thing wrapped in a scope, and introduce a new scope for each routine. Previously I used a .global directive to push labels out of the module scope so they're visible from everywhere, like this:

Code: Select all

; The "fp" module
{
  .global fp_add
  {
    beq zero
    ; do something
  zero
    ; do something else
    rts
  }
}
That's been working well.

I have recently introduced named scopes, which external code can see into.

Code: Select all

{ "fp"
  add
  {
    beq zero
    ; do something
  zero
    ; do something else
    rts
  }
}
Then external code can refer to fp.add. The inner scope isn't named, so zero is not accessible outside it. That's a very new addition and I haven't had much experience with it yet. I suspect I'll be rewriting a lot of code to use that style and .global will disappear.

Re: Local Labels

Posted: Tue Dec 16, 2025 7:26 am
by JohanFr
Hm I made a mistake in saying they are local labels. They are multi labels, meaning they can be declared more than once, which is handy for something like "!msg" or "!loop". But you would have to specify the next or the previous label using "-" or "+". So they are not truly local in that sense (you can reference a multi label over a normal label boundary) but I still find them very useful for generic label names where you don't want to use anonymous labels (!: xx, jmp !+ etc in the Kick assembler world)

Re: Local Labels

Posted: Tue Dec 16, 2025 9:03 am
by GARTHWILSON
BDD commented that in the HCD65 assembler, "A local label was defined by appending a dollar sign to the label name, e.g., EXIT$. Its scope was bounded by the nearest global labels," and that the Kowalski assembler implements local labels by making any label that starts with a period local to the scope between the two nearest global labels.  The 2500AD assembler I used in the 1980's used the $ after the name to mean local.

Chad and I have already talked about this on the side, but I'll present this anyway.  Using my program flow control structure macros, I would write it something like the following, eliminating the need for local labels.  Hopefully I didn't make any mistakes in the conversion.  It's one byte less, because although the RTS_IF_NEQ near the beginning assembles a BEQ around an RTS (instead of BNE to the RTS at the end), I removed the CMP #$80 near the end saving two bytes, and just branched on the N flag.  Unless I made a mistake somewhere, the rest will assemble exactly the same result.

Code: Select all

compute_patrol_one:
   LDA  anim_cnt                 ; move enemies horizontally
   AND  #1
   RTS_IF_NEQ


   LDX  #0
   BEGIN                         ; compute_patrol_two
      LDA  enem_page+1,X
      CLC
      ADC  enem_page+6,X
      STA  enem_page+1,X


      CMP  #$B0                  ; right border
      IF_CARRY_SET
         LDA  #$FF
         STA  enem_page+4,X
         STA  enem_page+6,X
      END_IF


      CMP  #$20                  ; compute_patrol_three -- left border
      IF_CARRY_CLR
         LDA  #1
         STA  enem_page+4,X
         STA  enem_page+6,X
      END_IF


      LDA  enem_page+7,X         ; compute_patrol_four -- check for sway
      IF_NOT_ZERO
         LDA  enem_page+7,X
         CLC
         ADC  enem_page+6,X
         STA  enem_page+7,X


         CMP  #$88               ; one block right
         IF_CARRY_SET
            LDA  #$FF
            STA  enem_page+4,X
            STA  enem_page+6,X
         END_IF


         CMP  #$78               ; compute_patrol_five -- one block left
         IF_CARRY_CLR
            LDA  #1
            STA  enem_page+4,X
            STA  enem_page+6,X
         END_IF
      END_IF


      TXA                        ; compute_patrol_six -- repeat for all enemies
      CLC
      ADC  #8
      TAX
   UNTIL_NEG
   RTS
 ;-------------

Re: Local Labels

Posted: Tue Dec 16, 2025 9:15 am
by barnacle
My thought for local variables is something which exists only in the scope of { }. C puts them on the (nominal) stack, so when the stack unwinds, the variables go away, but that's not really practical for 65c02.

But some smart bss manipulation might be used so the bss counter goes up and down as compilation proceeds to potentially reduce use of zero page locations (or even main locations) - it would have to know, or be told, that a variable is _strictly_ local and not required after this routine ends. Needs more thinking.

Neil

Re: Local Labels

Posted: Tue Dec 16, 2025 9:34 am
by drogon
ca65 from the cc65 suite is designed to support labels and blocks of code exactly like C does to give you local labels/variables and globals.

You can also have macros with local labels too.

In ca65 you have blocks of code inside .proc / .endproc - and all labels defined inside that block are local to that block. The .proc also declares a label, so you can JSR to it. If you have setup and the means to use segments (.text, .data, .zp) and so on then it's possible to allocate variables in this way too.

Also - temporary labels - a colon on it's own is a temporary/anonymous label and can be referred to as :+ or :- to jump to the next forward or backward reference. Handy for very short loops or if/then/else sort of construct.

-Gordon

Re: Local Labels

Posted: Tue Dec 16, 2025 8:41 pm
by SamCoVT
You mention TobuNES and the docs for that mention asm6 as the assembler, and the docs for that say:

Code: Select all

--------------------------------------------------------------
Labels
--------------------------------------------------------------

Labels are case sensitive.  The special '$' label holds the current program
address.  Labels beginning with '@' are local labels. They have limited scope,
visible only between non-local labels.  Names of local labels may be reused.

        label1:
          @tmp1:
          @tmp2:
        label2:
          @tmp1:
          @tmp2:
Are the local labels that start with an @ what you are looking for? You could use a real label at the beginning of your routine and then local labels for all the steps in between.

Also, if you write your label names based on what they do, rather than just numbering them, then you won't have numbering issues when you need to insert some code. Here's an example (my assembler uses _ on the front for local labels) - it's from a Forth so it's using X to access a data stack and it doesn't really matter how it works, but you you should be able to see it's printing out spaces in a loop:

Code: Select all

w_spaces:
                lda 1,x         ; ANS says this word takes a signed value
                bmi _done       ; but prints no spaces for negative values.

                ldy 0,x
                beq _msb
_loop:                          ; loop to zero out LSB
                lda #AscSP
                jsr emit_a      ; user routine preserves X and Y
                dey
                bne _loop       ; Y is zero on exit so looping again emits 256 more spaces
_msb:
                dec 1,x         ; when decrementing MSB goes negative, it was zero so we're done
                bpl _loop       ; otherwise emit another 256 spaces

_done:          inx
                inx
z_spaces:       rts
--- Edited to add extra example ---
and here's how I would probably label the routine you showed (note that I changed the name of the routine from "compute_patrol_one" to just "compute_patrol"):

Code: Select all

compute_patrol
   ; move enemies horizontally
   LDA anim_cnt
   AND #$01
   BNE @done
   LDX #$00

@compute_enemy
   LDA enem_page+1,X
   CLC
   ADC enem_page+6,X
   STA enem_page+1,X
   CMP #$B0 ; right border
   BCC @left_border
   LDA #$FF
   STA enem_page+4,X
   STA enem_page+6,X
@left_border
   CMP #$20 ; left border
   BCS @check_sway
   LDA #$01
   STA enem_page+4,X
   STA enem_page+6,X

@check_sway
   ; check for sway
   LDA enem_page+7,X
   BEQ @next_enemy
   LDA enem_page+7,X
   CLC
   ADC enem_page+6,X
   STA enem_page+7,X
   CMP #$88 ; one block right
   BCC @one_block_left
   LDA #$FF
   STA enem_page+4,X
   STA enem_page+6,X
@one_block_left
   CMP #$78 ; one block left
   BCS compute_patrol_six
   LDA #$01
   STA enem_page+4,X
   STA enem_page+6,X

@next_enemy
   ; repeat for all enemies
   TXA
   CLC
   ADC #$08
   TAX
   CMP #$80
   BNE @compute_enemy

@done
   RTS

Re: Local Labels

Posted: Tue Dec 16, 2025 10:54 pm
by fachat
Just for completeness, xa65 supports blocks with .( and .) As well as ca65 style @ and : local labels.
André

Re: Local Labels

Posted: Wed Dec 17, 2025 3:29 am
by sburrow
Wow! Thank you everyone for the replies! Great stuff here! I'm going to hit the highlights:
SamCoVT wrote:
Are the local labels that start with an @ what you are looking for?
PERFECT! Yes exactly! I will definitely need to try those out myself now. Thank you for spotting that so efficiently!
drogon wrote:
ca65 from the cc65 suite is designed to support labels and blocks of code exactly like C does to give you local labels/variables and globals.
Good to know! I am really thinking of trying it out.
John West wrote:
I use { } to introduce new scopes, as you suggest. It works very well.
What assembler are you using for that?
barnacle wrote:
You put your finger on my biggest annoyance with assembly: the lack of local labels (and incidentally, local storage for variables).
Haha, indeed. Glad to know I'm not the only one!
barnacle wrote:
so all my labels comprise an abbreviation of the function name and an incrementing number
That is a good idea!

So I just tested out SamCoVT's suggestion, and yeah, it works! I can use the same label inside of global labels (aka blocks). I also like the barnacle's idea to number then by 5's or 10's like in BASIC, if they do similar things. Or as also suggested, to now just name them what they do or where they go to, like how SamCoVT had demonstrated.

I'll be trying things out and seeing I like best. Thank you all so much for all of this! Very helpful going forward!

Chad

Re: Local Labels

Posted: Wed Dec 17, 2025 7:27 am
by John West
sburrow wrote:
John West wrote:
I use { } to introduce new scopes, as you suggest. It works very well.
What assembler are you using for that?
My own. Doesn't everyone write their own assembler? :-)

It does support a 65C02 target, but I use it mostly for the 65020. A consequence of designing your own processor is that no one else's software will support it - you have to do everything yourself.

Re: Local Labels

Posted: Wed Dec 17, 2025 8:15 am
by GlennSmith
Hi all,
Interesting topic/idea and is added to the wish list for my home-grown 65C02 assembler written in PLASMA.
I've had to 'pop the hood' (as you say over the pond) already - in writing some serious code (RA8875 display interfaced in // mode through the 6522 VIA) I found some unknown features in my assembler !
I think I'll use .PRO and .END compiler directives, as they are easily added. I need to find an extra bit in my hash/lookup tables to limit the scope, however.