1
0
mirror of https://github.com/mgcaret/of816.git synced 2024-12-28 10:30:21 +00:00
of816/asm/interpreter.s
2019-07-01 10:33:44 -07:00

1079 lines
26 KiB
ArmAsm

; Inner interpreter and support routines, and basic stack manipulation routines.
; Inner interpreter entry and Forth call nesting
; this inner interpreter expects cell-sized absolute references to other definitions
; Expectations:
; call with JSL in native mode with long registers
; D register: address of direct page to be used for low-level system functions and
; working registers.
; S register: return stack
; X register: data stack pointer in bank 0, relative to D register.
; See equates.inc. SP_MIN and SP_MAX reflect minimum and maximum allowed stack pointer
; Pops caller from return stack and initializes IP with it
; saving previous IP on return stack.
.if 1 ; faster
.proc _enter
.if trace
lda IP
ldy IP+2
wdm $81
.endif
phb ; (3) dummy value
lda 2,s ; (5)
ldy IP ; (4)
sta IP ; (4)
tya ; (2)
sta 1,s ; (5)
lda 4,s ; (5)
ldy IP+2 ; (4)
and #$FF ; (3)
sta IP+2 ; (4)
tya ; (2)
sta 3,s ; (5)
; fall-through ; (46 cycles)
.endproc
.else ; original implementation
.proc _enter
ldy IP ; (4)
lda IP+2 ; (4)
.if trace
wdm $81
.endif
sta TMP1 ; (4)
pla ; (5)
sta IP ; (4)
sep #SHORT_A ; (3)
pla ; (4)
rep #SHORT_A ; (3)
and #$FF ; (3)
sta IP+2 ; (4)
lda TMP1 ; (4)
pha ; (4)
phy ; (4)
; fall-through ; (50 cycles)
.endproc
.endif
.proc _next
inc IP ; inline fetch
bne :+
inc IP+2
: lda [IP] ; low word
tay
inc IP
bne :+
inc IP+2
: inc IP
bne :+
inc IP+2
: lda [IP] ; high word
inc IP
bne :+
inc IP+2
:
.if !no_fast_lits
ora #$0000 ; faster than php+plp
beq fast_num
.endif
run: sep #SHORT_A
pha
rep #SHORT_A
phy
rtl
fast_num: jsr _pushay
bra _next
.endproc
; Exit Forth thread: restore previous IP from return stack
; and resume execution
.proc _exit_next
ply
pla
.if trace
wdm $83
.endif
sty IP
sta IP+2
NEXT
.endproc
; Exit Forth thread, resume native code execution at IP+1 by swapping the 32-bit IP on the
; stack for the low 24 bits of the Forth IP
.proc _exit_code
.if 1 ; ever so slightly faster, eliminate TMP1 use
.if trace
lda IP+2
ldy IP
wdm $82
.endif
lda 3,s ; (5)
tay ; (2)
lda IP+1 ; (4) note offset is 1 to get high & middle bytes
sta 3,s ; (5)
sty IP+2 ; (4)
lda 1,s ; (5)
tay ; (2)
lda IP ; (4)
sta 2,s ; (5) note offset is 2 to place low (& middle again) bytes
sty IP ; (4)
tsc ; (2)
inc a ; (2) drop the extra byte
tcs ; (2)
rtl ; (47 cycles)
.else ; original
ldy IP ; (4)
lda IP+2 ; (4)
sta TMP1 ; (4)
pla ; (5)
sta IP ; (4)
pla ; (5)
sta IP+2 ; (4)
lda TMP1 ; (4)
.if trace
wdm $82
.endif
sep #SHORT_A ; (3)
pha ; (4)
rep #SHORT_A ; (3)
phy ; (4)
rtl ; (48 cycles)
.endif
.endproc
.proc _fetch_ip_word
inc IP
bne :+
inc IP+2
: lda [IP]
; fall-through
.endproc
.proc _inc_ip ; note fall-through from above!
inc IP
bne :+
inc IP+2
: rts
.endproc
.proc _fetch_ip_byte
inc IP
bne :+
inc IP+2
: lda [IP]
and #$00FF
rts
.endproc
.proc _fetch_ip_cell
inc IP
bne :+
inc IP+2
: lda [IP]
tay
inc IP
bne :+
inc IP+2
: inc IP
bne :+
inc IP+2
: lda [IP]
inc IP
bne :+
inc IP+2
: rts
.endproc
; convert XT address in YR to header address in YR
; return carry set if word has header
; return carry clear if word does not have header (is noname)
; Y = name length
.proc _xttohead
lp: jsr _decyr ; first one decrements before flags
lda [YR]
and #$80 ; see if it's the name length field
beq lp ; nope, go back again
lda [YR] ; get it back
and #$7F ; mask in length
tay ; and save it
beq nohead
yrminus4: lda YR
sec ; move to link field
sbc #$04
sta YR
lda YR+2
sbc #$00
sta YR+2
sec ; flag OK
rts
nohead: clc
rts
.endproc
_yrminus4 = _xttohead::yrminus4
.if 0
; Get caller address (must call with JSL)
.proc _trace_word
ldy #'>'
jsr _emit
lda 1,S ; get caller address
sta WR
lda 3,S
sta WR+2
jsr _wrminus4 ; get xt
lda WR ; copy xt to YR
sta YR
lda WR+2
sta YR+2
jsr _xttohead ; go to header
bcc do_hex ; No name
sty XR ; save length
stz XR+2
lda YR ; put address of name back into WR
clc
adc #$04
sta WR
lda YR+2
adc #$00
sta WR+2
ldy #.loword(do_emit-1)
lda #.hiword(do_emit-1)
jsr _str_op_ay ; now print word (destroys YR)
spacer: lda #' ' ; print a space and cleverly fall through
do_emit: tay
jsr _emit
clc
rtl
do_hex: jsr _incwr ; Move to word XT address
ldy #'$' ; because that's what we want to print
jsr _emit
lda WR+2 ; high word
jsr prhex ; print
lda WR ; low word
jsr prhex ; print
bra spacer ; and done
prhex: sta XR ; save it
lda #$04 ; 4 digits to do
sta XR+2 ; counter loc
digit: lda #$0000 ; start with nothing
clc ; rotate 4 bits from XR to A
rol XR
rol a
rol XR
rol a
rol XR
rol a
jsr _dtoc ; convert to ASCII
tay
jsr _emit ; and print
dec XR+2
bne digit ; do the rest if there are some left
rts
.endproc
.endif
; Stack primitives
; stack starts at STK_TOP and grows down toward STK_BTM
; STK_BTM points at the last usable cell
; STK_TOP points at the location above the first usable cell
.proc _stackdecr
cpx STK_BTM ; past the bottom already?
bcc _stko_err
dex
dex
dex
dex
rts
.endproc
.proc _stackincr
cpx STK_TOP ; already past where we can be?
bcs _stku_err ; yep, underflowed stack
inx
inx
inx
inx
rts
.endproc
.proc _popay
lda STACKBASE+2,x
ldy STACKBASE+0,x
.if trace
wdm $85
.endif
bra _stackincr
.endproc
.proc _peekay
cpx STK_TOP
bcs _stku_err
lda STACKBASE+2,x
ldy STACKBASE+0,x
rts
.endproc
.proc _popwr
jsr _popay
sty WR
sta WR+2
rts
.endproc
; no stack depth check
.proc _peekwr
lda STACKBASE+0,x
sta WR
lda STACKBASE+2,x
sta WR+2
rts
.endproc
.proc _popxr
jsr _popay
sty XR
sta XR+2
rts
.endproc
.proc _popyr
jsr _popay
sty YR
sta YR+2
rts
.endproc
.proc _stku_err
ldx STK_TOP
ldy #.loword(-4)
lda #.hiword(-4)
jmp _throway
.endproc
.proc _1parm
cpx STK_TOP
bcs _stku_err
rts
.endproc
.proc _l1parm
jsr _1parm
rtl
.endproc
.proc _2parm
txa
clc
adc #$04
docmp: cmp STK_TOP
bcs _stku_err
rts
.endproc
.proc _l2parm
jsr _2parm
rtl
.endproc
.proc _3parm
txa
clc
adc #$08
bra _2parm::docmp
.endproc
.proc _l3parm
jsr _3parm
rtl
.endproc
.proc _4parm
txa
clc
adc #$0C
bra _2parm::docmp
.endproc
.proc _l4parm
jsr _4parm
rtl
.endproc
.proc _stko_err
lda STK_BTM
clc
adc #32 ; 8 cells
tax
ldy #.loword(-3)
lda #.hiword(-3)
jmp _throway
.endproc
.proc _pushay
.if trace
wdm $86
.endif
jsr _stackdecr
sta STACKBASE+2,x
sty STACKBASE,x
rts
.endproc
.proc _pusha
.if trace
phy
tay
lda #$00
jsr _pushay
ply
.else
jsr _stackdecr
stz STACKBASE+2,x
sta STACKBASE,x
.endif
rts
.endproc
.proc _swap
jsr _2parm
; fall-through
.endproc
; when we know there are 2 parms on stack...
.proc _swap1
lda STACKBASE+6,x
ldy STACKBASE+2,x
sty STACKBASE+6,x
sta STACKBASE+2,x
lda STACKBASE+4,x
ldy STACKBASE+0,x
sty STACKBASE+4,x
sta STACKBASE+0,x
rts
.endproc
.proc _over
jsr _2parm
ldy STACKBASE+4,x
lda STACKBASE+6,x
jmp _pushay
.endproc
; Interpretation routines
; Push word data address, default routine used by CREATE
; call via JSL, pops return stack entry, pushes data address
; onto data stack
.proc _pushda
pla
clc
adc #$01
tay
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
PUSHNEXT
.endproc
; Pushes cell following JSL onto the stack
.proc _pushvalue
pla
clc
adc #$01
sta WR
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
sta WR+2
pushv2: ldy #$02
lda [WR],y ; high word
pha ; save for now
dey
dey
lda [WR],y ; low word
tay
pla
PUSHNEXT
.endproc
; Pushes stack top + cell following JSL onto the stack
.proc _field
jsr _1parm
pla
clc
adc #$01
sta WR
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
sta WR+2
ldy #$00
lda [WR],y ; low word
clc
adc STACKBASE+0,x
sta STACKBASE+0,x
iny
iny
lda [WR],y ; low word
adc STACKBASE+0,x
sta STACKBASE+0,x
NEXT
.endproc
.proc _push2value
pla
clc
adc #$01
sta WR
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
sta WR+2
ldy #$06
lda [WR],y ; high word
pha ; save for now
dey
dey
lda [WR],y ; low word
tay
pla
jsr _pushay
bra _pushvalue::pushv2
.endproc
; Return address of system variable # following the JSL
.proc _sysvar
pla ; return address + 1 -> WR
clc
adc #$01
sta WR
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
sta WR+2
lda [WR] ; get sysvar number (max of 16384*4)
clc
adc SYSVARS ; add to address of SYSVARS
tay
lda SYSVARS+2
adc #$00
PUSHNEXT
.endproc
; Jumps to the XT following JSL
.proc _deferred
pla
clc
adc #$01
sta WR
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
sta WR+2
ldy #$02
lda [WR],y ; high word
sep #SHORT_A
pha ; bank byte on stack
rep #SHORT_A
dey
dey
lda [WR],y ; low word
pha ; address on stack
rtl ; really a jump
.endproc
; After pop from data stack into AY, jumps to the XT following JSL
.proc _pop_deferred
pla
clc
adc #$01
sta WR
sep #SHORT_A
pla
rep #SHORT_A
and #$FF
adc #$00
sta WR+1
ldy #$02
lda [WR],y ; high word
sep #SHORT_A
pha ; bank byte on stack
rep #SHORT_A
dey
dey
lda [WR],y ; low word
pha ; RTS address on stack
jsr _popay
rts ; really a jump
.endproc
; ensure at least room for 8 items on stack
.proc _stackroom
txa
sec
sbc #$20 ; see if there is room for 8 items on stack
cmp STK_BTM
bcc makeroom
chktop: cpx STK_TOP ; new see if we are above the top
bcc :+
ldx STK_TOP
: rts
makeroom: txa
adc #$20
tax
bra chktop
.endproc
.proc _unimpl
ldy #.loword(-21)
lda #.hiword(-21)
jmp _throway
.endproc
.proc _callyr
tay
lda YR+2
sep #SHORT_A
pha
rep #SHORT_A
lda YR
pha
tya
rtl
.endproc
.proc _str_op_ay
sta YR+2
sty YR
; fall-through
.endproc
; Perform a "string" operation on the string pointed at in WR
; with length in XR and function in YR (address less 1)
; YR is called with A containing the byte from the string
; XR is converted to last address plus one of the string
; [YR] should return with carry clear if processing is to continue
; and carry set if it not.
; note YR is called with long registers
.proc _str_op
lda WR
clc
adc XR
sta XR
lda WR+2
adc XR+2
sta XR+2
loop: lda WR+2
cmp XR+2
bne :+
lda WR
cmp XR
: bcc :+
done: rts
: lda [WR]
and #$00FF ; compensate for long register
jsl f:_callyr
bcs done
jsr _incwr
bra loop
.endproc
; do string op with function in AY and string described by (c-addr u) on
; top of data stack
.proc _str_op_ays
sta YR+2
sty YR
jsr _popxr ; u -> XR
jsr _popwr ; c-addr -> YR
bra _str_op
.endproc
.proc _iter_ay
sta YR+2
sty YR
; fall-through
.endproc
; Perform a function pointed at in YR with count in XR times
; iteration # (from 0) will be in WR
; [YR] should return with carry clear if processing is to continue
; and carry set if it not.
.proc _iter
stz WR
stz WR+2
lda XR
ora XR+2
beq done ; in case no loops requested
loop: jsl f:_callyr
bcs done
jsr _incwr
lda WR+2
cmp XR+2
bne :+
lda WR
cmp XR
: bcc loop
done: rts
.endproc
.proc _decay
cpy #$0000
bne :+
dec a
: dey
rts
.endproc
; get AY from [WR]
.proc _wrfetchind
ldy #$02
lda [WR],y
pha
dey
dey
lda [WR],y
tay
pla
rts
.endproc
; store AY into [WR]
.proc _wrstoreind
phy
ldy #$02
sta [WR],y
dey
dey
pla
sta [WR],y
rts
.endproc
.proc _incwr
inc WR
bne :+
inc WR+2
: rts
.endproc
.proc _decwr
lda WR
bne :+
dec WR+2
: dec WR
rts
.endproc
.proc _decxr
lda XR
bne :+
dec XR+2
: dec XR
rts
.endproc
.proc _decyr
lda YR
bne :+
dec YR+2
: dec YR
rts
.endproc
.proc _wrplus4
lda WR
clc
adc #$04
sta WR
lda WR+2
adc #$00
sta WR+2
rts
.endproc
.proc _wrminus4
lda WR
sec
sbc #$04
sta WR
lda WR+2
sbc #$00
sta WR+2
rts
.endproc
.if 0
.proc _wrplusxr
lda WR
clc
adc XR
sta WR
lda WR+2
adc XR+2
sta WR+2
rts
.endproc
.proc _wrminusxr
lda WR
sec
sbc XR
sta WR
lda WR+2
sbc XR+2
sta WR+2
rts
.endproc
.endif
; conversion helpers
; Digit to ASCII character
.proc _d_to_c
clc
adc #'0'
cmp #'9'+1
bcc :+
adc #6
: rts
.endproc
; ASCII character to digit
; return carry clear if bad
; carry set if good
.proc _c_to_d
and #$ff
jsr _cupper
sec
sbc #'0'
bmi bad
cmp #10
bcc good
sbc #7
bmi bad
cmp #10
bcc bad ; so things like < don't convert
cmp #37
bcc good
bad: clc
rts
good: sec
rts
.endproc
; Upper case a character in accumulator
.proc _cupper
cmp #'z'+1
bcs :+
cmp #'a'
bcc :+
and #$DF
: rts
.endproc
; Upper case a character, 8 bit accumulator
.a8
.proc _cupper8
and #$7F
cmp #'z'+1
bcs :+
cmp #'a'
bcc :+
and #$DF
: rts
.endproc
.a16
; Move XR bytes from [WR] to [YR], starting at the bottom
; trashes WR, YR, and XR
; could be optimized to move words, excepting the last one if odd number of bytes
; use for moving data downward, but that adds two comparison instructions which
; are slower than the SEP/REP, maybe
.proc _move
.if 1 ; fast move in memmgr.s
sec
jmp _memmove_c
.else ; slower but smaller move
lp: lda XR+2 ; see if zero bytes
ora XR
bne :+
rts
: jsr _decxr ; pre-decrement XR
sep #SHORT_A
lda [WR]
sta [YR]
rep #SHORT_A
jsr _incwr ; post increment WR
inc YR ; and YR
bne lp
inc YR+2
bra lp
.endif
.endproc
; Move XR bytes from [WR] to [YR], starting at the top
; trashes YR and XR
; could be optimized to move words, excepting the last one if odd number of bytes
; use for moving data upward
.proc _moveup
.if 1 ; fast move in memmgr.s
clc
jmp _memmove_c
.else ; slower but smaller move
lp: jsr _wrplusxr
lda WR ; move WR to 1 past the end of the block
clc
adc XR
sta WR
lda WR+2
adc XR+2
sta WR+2
lda YR ; move YR to 1 past the end of the block
clc
adc XR
sta YR
lda YR+2
adc XR+2
sta YR+2
lda XR+2
ora XR
bne :+
rts
: jsr _decxr ; decrement XR
jsr _decwr ; and WR
jsr _decyr ; and YR
sep #SHORT_A
lda [WR]
sta [YR]
rep #SHORT_A
bra lp
.endif
.endproc
; With word header address in YR, set YR to previous dictionary entry header
; return with Z flag set if the new address is zero
.proc _prevword
ldy #$00
lda [YR],y ; low word
pha
iny
iny
lda [YR],y ; high word
sta YR+2
pla
sta YR
ora YR+2 ; set Z flag
rts
.endproc
; search dictionary for word at WR, length in XR, start of search (header) at YR
; if found, AY=XT and carry set, otherwise
; AY=0 and carry clear
; preserves WR, XR, and YR
.proc _search
olp: lda YR
ora YR+2
beq notfnd
ldy #$04 ; offset of length
lda [YR],y ; get name length
and #$7F ; mask in significant bits
cmp XR ; compare to supplied
bne snext ; not the right word
; its the right length, compare name
lda WR+2 ; save WR
pha
lda WR
pha
phx ; save SP
sep #SHORT_A ; need to compare bytes
.a8
ldx XR ; get length to match
ldy #$05 ; offset of name
clp: lda [WR]
jsr _cupper8 ; upper case
cmp [YR],y ; compare char
bne xsnext ; no match
iny ; move to next char
jsr _incwr
dex ; if X hit zero, matched it all
bne clp ; if it didn't, keep going
rep #SHORT_A ; match!
.a16
plx ; restore SP
pla
sta WR ; restore WR, in case caller needs it
pla
sta WR+2
tya ; y = 5+namelen=offset of flags=XT
clc
adc YR
tay
lda YR+2
adc #$00 ; AY=XT
sec
rts
xsnext: rep #SHORT_A
plx
pla
sta WR
pla
sta WR+2
snext: jsr _prevword
bne olp
notfnd: lda #$00
tay
clc
rts
.endproc
; find word, skipping any smudged word
.proc _search_unsmudged
lp: jsr _search
bcs :+ ; if carry clear
rts ; it wasn't found anyway
: pha ; save xt
phy
lda WR+2 ; save WR
pha
lda WR
pha
lda 5,s ; put xt in WR
sta WR
lda 7,s
sta WR+2
lda [WR] ; get flags at xt address
ply ; restore WR
sty WR
ply
sty WR+2
and #F_SMUDG
beq f_ok ; not set, word is OK
pla ; otherwise drop xt from return stack
pla
jsr _prevword ; go to previous word
bne lp ; and search if more
clc ; otherwise flag not found
rts
f_ok: ply ; get XT back
pla
sec
rts
.endproc