Transmogrify stack words into zp register words
Posted: Mon Jan 26, 2026 1:51 pm
I wanted to streamline some code like u A ! and A @ for heavily used variables, so I started hand-coding assembly for some inline "register" words that interacted directly with fixed 16-bit zero page cells.
In Taliforth (and I guess many other 6502 forths) we use X as the data stack pointer so have lots of simple assembly words implemented with zp,X indexing to manipulate the stack. Obviously the new code I was writing looked very similar but with direct zp addressing (no index).
However I was surprised to see that I could mechanically transform assembly for existing stack words to specialize them into words that targeted a fixed zp "register". Each opcode with an indexed-zero page mode like ADC zp, x (opcode $75) has a matching direct zero-page version with the opcode reduced by $10, e.g. ADC zp (opcode $65). This makes it easy to write a MAKE-REGISTER-WORD ( register-zpadr stack-offset xt ) that converts simple stack word object code into a new word targeting a fixed zp register in place of a given stack offset. This lets me dynamically create efficient register words on the fly.
See below for a few examples. Taliforth's existing stack word implementation is on the left, and the transformed code is on the right. Each transformed word is illustrated with a register at zp $42/43 replacing a targeted stack offset. Sometimes the results are obvious: a word like INVERT ( x - x ) produces a word like INVERT-REGISTER42 which does in inline invert of zp $42/43. From "+" ( x y -- x+y ) targeting NOS we get a word that does an inline addition of TOS into the register. But sometimes the results are surprising/interesting: DUP targeting TOS becomes REGISTER42@ and NIP targeting NOS becomes REGISTER42!
In Taliforth (and I guess many other 6502 forths) we use X as the data stack pointer so have lots of simple assembly words implemented with zp,X indexing to manipulate the stack. Obviously the new code I was writing looked very similar but with direct zp addressing (no index).
However I was surprised to see that I could mechanically transform assembly for existing stack words to specialize them into words that targeted a fixed zp "register". Each opcode with an indexed-zero page mode like ADC zp, x (opcode $75) has a matching direct zero-page version with the opcode reduced by $10, e.g. ADC zp (opcode $65). This makes it easy to write a MAKE-REGISTER-WORD ( register-zpadr stack-offset xt ) that converts simple stack word object code into a new word targeting a fixed zp register in place of a given stack offset. This lets me dynamically create efficient register words on the fly.
See below for a few examples. Taliforth's existing stack word implementation is on the left, and the transformed code is on the right. Each transformed word is illustrated with a register at zp $42/43 replacing a targeted stack offset. Sometimes the results are obvious: a word like INVERT ( x - x ) produces a word like INVERT-REGISTER42 which does in inline invert of zp $42/43. From "+" ( x y -- x+y ) targeting NOS we get a word that does an inline addition of TOS into the register. But sometimes the results are surprising/interesting: DUP targeting TOS becomes REGISTER42@ and NIP targeting NOS becomes REGISTER42!
Code: Select all
w_invert: zp_invert:
; ( x -- ~x ) ; r42: x -> ~x
; change TOS refs (mode zp,x) to register refs (mode: zp)
a9 ff lda #$FF a9 ff lda #$FF
55 00 eor 0,x 45 42 eor $42
95 00 sta 0,x 85 42 sta $42
a9 ff lda #$FF a9 ff lda #$FF
55 01 eor 1,x 45 43 eor $43
95 01 sta 1,x 85 43 sta $43
60 rts 60 rts
w_plus: zp_plus:
; ( x y -- x+y ) ; ( y -- ) r42: x -> x+y
; change NOS refs (mode: zp,x) to register refs (mode: zp)
18 clc 18 clc
b5 00 lda 0,x b5 00 lda 0,x
75 02 adc 2,x 65 42 adc $42
95 02 sta 2,x 85 42 sta $42
b5 01 lda 1,x b5 01 lda 1,x
75 03 adc 3,x 65 43 adc $43
95 03 sta 3,x 85 43 sta $43
e8 inx e8 inx
e8 inx e8 inx
60 rts 60 rts
w_nip: zp_store:
; ( x y -- y ) ; ( y -- ) r42: ? -> y
; change NOS refs (mode: zp,x) to register refs (mode: zp)
b5 00 lda 0,x b5 00 lda 0,x
95 02 sta 2,x 85 42 sta $42
b5 01 lda 1,x b5 01 lda 1,x
95 03 sta 3,x 85 43 sta $43
e8 inx e8 inx
e8 inx e8 inx
60 rts 60 rts
w_dup: zp_fetch:
; ( x -- x x ) ; r42: r ( -- r )
; change NOS refs (mode: zp,x) to register refs (mode: zp)
ca dex ca dex
ca dex ca dex
b5 02 lda 2,x a5 42 lda $42
95 00 sta 0,x 95 00 sta 0,x
b5 03 lda 3,x a5 43 lda $43
95 01 sta 1,x 95 01 sta 1,x
60 rts 60 rts