* * * * *

                              Complicating code

I recently added an .OPT directive to my 6809 assembler [1]. This allows me
to add options to a source file instead of having to always specify them on
the command line. I originally did this to support unit testing [2] and I
feel it's a nice addition.

When the test backend is enable, all the memory of the emulated 6809 is
marked as non-readable, non-writable, non-executable. As the code is
assembled, memory used by instructions are switched to “readable, executable”
and memory used by data becomes “readable, writable” (easy, because of an
early decision to have separate functions to write instructions vs. data). If
you reference memory outside of the addresses used by the source code being
assembled, you have to specify the permissions of said addresses.

-----[ Assembly ]-----
; The set up for our tests.
; We should only read from these locations

       .opt    test prot r,ECB.beggrp,ECB.beggrp + 1
       .opt    test prot r,$112

; We can read and write to these locations

       .opt    test prot rw,$0E00,$0E00 + 1023

; Set the stack for our tests

       .opt    test stack $FF00

; Initialize some VM memory

       .opt    test memw,ECB.beggrp,$0E00
       .opt    test memb,$112,2
-----[ END OF LINE ]-----

You really only need these for memory locations defined outside the source
code. In the example above, the memory referenced is defined by the Color
Computer and not by the code being tested, so they need to be initialized.
And because the system only supports 65,536 bytes of memory, we can easily
(on modern systems) assign permissions per byte.

So this all works and is great at finding bugs.

But then I thought—I can use .OPT to supress warnings as well. If I assemble
the following code:

-----[ Assembly ]-----
;**************************************************************************
;       frame_buffer            set frame buffer address        (GPL3+)
;Entry: A - MSB of frame buffer
;Exit:  D - trashed
;**************************************************************************

frame_buffer    ldb     PIA0BC          ; wait for vert. blank
               bpl     frame_buffer
now             stx     ,--s            ; save X
               ldx     #SAM.F6         ; point to framebuffer address bits
setaddress      clrb                    ; reset B
               lsla                    ; get next address bit
               rolb                    ; isolate it
               stb     b,x             ; and set the SAM F bit
               leax    -2,x            ; point to next F bit register
               cmpx    #SAM.F0         ; more?
               bhs     .setaddress     ; more ...
               puls    x,pc            ; return
-----[ END OF LINE ]-----

I get

-----[ data ]-----
frame_buffer.asm:9: warning: W0002: symbol 'frame_buffer.now' defined but not used
-----[ END OF LINE ]-----

The subroutine has effectively two entry points—frame_buffer will wait until
the next vertical blanking interrupt before setting the address, while
frame_buffer.now will set it immediately. The former is good if you are using
a double-buffer system for graphics, while the later is fine for just
switching the address once. Given that my assembler will warn on unused
labels, this means I'll always get this error when including this code. I can
supress that warning by issuing a -nW0002 on the command line, but then this
will miss other unused labels that might indicate an actual issue.

I wanted to have something like this:

-----[ Assembly ]-----
       .opt    * disable W0002
frame_buffer    ...
now             ...

               puls    x,p
       .opt    * enable W0002
-----[ END OF LINE ]-----

We first disable the warning for the code fragment, then afterwards we enable
it again. I coded this all up, but it never worked. It worked for other
warnings, but not this particular one.

The assembler is a classic two-pass assembler, but not all warnings are
issued during the passes, as can be seen here (using a file that generates
every possible warning with debug output enabled):

-----[ shell ]-----
[spc]lucy:~/source/asm/a09/misc>../a09 -ftest -d -o/dev/null warn.asm
warn.asm: debug: Pass 1
warn.asm:2: warning: W0010: missing initial label
warn.asm:10: warning: W0008: ext/tfr mixed sized registers
warn.asm:11: warning: W0001: label 'a_really_long_label_that_exceeds_the_internal_limit_its_quite_l' exceeds 63 characters
warn.asm:16: warning: W0001: label 'a_really_long_label_that_exceeds_the_internal_limit_its_quite_l' exceeds 63 characters
warn.asm:21: warning: W0001: label 'another_long_label_that_is_good.but_this_makes_it_too_long_to_u' exceeds 63 characters
warn.asm:23: warning: W0001: label 'another_long_label_that_is_good.but_this_makes_it_too_long_to_u' exceeds 63 characters
warn.asm:36: warning: W0013: label 'a' could be mistaken for register in index
warn.asm:37: warning: W0013: label 'b' could be mistaken for register in index
warn.asm:38: warning: W0013: label 'd' could be mistaken for register in index
warn.asm: debug: Pass 2
warn.asm:2: warning: W0003: 16-bit value truncated to 5 bits
warn.asm:3: warning: W0004: 16-bit value truncated to 8 bits
warn.asm:4: warning: W0005: address could be 8-bits, maybe use '<'?
warn.asm:5: warning: W0006: offset could be 5-bits, maybe use '<<'?
warn.asm:8: warning: W0005: address could be 8-bits, maybe use '<'?
warn.asm:9: warning: W0007: offset could be 8-bits, maybe use '<'?
warn.asm:11: warning: W0009: offset could be 8-bits, maybe use short branch?
warn.asm:13: warning: W0011: 5-bit offset upped to 8 bits for indirect mode
warn.asm:25: warning: W0012: branch to next location, maybe remove?
warn.asm:26: warning: W0012: branch to next location, maybe remove?
warn.asm:43: warning: W0017: cannot assign the stack address within .TEST directive
warn.asm:42: debug: Running test test
warn.asm:42: warning: W0014: possible self-modifying code
warn.asm:42: warning: W0016: memory write of 00 to 0034
warn.asm:42: warning: W0015: : reading from non-readable memory: PC=0016 addr=F015
warn.asm:42: debug: Post assembly phases
warn.asm:2: warning: W0002: symbol '.start' defined but not used
[spc]lucy:~/source/asm/a09/misc>
-----[ END OF LINE ]-----

You can see some are generated during pass 1, some during pass 2. The message
“Running test test” happens after the second pass is done, and the one I'm
trying to supress, W0002, at the very end of the program. The .OPT directives
are processed during passes 1 and 2. There's just no easy way to supress
W0002 just for a portion of the code, as I would have to carry forward that
any labels defined between the disable and enable of W0002 should be exempt
from the “no-label warning” check.

It's issues like these that complicate programs over time.

I was about to scrap the idea when I came up with a solution. Each symbol in
the symbol table has a reference count. At the end of assembly, there's code
that goes through the symbol table and issues the warning if a label has a
reference count of 0. All I did was create another option, .OPT * USES
<label>, to increment the reference count of a label. At the end of the day,
it works. I'm not saying this is a “good” solution, just “a” solution.

[1] https://github.com/spc476/a09
[2] gopher://gopher.conman.org/0Phlog:2023/12/11.1

Email author at [email protected]