Looking through the source code of the 6502 MS-BASIC, certain parts of it seem more reminiscent of how things would be done on the 8080 than on how they should be done on the 6502. Code to find a line with a specified number, for example, is relatively performance-critical, but Bill Gates' code seems less than optimal:
FNDLIN: LDWX TXTTAB ;LOAD [X,A] WITH [TXTTAB]
FNDLNC: LDYI 1
STWX LOWTR ;STORE [X,A] INTO LOWTR
LDADY LOWTR ;SEE IF LINK IS 0
BEQ FLINRT
INY
INY
LDA LINNUM+1 ;COMP HIGH ORDERS OF LINE NUMBERS.
CMPDY LOWTR
BCC FLNRTS ;NO SUCH LINE NUMBER.
BEQ FNDLO1
DEY
BNE AFFRTS ;ALWAYS BRANCH.
FNDLO1: LDA LINNUM
DEY
CMPDY LOWTR ;COMPARE LOW ORDERS.
BCC FLNRTS ;NO SUCH NUMBER.
BEQ FLNRTS ;GO TIT.
AFFRTS: DEY
LDADY LOWTR ;FETCH LINK.
TAX
DEY
LDADY LOWTR
BCS FNDLNC ;ALWAYS BRANCHES.
FLINRT: CLC ;C MAY BE HIGH.
FLNRTS: RTS ;RETURN TO CALLER.
The code uses a lot of INY and DEY instructions, even though the value of Y at any given spot in the code will always be the same; using an LDY #1
rather than DEY
at AFFRTS
would have allowed BEQ FNDL01 / DEY / BNE AFFRTS
to be replaced with BNE AFFRTS
. Further, INY INY
could be cheaply replaced with LDY #3
and eliminate the need for the preceding LDY #1
. Further, the use of X:A to hold a temporary high/low address foregoes the possibility of using (ind,x) mode [with x set to zero] which could help eliminate some of the gymnastics with Y.
On the other hand, if the code was based on a reworking of a similar algorithm on the 8080, the use of INY/DEY could make sense since the effects would be analogous to incrementing or decrementing HL on the 8080. On the 8080, code could rather efficiently do something like:
; Using Z80 mnemonics for 8080 opcodes
loop:
ex de,hl
start:
ld e,(HL) ; Fetch link LSB
inc HL
ld d,(HL) ; Fetch link MSB
inc HL
ld a,c ; Line number LSB
sub a,(HL)
inc hl
ld a,b ; Line number MSB
sbc a,(HL)
jc lp
For the 6502, a more efficient approach would be to have each line preceded by a length byte, allowing something like:
start:
lda linNum+1
sta lnTemp
ldy #2
ldx #0
loop:
lda (ptr),y
cmp lnTemp ; Carry clear if looking for < what's there
bcs oddBall
lda ptr
adc (ptr,x)
sta ptr
bcc loop
inc ptr+1
bcs lp ; Carry still set from before
oddBall:
bne notFound
lda lnNum ; Do other byte of line # if we haven't yet
sta lnTemp
dey
bne loop : If not equal, we need to do LSB of line #
clc
notFound:
rts
Only 25 cycles per line in the common case, with an extra 7-8 cycles on each page crossing (depending upon whether the lda(ind),y
crossed a page). So about twice as fast as the original code. This code would rely upon the 6502's ability to directly access two addresses relative to (ind) without having to manipulate any index registers, which is something the 8080 couldn't do, but exploiting that ability could have made things much faster. Further, it benefits from the fact that adding an 8-bit value to a 16-bit pointer is much faster than loading a 16-bit value.
Does the 8080 BASIC use routines which largely mirror the logic of the 6502 versions, thus suggesting that the 6502 was strongly derived from it? Or was Bill Gates simply following the coding style of a processor other than the 6502 (perhaps an 8080, or maybe PDP-10 or something else)?
ldax d/stax h/inx d/inx h/inc c/jnz lp/inc b/jnz lp
. Sure RST vectors are cute, but they're not exactly speedy.