11

I find I keep on doing things like this:

raster_interrupt:
    bgnd_color = * + 1
    lda #$00
    sta $d021
    ; rest of interrupt handler

so that I can do this kind of thing elsewhere in my program:

    lda #$01
    sta bgnd_color

This interrupt handler loads an immediate value into the accumulator, and then stores it in the hardware register that determines the background colour. Elsewhere in the program I modify that immediate value.

This saves a machine cycle in the interrupt handler, and does not use any zero page or other memory. Only the store instruction is a bit slower but that's okay; the background color doesn't change all that often.

The tricky part is maintaining the label that addresses the instruction's operand. As far as I can see, my options are:

  • what I did above, the * + 1 which is hacky, unintuitive and more awkward for 16 bit quantities. Also it's less maintainable because the label is not in the same line as the address, which means version control won't treat them as one unit, and also seems like a breeding ground for gotofail bugs.

  • label the instruction, and then refer to its operand with label + 1. This option I like slightly less because now the label names the instruction, not the value. That makes naming harder. Also I need to remember the + 1 on every reference to the value.

  • bgnd_color = raster_interrupt + 1, riskier version of the first option.

I would much rather do this on the same line as the opcode (more maintainable, more version-control friendly), something like this:

raster_interrupt:
    lda :bgnd_color
    sta $d021
    ; rest of interrupt handler

(I just invented a syntax for the purposes of this question. The idea here is that bgnd_color is still a label, but it's inline and denotes the address of the operand, 8 or 16 bits.). Self-modifying code is quite common on old micros, so I am surprised to not see any assemblers with features for conveniencing it. Have I overlooked any assembler that can do this?

  • 2
    In my experience on the Apple II, self-modifying code that replaces 16-bit addresses is more common than code that replaces 8-bit immediate values, as it allows cheaper indexed addressing in loops. In line-drawing code, it's also common to replace instructions (e.g. INX vs. DEX). I don't know that you can do much better than a simple label. It may also be important to specify an initial value: self-mod code used for obfuscation may not want to set the value before the first iteration. – fadden May 20 at 14:53
  • @fadden what you say about 16 bit addresses is certainly a good point. I think there should be a method for dealing with them too. Changing opcodes is not so bad though because you can put a label in front, on the same line. – Wilson May 20 at 15:22
  • I tried a simple macro in acme. Seemed like more trouble than it's worth, though. – Brian H May 20 at 19:46
  • @BrianH, I'd appreciate seeing what you came up with, all the same. If you're happy to share of course. – Wilson May 20 at 21:34
  • 1
    My preference is to put a label on the instruction to be patched, naming it something like ldBgColor_patch, and if the value will be fetched from mutliple places in addition to the LDA that uses it (as is the case with e.g. pointer used by in MS-Basic's CHRGET), assign a symbol to bgColor .equ ldBgColor_patch+1. That should be readable without assembler-specific knowledge, unlike approaches that exploit assembler-specific constructs. – supercat May 21 at 16:10
10

In Kick Assembler you can put labels in front of opcode arguments. For example you can write your raster interrupt like:

raster_interrupt:
    lda bgnd_color:#$00
    sta $d021
    ; rest of interrupt handler

and change the color elsewhere in your program with:

    lda #$01
    sta bgnd_color

Such labels in front of the argument provide a more compact and tidy syntax. But * = + 1 trick is kind of preferable because it is the oldschool conventional way. So when you share your codes with others, they will find it more familiar and readable.

| improve this answer | |
  • 1
    Oh, that's nice. Head on. As so often, someone already had the problem and a similar solution. Would you mind to add a link where to find this information (and maybe more and the assembler as well)? – Raffzahn May 21 at 21:57
5

Not that I know of. After all, "an instruction" is one of the basic units a assembler handles. It has no subpart.

Now, I really appreciate that you try to solve it using symbolic tools, which is the best way to avoid programming errors. Let the assembler do the job. For one, it will always be a secondary construct, based on a label - which could move - and second, you won't get it as build in, so you need to go for some label arithmetic. Personally I'd use a fourth way for such cases: Making all labels dependent and encapsulate the whole thing in a macro (or a set of macros).

This differs of course quite a lot on the Assembler you use and what option for manipulation it offers So hard to give an example without knowing.


CA65

Unlike promised I couldn't find the project it belonged to, so this is a quick recreation:

ca65 V2.18 - Git 1074d35
Main file   : test.asm
Current file: test.asm

000000r 1                       .P02
000000r 1               
000000r 1               .MACRO  PATCH   Instruction,Offset,Label
000000r 1               ; Instruction - The instruction to be modified as string
000000r 1               ;               enclosed in curly brackets {}
000000r 1               ; Offset      - The offset within the instruction to be patched.
000000r 1               ; Label       - Label to be set
000000r 1               
000000r 1               Label   :=      *+Offset
000000r 1                       Instruction
000000r 1               .ENDMACRO
000000r 1               
000000r 1                       .ORG    $0800
000800  1               
000800  1               raster_interrupt:
000800  1  A9 00                PATCH  {LDA #$00},1,bgnd_color
000802  1  8D 21 D0             STA     $D021
000805  1               ; rest of interrupt handler
000805  1  60                   RTS
000806  1               
000806  1               
000806  1               Somewhere_else:
000806  1  A9 01                LDA     #$01
000808  1  8D 01 08             STA     bgnd_color
00080B  1               
00080B  1                       .END

So essentially it's exact like your first version, which is usually the way to go, but as you say rather ugly and error prone. Encapsulated in a macro it not only looks nice but does highlight that it's a rather special instruction. Chances of disturbing edits or alike are limited.

This version is rather simple and it should be possible to implement that with other assemblers as well. Of course it can be improved a lot - like checking the Offset value and adding some luxury for modification of 16 bit values (for example separate labels (*H and *L) for both bytes), but that will require a bit more work depending on the assembler used.


Use with 64TASS

64TASS is a rather strange beast. Some great parts, like scoping, but in general rather chaotic development of features. I had to dive into the C source to see that, what looked possible by reading the manual, was simply not implemented. So this version is less elegant, but should do the job.

; 64tass Turbo Assembler Macro V1.55.2200 listing file
; 64tass -Wall -l tass.L1 -L tass.L2 test-tass.asm
; Thu May 21 03:13:03 2020

;Offset ;Hex        ;Monitor    ;Source

;******  Processing input file: test-tass.asm

.0800                           raster_interrupt:
.0800                           bgcolor
=$0801                          VAL     =      *+1
=$0801                          VALL    =      *+1
=$0802                          VALH    =      *+2
.0800   a9 00       lda #$00            LDA #$00
.0802   8d 21 d0    sta $d021           STA     $D021
.0805                           stuff
=$0806                          VAL     =      *+1
=$0806                          VALL    =      *+1
=$0807                          VALH    =      *+2
.0805   8d 34 12    sta $1234           STA $1234
.0808   60          rts                 RTS
.0809                           Somewhere_else:
.0809   a9 01       lda #$01            LDA     #$01
.080b   8d 01 08    sta $0801           STA     bgcolor.VAL
.080e   8d 06 08    sta $0806           STA     stuff.VALL
.0811   8e 07 08    stx $0807           STX     stuff.VALH

;******  End of listing        .end

Since the source lines are not included in a listing, thus making the macro call invisible, here's the source as well.

PATCH   .macro Instr
VAL     =      *+1
VALL    =      *+1
VALH    =      *+2
        \@
    .endm

        * =    $0800

raster_interrupt:
bgcolor #PATCH  LDA #$00
        STA     $D021
; rest of interrupt handler
stuff   #PATCH  STA $1234ds
        RTS

Somewhere_else:
        LDA     #$01
        STA     bgcolor.VAL

        STA     stuff.VALL
        STX     stuff.VALH  
        .end
| improve this answer | |
  • @Wilson As said, it'll be helpful to know what assembler you're using. I can look for a CA65 macro I once did. – Raffzahn May 20 at 16:29
  • @Wilson let me dig. I would not wholeheartedly recommend CA65, in fact, it got so many details I hate, but then again, it does the job. I would love to get ORCA-M back. For 64tass I need to look up what's possible. – Raffzahn May 20 at 16:33
  • They all have their quirks. 64tass won't let you start a symbol with '_', which coincidentally is my personal convention for labeling self-modifying code. Still, it's a solid choice for C64 code. ISTR the IIgs version of Orca/M having a really nice macro system, which you might expect given the name. – fadden May 20 at 18:15
  • @Wilson Done - take a look. I would belive it can be recreated with TASS. – Raffzahn May 20 at 18:46
  • @fadden ORCA-M is rather close modelled after the IBM /370 assembler (more or less the F-Assembler) so, no wonder I like it :)) Of course it's easy to do it right if the macro language isn't a clutter of additions but planned from start. – Raffzahn May 20 at 18:50
3

ca65 (the assembler of the cc65 suite) has two macro packages for writing self modifying code in a comprehensible way. As described in the documentation, the main package is smc.inc, while the second package opcodes.inc is only necessary if you want also to modify opcodes. For your described application, the smc.inc package should be sufficient.

You basically write SMC_StoreValue label at the place you want to save a register and add SMC label, { LDA #SMC_Value } at the line you want to restore the value.

Example 1: Storing and restoring A,X,Y

.include "smc.inc" ;this line goes to the beginning of you assembler file

SMC_StoreValue storeA
SMC_StoreValue storeX,X
SMC_StoreValue storeY,Y
   ...some code...
SMC storeA, { LDA #SMC_Value }
SMC storeX, { LDX #SMC_Value }
SMC storeY, { LDY #SMC_Value }

Example 2: Switching X and Y

SMC_StoreValue storeX,X
SMC_StoreValue storeY,Y
SMC storeX, { LDY #SMC_Value }
SMC storeY, { LDX #SMC_Value }
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.