Page 3 of 15
Re: Neolithic Tiny Basic
Posted: Tue Jan 14, 2025 11:16 am
by barnacle
Sorry, but the simulator won't let me cut and paste from the window, so it's screen grab time...
Which looks as if my subtract routine is working as expected: I think here that in all cases, for _unsigned_ comparison, the carry is SET if the first parameter is greater or equal to the second. Except when comparing zero with zero, which is probably not a problem...
For _signed_ comparison, the high bit of the result is ZERO if the first parameter is greater or equal to the second.
Which means I can use the routine as it is, as the carry is preserved back to the calling routine, and the result is also returned (albeit in y:a so I'll have to juggle). Which means I can do away with my compare routine, just use the subtract routine instead, saving speed and stack space. Decoding the result can be done by the caller.
Neil
Re: Neolithic Tiny Basic
Posted: Tue Jan 14, 2025 11:19 am
by barnacle
Um, except when comparing anything with zero... the carry isn't set.
Which makes sense: invert zero to get -1, +1 = 0, add zero to anything and the carry will never be set. The question is, will this be a problem?
Neil
Re: Neolithic Tiny Basic
Posted: Tue Jan 14, 2025 2:10 pm
by barnacle
So, I wrote a different subtract routine which uses the sbc instruction rather than the original approach of just inverting one operand and dropping through to the add routine. It gives the same results, as expected, but now the carry makes more sense:
Now we have it working for comparison with zero, too... for _unsigned_ if the first column is greater or equal to the second column, the carry is set; for _signed_ comparison if the first column is greater or equal to the second, the top bit of the result is zero.
Neil
Re: Neolithic Tiny Basic
Posted: Tue Jan 14, 2025 3:41 pm
by rwiker
I was wondering whether the overflow flag might be useful for these comparisons, and found this page:
https://www.righto.com/2012/12/the-6502 ... ained.html
Looks like a good explanation of integer operations on the 6502, in general.
Re: Neolithic Tiny Basic
Posted: Tue Jan 14, 2025 5:15 pm
by barnacle
Ah, always good to read Ken Shirriff (plus his occasional appearances with Curious Marc!).
Curiously, my original signed comparison used the V flag in a variant of the approach used in section 6.1 of the tutorial:
http://www.6502.org/tutorials/compare_beyond.html but unfortunately that doesn't work for unsigned numbers.
Neil
edit: to my recollection I have never actually used the V flag since 1978...
Re: Neolithic Tiny Basic
Posted: Tue Jan 21, 2025 7:12 am
by barnacle
Ah, if only I thought things through...
After all the effort to sort out the comparison, and have a generalised subtraction routine that could be used anywhere... I finally get around to realising that it's smaller and faster to do the comparison bit directly. The general API involves putting the first parameter on the stack, the second in Y:A, and calling a routine which accesses that first parameter via zp,x addressing. The sixteen bit result is returned in Y:A.
Which is fine, but (a) means that I can't use the invert and add method to subtract (it doesn't properly sort out the flags when subtracting zero) which means I have excess code there:
Code: Select all
e61a : libsub16:
e61a : da phx
e61b : ba tsx
e61c : 85b3 sta maths1
e61e : 84b4 sty maths1+1 ; save minuend
e620 : 38 sec
e621 : bd0401 lda CALLER_LO,x
e624 : e5b3 sbc maths1
e626 : 85b3 sta maths1
e628 : bd0501 lda CALLER_HI,x
e62b : e5b4 sbc maths1+1
e62d : a8 tay
e62e : a5b3 lda maths1
e630 : fa plx
e631 : 60 rts
Twenty-four bytes instead of three that a call to invert and drop straight through to libadd16 would have...
And the compare operation is also smaller if I do it inline:
Code: Select all
; if (space > insert_length)
e58e : a5b2 lda slu_space+1
e590 : 48 pha
e591 : a5b1 lda slu_space
e593 : 48 pha
e594 : a502 lda buffer+2
e596 : a000 ldy #0
e598 : 201ae6 jsr libsub16
e59b : fa plx
e59c : fa plx
;vs
e59d : 38 sec
e59e : a5b1 lda slu_space
e5a0 : c502 cmp buffer+2
e5a2 : a5b2 lda slu_space+1
e5a4 : e900 sbc #0
e5a6 : 9047 bcc sl_11
Fifteen bytes for the API call, versus nine for the inline version.
So I won't be doing it that way any longer
The subtract routine is required for evaluation of variables, but that's signed and the output flag is not (as far as I know at this point) necessary, so if I get the basic functional and find it isn't, that will go back as it was - invert and drop through, saving a further twenty-one bytes.
Neil
Re: Neolithic Tiny Basic
Posted: Sat Jan 25, 2025 9:03 pm
by barnacle
It is now possible to enter numbered program lines and have them appear in the right place in memory (or disappear, if that is the intent). It started as around eighty lines of C and it definitely takes up more lines as machine code - though there are a few obvious places where space can be saved. There are a couple of calculations which could be performed once, rather than down each program branch... also a couple of cases where I copy a variable, then add to the variable and put it somewhere else, which could interleave the steps to save a couple of loads.
But I figure, get it working, then optimise!
Code: Select all
;-----------------------------------------------------
;
; storeline
; The line has been tokenised and enumerated in buffer
; If the line is not in the store, insert it in its co
; If it is in the store, replace it (or if empty, dele
; On entry, y:a has the line number
; There is a lot going on here...
bss
00ad = slu_this_line ds 2
00af = slu_line_found ds 2 ; store_line vars
00b1 = slu_space ds 2 ; remaining space in memory
00b3 = slu_ex_length ds 1
code
e4f8 : store_line:
; uint16_t line_found;
; bool line_exists = false;
; int insert_length = 0;
; int existing_length = 0;
; int space;
e4f8 : 85ad sta slu_this_line
e4fa : 84ae sty slu_this_line+1 ; save this_line
;{
; // seek this line in memory
; mem_ptr = find_line(this_line);
e4fc : 2099e3 jsr find_line
e4ff : 8550 sta mem_ptr
e501 : 8451 sty mem_ptr+1
; line_found = * (uint16_t *) mem_ptr;
e503 : 2089e3 jsr star_int16
e506 : 85af sta slu_line_found
e508 : 84b0 sty slu_line_found+1
; insert_length = * (uint8_t *) &buffer[2];
; insert_length remains at buffer[2]
; space = &mem[MEM_END] - mem_last;
e50a : 38 sec
e50b : a9ff lda #lo MEM_END
e50d : e558 sbc mem_last
e50f : 85b1 sta slu_space
e511 : a97f lda #hi MEM_END
e513 : e559 sbc mem_last+1
e515 : 85b2 sta slu_space+1
; printf ("%d free\n", space);
;
; if ((0 == line_found) && (insert_length > 4)) // emp
e517 : a5af lda slu_line_found
e519 : 05b0 ora slu_line_found+1
e51b : d049 bne sl_10 ; line found != 0
e51d : a904 lda #4
e51f : c502 cmp buffer+2
e521 : b043 bcs sl_10 ; empty line, skip
; { // 10
; // we point at the next available storage memory
; if (space > insert_length)
e523 : 38 sec
e524 : a5b1 lda slu_space
e526 : c502 cmp buffer+2
e528 : a5b2 lda slu_space+1
e52a : e900 sbc #0
e52c : 9031 bcc sl_05 ; insufficient space
; { // 05
; memmove (mem_ptr, buffer, insert_length);
e52e : a550 lda mem_ptr
e530 : 859c sta mm_to
e532 : a551 lda mem_ptr+1
e534 : 859d sta mm_to+1 ; destination mem_ptr
e536 : a900 lda #lo buffer
e538 : 859a sta mm_from
e53a : a900 lda #hi buffer
e53c : 859b sta mm_from+1 ; source buffer
e53e : a502 lda buffer+2
e540 : 859e sta mm_size
e542 : 649f stz mm_size+1
e544 : 2042e3 jsr memmove
; mem_ptr += insert_length;
e547 : 18 clc
e548 : a550 lda mem_ptr
e54a : 6502 adc buffer+2
e54c : 8550 sta mem_ptr
e54e : a551 lda mem_ptr+1
e550 : 6900 adc #0
e552 : 8551 sta mem_ptr+1
; mem_last = mem_ptr;
COPY16 mem_ptr, mem_last
e554 : a550 > lda mem_ptr
e556 : 8558 > sta mem_last
e558 : a551 > lda mem_ptr+1
e55a : 8559 > sta mem_last+1
e55c : 4ca4e6 jmp sl_20
; }
e55f : sl_05:
; else
; { // 10
; err = ERR_MEM;
e55f : a901 lda #ERR_MEM
e561 : 8557 sta err
e563 : 4ca4e6 jmp sl_20
; }
; }
e566 : sl_10:
; else if (line_found != 0)
; {
e566 : a5af lda slu_line_found
e568 : 05b0 ora slu_line_found+1
e56a : d003 bne sl_101
e56c : 4ca4e6 jmp sl_19 ; line zero was found, we're done
; if (line_found != this_line)
e56f : sl_101:
e56f : a5b0 lda slu_line_found+1
e571 : c5ae cmp slu_this_line+1
e573 : d006 bne sl_102
e575 : a5af lda slu_line_found
e577 : c5ad cmp slu_this_line
e579 : f05f beq sl_14
; { // 14
; // we need to shuffle up and insert
e57b : sl_102:
; if (insert_length > 4)
e57b : a502 lda buffer+2
e57d : c904 cmp #4
e57f : 9059 bcc sl_13
; { // 13
; // the line is valid, it needs space
e581 : 38 sec
e582 : a5b1 lda slu_space
e584 : c502 cmp buffer+2
e586 : a5b2 lda slu_space+1
e588 : e900 sbc #0
e58a : 9047 bcc sl_11
; { // 11
; memmove (mem_ptr + insert_length, mem_ptr, mem_l
e58c : 18 clc
e58d : a550 lda mem_ptr
e58f : 859a sta mm_from ; while we have it to hand
e591 : 6502 adc buffer+2
e593 : 859c sta mm_to
e595 : a551 lda mem_ptr+1
e597 : 859b sta mm_from+1 ; ditto
e599 : 6900 adc #0
e59b : 859d sta mm_to+1
e59d : 38 sec
e59e : a558 lda mem_last
e5a0 : e550 sbc mem_ptr
e5a2 : 859e sta mm_size
e5a4 : a559 lda mem_last+1
e5a6 : e551 sbc mem_ptr+1
e5a8 : 859f sta mm_size+1
e5aa : 2042e3 jsr memmove
; mem_last += insert_length;
e5ad : 18 clc
e5ae : a558 lda mem_last
e5b0 : 6502 adc buffer+2
e5b2 : 8558 sta mem_last
e5b4 : a559 lda mem_last+1
e5b6 : 6900 adc #0
e5b8 : 8559 sta mem_last+1
; memmove (mem_ptr, buffer, insert_length);
e5ba : a550 lda mem_ptr
e5bc : 859c sta mm_to
e5be : a551 lda mem_ptr+1
e5c0 : 859d sta mm_to+1
e5c2 : a900 lda #lo buffer
e5c4 : 859a sta mm_from
e5c6 : a900 lda #hi buffer
e5c8 : 859b sta mm_from+1
e5ca : a502 lda buffer+2
e5cc : 859e sta mm_size
e5ce : 649f stz mm_size+1
e5d0 : 2042e3 jsr memmove
; }
e5d3 : sl_11:
; else
; { // 12
; err = ERR_MEM;
e5d3 : a901 lda #ERR_MEM
e5d5 : 8557 sta err
e5d7 : 4ca4e6 jmp sl_20
; }
e5da : sl_12:
; }
; // we don't insert an empty line!
e5da : sl_13:
; }
e5da : sl_14:
; else
; { // 19
; // we need to replace or delete this existing line
; existing_length = (* (uint8_t *)(mem_ptr + 2))
e5da : a002 ldy #2
e5dc : b150 lda (mem_ptr),y
e5de : 85b3 sta slu_ex_length
; if (insert_length > 4)
e5e0 : a904 lda #4
e5e2 : c502 cmp buffer+2
e5e4 : 9003 bcc sl_141
e5e6 : 4c65e6 jmp sl_17 ; empty line
; { // 17
; if (space > insert_length)
e5e9 : sl_141:
e5e9 : 38 sec
e5ea : a5b1 lda slu_space
e5ec : c502 cmp buffer+2
e5ee : a5b2 lda slu_space+1
e5f0 : e900 sbc #0
e5f2 : 906a bcc sl_15
; { // 15
; // replace the line
; // probably need to move existing memory to fit
; memmove(mem_ptr + insert_length, mem_ptr + exist
e5f4 : 18 clc
e5f5 : a550 lda mem_ptr
e5f7 : 6502 adc buffer+2
e5f9 : 859c sta mm_to
e5fb : a551 lda mem_ptr+1
e5fd : 6900 adc #0
e5ff : 859d sta mm_to+1 ; mem_ptr + insert_length
e601 : 18 clc
e602 : a550 lda mem_ptr
e604 : 65b3 adc slu_ex_length
e606 : 859a sta mm_from
e608 : a551 lda mem_ptr+1
e60a : 6900 adc #0
e60c : 859b sta mm_from+1 ; mem_ptr + existing_length
e60e : 38 sec
e60f : a558 lda mem_last
e611 : e550 sbc mem_ptr
e613 : 859e sta mm_size
e615 : a559 lda mem_last+1
e617 : e551 sbc mem_ptr+1
e619 : 859f sta mm_size+1
e61b : 38 sec
e61c : a59e lda mm_size
e61e : e5b3 sbc slu_ex_length
e620 : 859e sta mm_size
e622 : a59f lda mm_size+1
e624 : e900 sbc #0
e626 : 859f sta mm_size+1 ;size
e628 : 2042e3 jsr memmove
; memmove(mem_ptr, buffer, insert_length);
e62b : a550 lda mem_ptr
e62d : 859c sta mm_to
e62f : a551 lda mem_ptr+1
e631 : 859d sta mm_to+1
e633 : a900 lda #lo buffer
e635 : 859a sta mm_from
e637 : a900 lda #hi buffer
e639 : 859b sta mm_from+1
e63b : a502 lda buffer+2
e63d : 859e sta mm_size
e63f : 649f stz mm_size+1
e641 : 2042e3 jsr memmove
; mem_last += insert_length;
e644 : 18 clc
e645 : a558 lda mem_last
e647 : 6502 adc buffer+2
e649 : 8558 sta mem_last
e64b : a559 lda mem_last+1
e64d : 6900 adc #0
e64f : 8559 sta mem_last+1
; mem_last -= existing_length;
e651 : 38 sec
e652 : a558 lda mem_last
e654 : e5b3 sbc slu_ex_length
e656 : 8558 sta mem_last
e658 : a559 lda mem_last+1
e65a : e900 sbc #0
e65c : 8559 sta mem_last+1
; }
e65e : sl_15:
; else
; { // 16
; err = ERR_MEM;
e65e : a901 lda #ERR_MEM
e660 : 8557 sta err
e662 : 4ca4e6 jmp sl_20
; }
e665 : sl_16:
; }
e665 : sl_17:
; else
; { // 18
; // it's empty; delete the line
; memmove(mem_ptr, mem_ptr + existing_length, mem_l
e665 : a550 lda mem_ptr
e667 : 859c sta mm_to
e669 : a551 lda mem_ptr+1
e66b : 859d sta mm_to+1 ; dest mem_ptr
e66d : 18 clc
e66e : a550 lda mem_ptr
e670 : 65b3 adc slu_ex_length
e672 : 859a sta mm_from
e674 : a551 lda mem_ptr+1
e676 : 6900 adc #0
e678 : 859b sta mm_from+1 ; source mem_ptr+existing_length
e67a : 38 sec
e67b : a558 lda mem_last
e67d : e550 sbc mem_ptr
e67f : 859e sta mm_size
e681 : a559 lda mem_last+1
e683 : e551 sbc mem_ptr+1
e685 : 859f sta mm_size+1
e687 : 38 sec
e688 : a59e lda mm_size
e68a : e5b3 sbc slu_ex_length
e68c : 859e sta mm_size
e68e : a59f lda mm_size+1
e690 : e900 sbc #0
e692 : 859f sta mm_size+1 ;size
e694 : 2042e3 jsr memmove
; mem_last -= existing_length;
e697 : 38 sec
e698 : a558 lda mem_last
e69a : e5b3 sbc slu_ex_length
e69c : 8558 sta mem_last
e69e : a559 lda mem_last+1
e6a0 : e900 sbc #0
e6a2 : 8559 sta mem_last+1
; }
e6a4 : sl_18:
; }
e6a4 : sl_19:
; }
e6a4 : sl_20:
; *(int16_t *) mem_last = 0; // mark end of memory
e6a4 : a000 ldy #0
e6a6 : a900 lda #0
e6a8 : 9158 sta (mem_last),y
e6aa : c8 iny
e6ab : 9158 sta (mem_last),y
e6ad : 60 rts
;}
A mere 427 bytes... I can do better! But it does work, so that's the first stage done!
It works only if:
- The input line is in buffer
- The first two characters in buffer are the line number as an int16
- The third character in buffer is the total length of the line in the buffer
- I tokenise the text in the buffer, but that's not necessary to store it
but since all my text goes in that way, no problem...
Neil
Re: Neolithic Tiny Basic
Posted: Tue Jan 28, 2025 8:51 am
by barnacle
Hmm, after a couple of bugfixes and some factoring, it's a whole five bytes shorter...
Factoring short fragments carry almost as much overhead as actual code saved, it seems.
Neil
Re: Neolithic Tiny Basic
Posted: Tue Jan 28, 2025 1:50 pm
by drogon
Hmm, after a couple of bugfixes and some factoring, it's a whole five bytes shorter...
Factoring short fragments carry almost as much overhead as actual code saved, it seems.
Neil
Sometimes you have to look at the bigger picture...
In my current quest to squeeze as much functionality into my own TinyBasic, I've managed to "find" some 200 bytes so I can fit in the EEPROM protection code, code to read the on-board button and some wiring-like GPIO commands to save peek/poke on the VIA. I have the very real constraint of 4KB of ROM space to work with, so refactoring, re-writing, changing order to make more effective use of a branch, using branches rather than jmp's and so on. I've even resorted to treating the 2 byte NMI vector as valid code space because it can never happen in my system.
Even something that seemingly only saves one byte has been worth it.
And often it's been at the expense of speed - a fair trade off, given my TB isn't going to win any speed contests - and right now to give me even more space I'm considering a re-write of my code to read in a decimal number - right now I multiply the 'accumulator' by 2, then 2 again, then add in the original accumulator, so now it's *5 then double that to get *10... I'm considering doubling the accumulator then in a loop add it to itself 4 times. Slower and it saves just 5 bytes but might be worth it one day..
Don't give up... There's always a byte to be saved somewhere!
-Gordon
Re: Neolithic Tiny Basic
Posted: Tue Jan 28, 2025 3:52 pm
by barnacle
Oh yes. My usual approach is make it work, then make it fast (and/or elegant!).
The snag with that store_line routine is that there's a lot of places that look as if it _should_ help to factor out... for example there are two places where one variable is added to another, result in the first:
Code: Select all
; mem_last += insert_length
clc
lda mem_last
adc buffer+2
sta mem_last
lda mem_last+1
adc #0
sta mem_last+1
(thirteen bytes)
Which are replaced by a call to hsl_3 (yeah, yeah, I gave up on meaningful names!)
Code: Select all
; mem_last += insert_length
hsl_3:
clc
lda mem_last
adc buffer+2
sta mem_last
lda mem_last+1
adc #0
sta mem_last+1
rts
Fourteen bytes, but also two calls - so a total now of twenty bytes instead of twenty-six.
There's also an existing 16 bit addition, but that requires two bytes pushing on the stack, the other parameter into y:a, and the result in y:a which needs saving at the target, and a couple of bytes still on the stack to get rid of. Significantly larger than the original version
So it's worth doing, but it's not always obvious what the best approach is... and other similar operations may save only one or two bytes.
So I'm leaving that where it is for now; it's moved on to the state where I can enter code (any line order) _and_ type 'list' and get a list! Whee!
Neil
Re: Neolithic Tiny Basic
Posted: Tue Jan 28, 2025 4:10 pm
by barnacle
Oh damn, every time I look at it...
Code: Select all
; mem_last += insert_length
clc
lda mem_last
adc buffer+2
sta mem_last
lda mem_last+1
adc #0
sta mem_last+1
Is always adding an eight bit value to a sixteen bit, so it could be replaced by
Code: Select all
; mem_last += insert_length
clc
lda mem_last
adc buffer+2
sta mem_last
bcc $+4 ; $ is at start of bcc
inc mem_last+1
saving another two bytes.
Neil
Re: Neolithic Tiny Basic
Posted: Wed Jan 29, 2025 4:31 pm
by barnacle
To my great surprise, the print command pretty much worked first time:
The % prefix tells the numeric output to be right-justified in a seven-wide column. It is correctly evaluating expressions.
I can't check the two string output modes until I have the assign command working, and at the moment there's it's not displaying multiple parameters, which it should.
Neil
Re: Neolithic Tiny Basic
Posted: Wed Jan 29, 2025 8:01 pm
by barnacle
Multiple parameters are now printed; a jump to the wrong place
Neil
Re: Neolithic Tiny Basic
Posted: Tue Feb 04, 2025 8:23 am
by barnacle
Spot the obvious mistake, which took me a week of hair-pulling to find:
Code: Select all
isalpha:
cmp #'a'
bcc is_no
cmp #'z' + 1
bcc is_yes ; it's a lower case letter
cmp #'A'
bcc is_no
cmp #'Z' + 1
bcc is_yes ; it's upper case
; or drop through to is_no
;-----------------------------------------------------------------------
is_no:
clc
rts
should be
Code: Select all
isalpha:
cmp #'A'
bcc is_no
cmp #'Z' + 1
bcc is_yes ; it's a lower case letter
cmp #'a'
bcc is_no
cmp #'z' + 1
bcc is_yes ; it's upper case
; or drop through to is_no
;-----------------------------------------------------------------------
is_no:
clc
rts
Subtle... who'da thunk that 'A' was less than 'a' ?
Neil
p.s. shame the 6502 doesn't have a complement carry instruction...
Re: Neolithic Tiny Basic
Posted: Tue Feb 04, 2025 12:01 pm
by Yuri
p.s. shame the 6502 doesn't have a complement carry instruction...
Maybe something like so?
Code: Select all
is_alpha:
ora #$20
cmp #$7B
ror
eor #$80
rol
rts
EDIT: I'll get the right values in there sooner or later... @_@
Blah, still flawed with values less than 'A', feel like you don't need 4 compares tho....