2019-04-15 01:11:13 +10:00
|
|
|
; core
|
|
|
|
;
|
|
|
|
; Routines used by pretty much all parts. You will want to include it first
|
|
|
|
; in your glue file.
|
|
|
|
|
|
|
|
; *** CONSTS ***
|
2019-06-15 12:14:09 +10:00
|
|
|
.equ ASCII_BS 0x08
|
2019-05-15 06:42:44 +10:00
|
|
|
.equ ASCII_CR 0x0d
|
|
|
|
.equ ASCII_LF 0x0a
|
2019-06-15 12:14:09 +10:00
|
|
|
.equ ASCII_DEL 0x7f
|
2019-04-15 01:11:13 +10:00
|
|
|
|
2019-04-24 03:29:16 +10:00
|
|
|
; *** DATA ***
|
|
|
|
; Useful data to point to, when a pointer is needed.
|
|
|
|
P_NULL: .db 0
|
|
|
|
|
2019-04-23 04:26:16 +10:00
|
|
|
; *** REGISTER FIDDLING ***
|
2019-04-15 01:11:13 +10:00
|
|
|
|
|
|
|
; add the value of A into DE
|
|
|
|
addDE:
|
2019-05-01 05:51:39 +10:00
|
|
|
push af
|
2019-04-15 01:11:13 +10:00
|
|
|
add a, e
|
|
|
|
jr nc, .end ; no carry? skip inc
|
|
|
|
inc d
|
|
|
|
.end:
|
|
|
|
ld e, a
|
2019-05-01 05:51:39 +10:00
|
|
|
pop af
|
2019-06-01 01:12:29 +10:00
|
|
|
noop: ; piggy backing on the first "ret" we have
|
2019-04-15 01:11:13 +10:00
|
|
|
ret
|
|
|
|
|
2019-10-11 05:44:23 +11:00
|
|
|
; copy (HL) into DE, then exchange the two, utilising the optimised HL instructions.
|
|
|
|
; ld must be done little endian, so least significant byte first.
|
|
|
|
intoHL:
|
|
|
|
push de
|
|
|
|
ld e, (hl)
|
|
|
|
inc hl
|
|
|
|
ld d, (hl)
|
|
|
|
ex de, hl
|
|
|
|
pop de
|
2019-04-16 05:08:00 +10:00
|
|
|
ret
|
|
|
|
|
2019-10-11 05:44:23 +11:00
|
|
|
intoDE:
|
|
|
|
ex de, hl
|
|
|
|
call intoHL
|
|
|
|
ex de, hl ; de preserved by intoHL, so no push/pop needed
|
2019-04-24 03:29:16 +10:00
|
|
|
ret
|
|
|
|
|
2019-06-05 01:53:02 +10:00
|
|
|
intoIX:
|
2019-10-11 05:44:23 +11:00
|
|
|
push ix
|
|
|
|
ex (sp), hl ;swap hl with ix, on the stack
|
|
|
|
call intoHL
|
|
|
|
ex (sp), hl ;restore hl from stack
|
|
|
|
pop ix
|
2019-06-05 01:53:02 +10:00
|
|
|
ret
|
|
|
|
|
2019-04-15 13:14:07 +10:00
|
|
|
; add the value of A into HL
|
addHL and subHL affect flags, and are smaller (#30)
* addHL and subHL affect flags, and are smaller
Most importantly, addHL and subHL now affect the flags as you would expect from a 16 bit addition/subtraction. This seems like it'd be preferred behaviour, however I realise any code relying on it not affecting flags would break. One byte saved in addHL, and two bytes saved in subHL. Due to the branching nature of the original code, it's difficult to compare speeds, subHL is either 1 or 6 cycles faster depending on branching, and addHL is between -1 and 3 cycles faster. If the chance of a carry is 50%, addHL is expected to be a cycle faster, but for a chance of carry below 25% (so a < 0x40) this will be up to a cycle slower.
* Update core.asm
* Reworked one use of addHL
By essentially inlining both addHL and cpHLDE, 100 cycles are saved, but due to the registers not needing preserving, a byte is saved too.
* Corrected spelling error in comment
* Reworked second use of addHL
43 cycles saved, and no more addHL in critical loops. No bytes saved or used.
* Fixed tabs and spacing, and made a comment clearer.
* Clearer comments
* Adopted push/pop notation
2019-10-18 07:45:27 +11:00
|
|
|
; affects carry flag according to the 16-bit addition, Z, S and P untouched.
|
2019-04-15 13:14:07 +10:00
|
|
|
addHL:
|
addHL and subHL affect flags, and are smaller (#30)
* addHL and subHL affect flags, and are smaller
Most importantly, addHL and subHL now affect the flags as you would expect from a 16 bit addition/subtraction. This seems like it'd be preferred behaviour, however I realise any code relying on it not affecting flags would break. One byte saved in addHL, and two bytes saved in subHL. Due to the branching nature of the original code, it's difficult to compare speeds, subHL is either 1 or 6 cycles faster depending on branching, and addHL is between -1 and 3 cycles faster. If the chance of a carry is 50%, addHL is expected to be a cycle faster, but for a chance of carry below 25% (so a < 0x40) this will be up to a cycle slower.
* Update core.asm
* Reworked one use of addHL
By essentially inlining both addHL and cpHLDE, 100 cycles are saved, but due to the registers not needing preserving, a byte is saved too.
* Corrected spelling error in comment
* Reworked second use of addHL
43 cycles saved, and no more addHL in critical loops. No bytes saved or used.
* Fixed tabs and spacing, and made a comment clearer.
* Clearer comments
* Adopted push/pop notation
2019-10-18 07:45:27 +11:00
|
|
|
push de
|
|
|
|
ld d, 0
|
|
|
|
ld e, a
|
|
|
|
add hl, de
|
|
|
|
pop de
|
2019-04-15 13:14:07 +10:00
|
|
|
ret
|
|
|
|
|
addHL and subHL affect flags, and are smaller (#30)
* addHL and subHL affect flags, and are smaller
Most importantly, addHL and subHL now affect the flags as you would expect from a 16 bit addition/subtraction. This seems like it'd be preferred behaviour, however I realise any code relying on it not affecting flags would break. One byte saved in addHL, and two bytes saved in subHL. Due to the branching nature of the original code, it's difficult to compare speeds, subHL is either 1 or 6 cycles faster depending on branching, and addHL is between -1 and 3 cycles faster. If the chance of a carry is 50%, addHL is expected to be a cycle faster, but for a chance of carry below 25% (so a < 0x40) this will be up to a cycle slower.
* Update core.asm
* Reworked one use of addHL
By essentially inlining both addHL and cpHLDE, 100 cycles are saved, but due to the registers not needing preserving, a byte is saved too.
* Corrected spelling error in comment
* Reworked second use of addHL
43 cycles saved, and no more addHL in critical loops. No bytes saved or used.
* Fixed tabs and spacing, and made a comment clearer.
* Clearer comments
* Adopted push/pop notation
2019-10-18 07:45:27 +11:00
|
|
|
|
2019-05-29 05:56:39 +10:00
|
|
|
; subtract the value of A from HL
|
addHL and subHL affect flags, and are smaller (#30)
* addHL and subHL affect flags, and are smaller
Most importantly, addHL and subHL now affect the flags as you would expect from a 16 bit addition/subtraction. This seems like it'd be preferred behaviour, however I realise any code relying on it not affecting flags would break. One byte saved in addHL, and two bytes saved in subHL. Due to the branching nature of the original code, it's difficult to compare speeds, subHL is either 1 or 6 cycles faster depending on branching, and addHL is between -1 and 3 cycles faster. If the chance of a carry is 50%, addHL is expected to be a cycle faster, but for a chance of carry below 25% (so a < 0x40) this will be up to a cycle slower.
* Update core.asm
* Reworked one use of addHL
By essentially inlining both addHL and cpHLDE, 100 cycles are saved, but due to the registers not needing preserving, a byte is saved too.
* Corrected spelling error in comment
* Reworked second use of addHL
43 cycles saved, and no more addHL in critical loops. No bytes saved or used.
* Fixed tabs and spacing, and made a comment clearer.
* Clearer comments
* Adopted push/pop notation
2019-10-18 07:45:27 +11:00
|
|
|
; affects flags according to the 16-bit subtraction.
|
2019-05-29 05:56:39 +10:00
|
|
|
subHL:
|
addHL and subHL affect flags, and are smaller (#30)
* addHL and subHL affect flags, and are smaller
Most importantly, addHL and subHL now affect the flags as you would expect from a 16 bit addition/subtraction. This seems like it'd be preferred behaviour, however I realise any code relying on it not affecting flags would break. One byte saved in addHL, and two bytes saved in subHL. Due to the branching nature of the original code, it's difficult to compare speeds, subHL is either 1 or 6 cycles faster depending on branching, and addHL is between -1 and 3 cycles faster. If the chance of a carry is 50%, addHL is expected to be a cycle faster, but for a chance of carry below 25% (so a < 0x40) this will be up to a cycle slower.
* Update core.asm
* Reworked one use of addHL
By essentially inlining both addHL and cpHLDE, 100 cycles are saved, but due to the registers not needing preserving, a byte is saved too.
* Corrected spelling error in comment
* Reworked second use of addHL
43 cycles saved, and no more addHL in critical loops. No bytes saved or used.
* Fixed tabs and spacing, and made a comment clearer.
* Clearer comments
* Adopted push/pop notation
2019-10-18 07:45:27 +11:00
|
|
|
push de
|
|
|
|
ld d, 0
|
|
|
|
ld e, a
|
|
|
|
or a ;reset carry flag
|
|
|
|
sbc hl, de ;There is no 'sub hl, de', so we must use sbc
|
|
|
|
pop de
|
2019-05-29 05:56:39 +10:00
|
|
|
ret
|
2019-04-16 06:53:11 +10:00
|
|
|
|
2019-06-01 01:12:29 +10:00
|
|
|
; Compare HL with DE and sets Z and C in the same way as a regular cp X where
|
|
|
|
; HL is A and DE is X.
|
|
|
|
cpHLDE:
|
2019-10-17 11:39:38 +11:00
|
|
|
push hl
|
|
|
|
or a ;reset carry flag
|
|
|
|
sbc hl, de ;There is no 'sub hl, de', so we must use sbc
|
|
|
|
pop hl
|
2019-07-24 12:59:10 +10:00
|
|
|
ret
|
2019-06-01 01:12:29 +10:00
|
|
|
|
2019-04-16 06:53:11 +10:00
|
|
|
; Write the contents of HL in (DE)
|
2019-10-17 11:39:38 +11:00
|
|
|
; de and hl are preserved, so no pushing/popping necessary
|
2019-04-16 06:53:11 +10:00
|
|
|
writeHLinDE:
|
2019-10-17 11:39:38 +11:00
|
|
|
ex de, hl
|
|
|
|
ld (hl), e
|
|
|
|
inc hl
|
|
|
|
ld (hl), d
|
|
|
|
dec hl
|
|
|
|
ex de, hl
|
2019-04-16 06:53:11 +10:00
|
|
|
ret
|
|
|
|
|
2019-06-05 01:53:02 +10:00
|
|
|
; Call the method (IX) is a pointer to. In other words, call intoIX before
|
|
|
|
; callIX
|
|
|
|
callIXI:
|
|
|
|
push ix
|
|
|
|
call intoIX
|
|
|
|
call callIX
|
|
|
|
pop ix
|
|
|
|
ret
|
|
|
|
|
2019-04-15 07:01:28 +10:00
|
|
|
; jump to the location pointed to by IX. This allows us to call IX instead of
|
2019-06-01 01:12:29 +10:00
|
|
|
; just jumping it. We use IX because we seldom use this for arguments.
|
2019-04-15 07:01:28 +10:00
|
|
|
callIX:
|
|
|
|
jp (ix)
|
|
|
|
|
2019-04-24 05:50:26 +10:00
|
|
|
callIY:
|
|
|
|
jp (iy)
|
|
|
|
|
2019-04-23 04:26:16 +10:00
|
|
|
; Ensures that Z is unset (more complicated than it sounds...)
|
Decimal parse optimisations (#45)
* Optimised parsing functions and other minor optimisations
UnsetZ has been reduced by a byte, and between 17 and 28 cycles saved based on branching. Since branching is based on a being 0, it shouldn't have to branch very often and so be 28 cycles saved most the time. Including the initial call, the old version was 60 cycles, so this should be nearly twice as fast.
fmtHex has been reduced by 4 bytes and between 3 and 8 cycles based on branching.
fmtHexPair had a redundant "and" removed, saving two bytes and seven cycles.
parseHex has been reduced by 7 bytes. Due to so much branching, it's hard to say if it's faster, but it should be since it's fewer operations and now conditional returns are used which are a cycle faster than conditional jumps. I think there's more to improve here, but I haven't come up with anything yet.
* Major parsing optimisations
Totally reworked both parseDecimal and parseDecimalDigit
parseDecimalDigit no longer exists, as it could be replaced by an inline alternative in the 4 places it appeared. This saves one byte overall, as the inline version is 4 bytes, 1 byte more than a call, and removing the function saved 5 bytes. It has been reduced from between 52 and 35 cycles (35 on error, so we'd expect 52 cycles to be more common unless someone's really bad at programming) to 14 cycles, so 2-3 times faster.
parseDecimal has been reduced by a byte, and now the main loop is just about twice as fast, but with increased overhead. To put this into perspective, if we ignore error cases:
For decimals of length 1 it'll be 1.20x faster, for decimals of length 2, 1.41x faster, for length 3, 1.51x faster, for length 4, 1.57x faster, and for length 5 and above, at least 1.48x faster (even faster if there's leading zeroes or not the worst case scenario).
I believe there is still room for improvement, since the first iteration can be nearly replaced with "ld l, c" since 0*10=0, but when I tried this I could either add a zero check into the main loop, adding around 40 cycles and 10 bytes, or add 20 bytes to the overhead, and I don't think either of those options are worth it.
* Inlined parseDecimalDigit
See previous commit, and /lib/parse.asm, for details
* Fixed tabs and spacing
* Fixed tabs and spacing
* Better explanation and layout
* Corrected error in comments, and a new parseHex
5 bytes saved in parseHex, again hard to say what that does to speed, the shortest possible speed is probably a little slower but I think non-error cases should be around 9 cycles faster for decimal and 18 cycles faster for hex as there's now only two conditional returns and no compliment carries.
* Fixed the new parseHex
I accidentally did `add 0xe9` without specifying `a`
* Commented the use of daa
I made the comments surrounding my use of daa much clearer, so it isn't quite so mystical what's being done here.
* Removed skip leading zeroes, added skip first multiply
Now instead of skipping leading zeroes, the first digit is loaded directly into hl without first multiplying by 10. This means the first loop is skipped in the overhead, making the method 2-3 times faster overall, and is now faster for the more common fewer digit cases too. The number of bytes is exactly the same, and the inner loop is slightly faster too thanks to no longer needing to load a into c.
To be more precise about the speed increase over the current code, for decimals of length 1 it'll be 3.18x faster, for decimals of length 2, 2.50x faster, for length 3, 2.31x faster, for length 4, 2.22x faster, and for length 5 and above, at least 2.03x faster. In terms of cycles, this is around 100+(132*length) cycles saved per decimal.
* Fixed erroring out for all number >0x1999
I fixed the errors for numbers >0x1999, sadly it is now 6 bytes bigger, so 5 bytes larger than the original, but the speed increases should still hold.
* Fixed more errors, clearer choice of constants
* Clearer choice of constants
* Moved and indented comment about fmtHex's method
* Marked inlined parseDecimalDigit uses
* Renamed .error, removed trailing whitespace, more verbose comments.
2019-10-24 22:58:32 +11:00
|
|
|
; There are often better inline alternatives, either replacing rets with
|
|
|
|
; appropriate jmps, or if an 8 bit register is known to not be 0, an inc
|
|
|
|
; then a dec. If a is nonzero, 'or a' is optimal.
|
2019-04-23 04:26:16 +10:00
|
|
|
unsetZ:
|
Decimal parse optimisations (#45)
* Optimised parsing functions and other minor optimisations
UnsetZ has been reduced by a byte, and between 17 and 28 cycles saved based on branching. Since branching is based on a being 0, it shouldn't have to branch very often and so be 28 cycles saved most the time. Including the initial call, the old version was 60 cycles, so this should be nearly twice as fast.
fmtHex has been reduced by 4 bytes and between 3 and 8 cycles based on branching.
fmtHexPair had a redundant "and" removed, saving two bytes and seven cycles.
parseHex has been reduced by 7 bytes. Due to so much branching, it's hard to say if it's faster, but it should be since it's fewer operations and now conditional returns are used which are a cycle faster than conditional jumps. I think there's more to improve here, but I haven't come up with anything yet.
* Major parsing optimisations
Totally reworked both parseDecimal and parseDecimalDigit
parseDecimalDigit no longer exists, as it could be replaced by an inline alternative in the 4 places it appeared. This saves one byte overall, as the inline version is 4 bytes, 1 byte more than a call, and removing the function saved 5 bytes. It has been reduced from between 52 and 35 cycles (35 on error, so we'd expect 52 cycles to be more common unless someone's really bad at programming) to 14 cycles, so 2-3 times faster.
parseDecimal has been reduced by a byte, and now the main loop is just about twice as fast, but with increased overhead. To put this into perspective, if we ignore error cases:
For decimals of length 1 it'll be 1.20x faster, for decimals of length 2, 1.41x faster, for length 3, 1.51x faster, for length 4, 1.57x faster, and for length 5 and above, at least 1.48x faster (even faster if there's leading zeroes or not the worst case scenario).
I believe there is still room for improvement, since the first iteration can be nearly replaced with "ld l, c" since 0*10=0, but when I tried this I could either add a zero check into the main loop, adding around 40 cycles and 10 bytes, or add 20 bytes to the overhead, and I don't think either of those options are worth it.
* Inlined parseDecimalDigit
See previous commit, and /lib/parse.asm, for details
* Fixed tabs and spacing
* Fixed tabs and spacing
* Better explanation and layout
* Corrected error in comments, and a new parseHex
5 bytes saved in parseHex, again hard to say what that does to speed, the shortest possible speed is probably a little slower but I think non-error cases should be around 9 cycles faster for decimal and 18 cycles faster for hex as there's now only two conditional returns and no compliment carries.
* Fixed the new parseHex
I accidentally did `add 0xe9` without specifying `a`
* Commented the use of daa
I made the comments surrounding my use of daa much clearer, so it isn't quite so mystical what's being done here.
* Removed skip leading zeroes, added skip first multiply
Now instead of skipping leading zeroes, the first digit is loaded directly into hl without first multiplying by 10. This means the first loop is skipped in the overhead, making the method 2-3 times faster overall, and is now faster for the more common fewer digit cases too. The number of bytes is exactly the same, and the inner loop is slightly faster too thanks to no longer needing to load a into c.
To be more precise about the speed increase over the current code, for decimals of length 1 it'll be 3.18x faster, for decimals of length 2, 2.50x faster, for length 3, 2.31x faster, for length 4, 2.22x faster, and for length 5 and above, at least 2.03x faster. In terms of cycles, this is around 100+(132*length) cycles saved per decimal.
* Fixed erroring out for all number >0x1999
I fixed the errors for numbers >0x1999, sadly it is now 6 bytes bigger, so 5 bytes larger than the original, but the speed increases should still hold.
* Fixed more errors, clearer choice of constants
* Clearer choice of constants
* Moved and indented comment about fmtHex's method
* Marked inlined parseDecimalDigit uses
* Renamed .error, removed trailing whitespace, more verbose comments.
2019-10-24 22:58:32 +11:00
|
|
|
or a ;if a nonzero, Z reset
|
|
|
|
ret nz
|
|
|
|
cp 1 ;if a is zero, Z reset
|
2019-04-23 04:26:16 +10:00
|
|
|
ret
|
|
|
|
|
Decimal parse optimisations (#45)
* Optimised parsing functions and other minor optimisations
UnsetZ has been reduced by a byte, and between 17 and 28 cycles saved based on branching. Since branching is based on a being 0, it shouldn't have to branch very often and so be 28 cycles saved most the time. Including the initial call, the old version was 60 cycles, so this should be nearly twice as fast.
fmtHex has been reduced by 4 bytes and between 3 and 8 cycles based on branching.
fmtHexPair had a redundant "and" removed, saving two bytes and seven cycles.
parseHex has been reduced by 7 bytes. Due to so much branching, it's hard to say if it's faster, but it should be since it's fewer operations and now conditional returns are used which are a cycle faster than conditional jumps. I think there's more to improve here, but I haven't come up with anything yet.
* Major parsing optimisations
Totally reworked both parseDecimal and parseDecimalDigit
parseDecimalDigit no longer exists, as it could be replaced by an inline alternative in the 4 places it appeared. This saves one byte overall, as the inline version is 4 bytes, 1 byte more than a call, and removing the function saved 5 bytes. It has been reduced from between 52 and 35 cycles (35 on error, so we'd expect 52 cycles to be more common unless someone's really bad at programming) to 14 cycles, so 2-3 times faster.
parseDecimal has been reduced by a byte, and now the main loop is just about twice as fast, but with increased overhead. To put this into perspective, if we ignore error cases:
For decimals of length 1 it'll be 1.20x faster, for decimals of length 2, 1.41x faster, for length 3, 1.51x faster, for length 4, 1.57x faster, and for length 5 and above, at least 1.48x faster (even faster if there's leading zeroes or not the worst case scenario).
I believe there is still room for improvement, since the first iteration can be nearly replaced with "ld l, c" since 0*10=0, but when I tried this I could either add a zero check into the main loop, adding around 40 cycles and 10 bytes, or add 20 bytes to the overhead, and I don't think either of those options are worth it.
* Inlined parseDecimalDigit
See previous commit, and /lib/parse.asm, for details
* Fixed tabs and spacing
* Fixed tabs and spacing
* Better explanation and layout
* Corrected error in comments, and a new parseHex
5 bytes saved in parseHex, again hard to say what that does to speed, the shortest possible speed is probably a little slower but I think non-error cases should be around 9 cycles faster for decimal and 18 cycles faster for hex as there's now only two conditional returns and no compliment carries.
* Fixed the new parseHex
I accidentally did `add 0xe9` without specifying `a`
* Commented the use of daa
I made the comments surrounding my use of daa much clearer, so it isn't quite so mystical what's being done here.
* Removed skip leading zeroes, added skip first multiply
Now instead of skipping leading zeroes, the first digit is loaded directly into hl without first multiplying by 10. This means the first loop is skipped in the overhead, making the method 2-3 times faster overall, and is now faster for the more common fewer digit cases too. The number of bytes is exactly the same, and the inner loop is slightly faster too thanks to no longer needing to load a into c.
To be more precise about the speed increase over the current code, for decimals of length 1 it'll be 3.18x faster, for decimals of length 2, 2.50x faster, for length 3, 2.31x faster, for length 4, 2.22x faster, and for length 5 and above, at least 2.03x faster. In terms of cycles, this is around 100+(132*length) cycles saved per decimal.
* Fixed erroring out for all number >0x1999
I fixed the errors for numbers >0x1999, sadly it is now 6 bytes bigger, so 5 bytes larger than the original, but the speed increases should still hold.
* Fixed more errors, clearer choice of constants
* Clearer choice of constants
* Moved and indented comment about fmtHex's method
* Marked inlined parseDecimalDigit uses
* Renamed .error, removed trailing whitespace, more verbose comments.
2019-10-24 22:58:32 +11:00
|
|
|
|
2019-04-23 04:26:16 +10:00
|
|
|
; *** STRINGS ***
|
|
|
|
|
2019-04-24 03:29:16 +10:00
|
|
|
; Fill B bytes at (HL) with A
|
|
|
|
fill:
|
|
|
|
push bc
|
|
|
|
push hl
|
|
|
|
.loop:
|
|
|
|
ld (hl), a
|
|
|
|
inc hl
|
|
|
|
djnz .loop
|
|
|
|
pop hl
|
|
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
2019-04-23 23:37:22 +10:00
|
|
|
; Increase HL until the memory address it points to is equal to A for a maximum
|
|
|
|
; of 0xff bytes. Returns the new HL value as well as the number of bytes
|
|
|
|
; iterated in A.
|
|
|
|
; If a null char is encountered before we find A, processing is stopped in the
|
|
|
|
; same way as if we found our char (so, we look for A *or* 0)
|
2019-05-15 05:26:29 +10:00
|
|
|
; Set Z if the character is found. Unsets it if not
|
2019-04-23 23:37:22 +10:00
|
|
|
findchar:
|
2019-04-15 03:56:04 +10:00
|
|
|
push bc
|
2019-04-23 23:37:22 +10:00
|
|
|
ld c, a ; let's use C as our cp target
|
2019-04-15 03:56:04 +10:00
|
|
|
ld a, 0xff
|
|
|
|
ld b, a
|
|
|
|
|
|
|
|
.loop: ld a, (hl)
|
2019-04-23 23:37:22 +10:00
|
|
|
cp c
|
2019-05-15 05:26:29 +10:00
|
|
|
jr z, .match
|
|
|
|
or a ; cp 0
|
|
|
|
jr z, .nomatch
|
2019-04-15 03:56:04 +10:00
|
|
|
inc hl
|
|
|
|
djnz .loop
|
2019-05-15 05:26:29 +10:00
|
|
|
.nomatch:
|
|
|
|
call unsetZ
|
|
|
|
jr .end
|
|
|
|
.match:
|
2019-04-15 03:56:04 +10:00
|
|
|
; We ran 0xff-B loops. That's the result that goes in A.
|
|
|
|
ld a, 0xff
|
2019-05-18 10:47:43 +10:00
|
|
|
sub b
|
2019-05-15 05:26:29 +10:00
|
|
|
cp a ; ensure Z
|
|
|
|
.end:
|
2019-04-15 03:56:04 +10:00
|
|
|
pop bc
|
|
|
|
ret
|
|
|
|
|
Decimal parse optimisations (#45)
* Optimised parsing functions and other minor optimisations
UnsetZ has been reduced by a byte, and between 17 and 28 cycles saved based on branching. Since branching is based on a being 0, it shouldn't have to branch very often and so be 28 cycles saved most the time. Including the initial call, the old version was 60 cycles, so this should be nearly twice as fast.
fmtHex has been reduced by 4 bytes and between 3 and 8 cycles based on branching.
fmtHexPair had a redundant "and" removed, saving two bytes and seven cycles.
parseHex has been reduced by 7 bytes. Due to so much branching, it's hard to say if it's faster, but it should be since it's fewer operations and now conditional returns are used which are a cycle faster than conditional jumps. I think there's more to improve here, but I haven't come up with anything yet.
* Major parsing optimisations
Totally reworked both parseDecimal and parseDecimalDigit
parseDecimalDigit no longer exists, as it could be replaced by an inline alternative in the 4 places it appeared. This saves one byte overall, as the inline version is 4 bytes, 1 byte more than a call, and removing the function saved 5 bytes. It has been reduced from between 52 and 35 cycles (35 on error, so we'd expect 52 cycles to be more common unless someone's really bad at programming) to 14 cycles, so 2-3 times faster.
parseDecimal has been reduced by a byte, and now the main loop is just about twice as fast, but with increased overhead. To put this into perspective, if we ignore error cases:
For decimals of length 1 it'll be 1.20x faster, for decimals of length 2, 1.41x faster, for length 3, 1.51x faster, for length 4, 1.57x faster, and for length 5 and above, at least 1.48x faster (even faster if there's leading zeroes or not the worst case scenario).
I believe there is still room for improvement, since the first iteration can be nearly replaced with "ld l, c" since 0*10=0, but when I tried this I could either add a zero check into the main loop, adding around 40 cycles and 10 bytes, or add 20 bytes to the overhead, and I don't think either of those options are worth it.
* Inlined parseDecimalDigit
See previous commit, and /lib/parse.asm, for details
* Fixed tabs and spacing
* Fixed tabs and spacing
* Better explanation and layout
* Corrected error in comments, and a new parseHex
5 bytes saved in parseHex, again hard to say what that does to speed, the shortest possible speed is probably a little slower but I think non-error cases should be around 9 cycles faster for decimal and 18 cycles faster for hex as there's now only two conditional returns and no compliment carries.
* Fixed the new parseHex
I accidentally did `add 0xe9` without specifying `a`
* Commented the use of daa
I made the comments surrounding my use of daa much clearer, so it isn't quite so mystical what's being done here.
* Removed skip leading zeroes, added skip first multiply
Now instead of skipping leading zeroes, the first digit is loaded directly into hl without first multiplying by 10. This means the first loop is skipped in the overhead, making the method 2-3 times faster overall, and is now faster for the more common fewer digit cases too. The number of bytes is exactly the same, and the inner loop is slightly faster too thanks to no longer needing to load a into c.
To be more precise about the speed increase over the current code, for decimals of length 1 it'll be 3.18x faster, for decimals of length 2, 2.50x faster, for length 3, 2.31x faster, for length 4, 2.22x faster, and for length 5 and above, at least 2.03x faster. In terms of cycles, this is around 100+(132*length) cycles saved per decimal.
* Fixed erroring out for all number >0x1999
I fixed the errors for numbers >0x1999, sadly it is now 6 bytes bigger, so 5 bytes larger than the original, but the speed increases should still hold.
* Fixed more errors, clearer choice of constants
* Clearer choice of constants
* Moved and indented comment about fmtHex's method
* Marked inlined parseDecimalDigit uses
* Renamed .error, removed trailing whitespace, more verbose comments.
2019-10-24 22:58:32 +11:00
|
|
|
|
2019-04-15 01:11:13 +10:00
|
|
|
; Format the lower nibble of A into a hex char and stores the result in A.
|
|
|
|
fmtHex:
|
Decimal parse optimisations (#45)
* Optimised parsing functions and other minor optimisations
UnsetZ has been reduced by a byte, and between 17 and 28 cycles saved based on branching. Since branching is based on a being 0, it shouldn't have to branch very often and so be 28 cycles saved most the time. Including the initial call, the old version was 60 cycles, so this should be nearly twice as fast.
fmtHex has been reduced by 4 bytes and between 3 and 8 cycles based on branching.
fmtHexPair had a redundant "and" removed, saving two bytes and seven cycles.
parseHex has been reduced by 7 bytes. Due to so much branching, it's hard to say if it's faster, but it should be since it's fewer operations and now conditional returns are used which are a cycle faster than conditional jumps. I think there's more to improve here, but I haven't come up with anything yet.
* Major parsing optimisations
Totally reworked both parseDecimal and parseDecimalDigit
parseDecimalDigit no longer exists, as it could be replaced by an inline alternative in the 4 places it appeared. This saves one byte overall, as the inline version is 4 bytes, 1 byte more than a call, and removing the function saved 5 bytes. It has been reduced from between 52 and 35 cycles (35 on error, so we'd expect 52 cycles to be more common unless someone's really bad at programming) to 14 cycles, so 2-3 times faster.
parseDecimal has been reduced by a byte, and now the main loop is just about twice as fast, but with increased overhead. To put this into perspective, if we ignore error cases:
For decimals of length 1 it'll be 1.20x faster, for decimals of length 2, 1.41x faster, for length 3, 1.51x faster, for length 4, 1.57x faster, and for length 5 and above, at least 1.48x faster (even faster if there's leading zeroes or not the worst case scenario).
I believe there is still room for improvement, since the first iteration can be nearly replaced with "ld l, c" since 0*10=0, but when I tried this I could either add a zero check into the main loop, adding around 40 cycles and 10 bytes, or add 20 bytes to the overhead, and I don't think either of those options are worth it.
* Inlined parseDecimalDigit
See previous commit, and /lib/parse.asm, for details
* Fixed tabs and spacing
* Fixed tabs and spacing
* Better explanation and layout
* Corrected error in comments, and a new parseHex
5 bytes saved in parseHex, again hard to say what that does to speed, the shortest possible speed is probably a little slower but I think non-error cases should be around 9 cycles faster for decimal and 18 cycles faster for hex as there's now only two conditional returns and no compliment carries.
* Fixed the new parseHex
I accidentally did `add 0xe9` without specifying `a`
* Commented the use of daa
I made the comments surrounding my use of daa much clearer, so it isn't quite so mystical what's being done here.
* Removed skip leading zeroes, added skip first multiply
Now instead of skipping leading zeroes, the first digit is loaded directly into hl without first multiplying by 10. This means the first loop is skipped in the overhead, making the method 2-3 times faster overall, and is now faster for the more common fewer digit cases too. The number of bytes is exactly the same, and the inner loop is slightly faster too thanks to no longer needing to load a into c.
To be more precise about the speed increase over the current code, for decimals of length 1 it'll be 3.18x faster, for decimals of length 2, 2.50x faster, for length 3, 2.31x faster, for length 4, 2.22x faster, and for length 5 and above, at least 2.03x faster. In terms of cycles, this is around 100+(132*length) cycles saved per decimal.
* Fixed erroring out for all number >0x1999
I fixed the errors for numbers >0x1999, sadly it is now 6 bytes bigger, so 5 bytes larger than the original, but the speed increases should still hold.
* Fixed more errors, clearer choice of constants
* Clearer choice of constants
* Moved and indented comment about fmtHex's method
* Marked inlined parseDecimalDigit uses
* Renamed .error, removed trailing whitespace, more verbose comments.
2019-10-24 22:58:32 +11:00
|
|
|
; The idea here is that there's 7 characters between '9' and 'A'
|
|
|
|
; in the ASCII table, and so we add 7 if the digit is >9.
|
|
|
|
; daa is designed for using Binary Coded Decimal format, where each
|
|
|
|
; nibble represents a single base 10 digit. If a nibble has a value >9,
|
|
|
|
; it adds 6 to that nibble, carrying to the next nibble and bringing the
|
|
|
|
; value back between 0-9. This gives us 6 of that 7 we needed to add, so
|
|
|
|
; then we just condtionally set the carry and add that carry, along with
|
|
|
|
; a number that maps 0 to '0'. We also need the upper nibble to be a
|
|
|
|
; set value, and have the N, C and H flags clear.
|
|
|
|
or 0xf0
|
|
|
|
daa ; now a =0x50 + the original value + 0x06 if >= 0xfa
|
|
|
|
add a, 0xa0 ; cause a carry for the values that were >=0x0a
|
|
|
|
adc a, 0x40
|
2019-04-15 01:11:13 +10:00
|
|
|
ret
|
|
|
|
|
|
|
|
; Formats value in A into a string hex pair. Stores it in the memory location
|
|
|
|
; that HL points to. Does *not* add a null char at the end.
|
|
|
|
fmtHexPair:
|
|
|
|
push af
|
|
|
|
|
|
|
|
; let's start with the rightmost char
|
|
|
|
inc hl
|
|
|
|
call fmtHex
|
|
|
|
ld (hl), a
|
|
|
|
|
|
|
|
; and now with the leftmost
|
|
|
|
dec hl
|
|
|
|
pop af
|
|
|
|
push af
|
|
|
|
rra \ rra \ rra \ rra
|
|
|
|
call fmtHex
|
|
|
|
ld (hl), a
|
|
|
|
|
|
|
|
pop af
|
|
|
|
ret
|
|
|
|
|
Decimal parse optimisations (#45)
* Optimised parsing functions and other minor optimisations
UnsetZ has been reduced by a byte, and between 17 and 28 cycles saved based on branching. Since branching is based on a being 0, it shouldn't have to branch very often and so be 28 cycles saved most the time. Including the initial call, the old version was 60 cycles, so this should be nearly twice as fast.
fmtHex has been reduced by 4 bytes and between 3 and 8 cycles based on branching.
fmtHexPair had a redundant "and" removed, saving two bytes and seven cycles.
parseHex has been reduced by 7 bytes. Due to so much branching, it's hard to say if it's faster, but it should be since it's fewer operations and now conditional returns are used which are a cycle faster than conditional jumps. I think there's more to improve here, but I haven't come up with anything yet.
* Major parsing optimisations
Totally reworked both parseDecimal and parseDecimalDigit
parseDecimalDigit no longer exists, as it could be replaced by an inline alternative in the 4 places it appeared. This saves one byte overall, as the inline version is 4 bytes, 1 byte more than a call, and removing the function saved 5 bytes. It has been reduced from between 52 and 35 cycles (35 on error, so we'd expect 52 cycles to be more common unless someone's really bad at programming) to 14 cycles, so 2-3 times faster.
parseDecimal has been reduced by a byte, and now the main loop is just about twice as fast, but with increased overhead. To put this into perspective, if we ignore error cases:
For decimals of length 1 it'll be 1.20x faster, for decimals of length 2, 1.41x faster, for length 3, 1.51x faster, for length 4, 1.57x faster, and for length 5 and above, at least 1.48x faster (even faster if there's leading zeroes or not the worst case scenario).
I believe there is still room for improvement, since the first iteration can be nearly replaced with "ld l, c" since 0*10=0, but when I tried this I could either add a zero check into the main loop, adding around 40 cycles and 10 bytes, or add 20 bytes to the overhead, and I don't think either of those options are worth it.
* Inlined parseDecimalDigit
See previous commit, and /lib/parse.asm, for details
* Fixed tabs and spacing
* Fixed tabs and spacing
* Better explanation and layout
* Corrected error in comments, and a new parseHex
5 bytes saved in parseHex, again hard to say what that does to speed, the shortest possible speed is probably a little slower but I think non-error cases should be around 9 cycles faster for decimal and 18 cycles faster for hex as there's now only two conditional returns and no compliment carries.
* Fixed the new parseHex
I accidentally did `add 0xe9` without specifying `a`
* Commented the use of daa
I made the comments surrounding my use of daa much clearer, so it isn't quite so mystical what's being done here.
* Removed skip leading zeroes, added skip first multiply
Now instead of skipping leading zeroes, the first digit is loaded directly into hl without first multiplying by 10. This means the first loop is skipped in the overhead, making the method 2-3 times faster overall, and is now faster for the more common fewer digit cases too. The number of bytes is exactly the same, and the inner loop is slightly faster too thanks to no longer needing to load a into c.
To be more precise about the speed increase over the current code, for decimals of length 1 it'll be 3.18x faster, for decimals of length 2, 2.50x faster, for length 3, 2.31x faster, for length 4, 2.22x faster, and for length 5 and above, at least 2.03x faster. In terms of cycles, this is around 100+(132*length) cycles saved per decimal.
* Fixed erroring out for all number >0x1999
I fixed the errors for numbers >0x1999, sadly it is now 6 bytes bigger, so 5 bytes larger than the original, but the speed increases should still hold.
* Fixed more errors, clearer choice of constants
* Clearer choice of constants
* Moved and indented comment about fmtHex's method
* Marked inlined parseDecimalDigit uses
* Renamed .error, removed trailing whitespace, more verbose comments.
2019-10-24 22:58:32 +11:00
|
|
|
|
2019-04-15 01:11:13 +10:00
|
|
|
; Compares strings pointed to by HL and DE up to A count of characters. If
|
|
|
|
; equal, Z is set. If not equal, Z is reset.
|
|
|
|
strncmp:
|
|
|
|
push bc
|
|
|
|
push hl
|
|
|
|
push de
|
|
|
|
|
|
|
|
ld b, a
|
|
|
|
.loop:
|
|
|
|
ld a, (de)
|
|
|
|
cp (hl)
|
2019-04-15 07:01:28 +10:00
|
|
|
jr nz, .end ; not equal? break early. NZ is carried out
|
|
|
|
; to the called
|
|
|
|
cp 0 ; If our chars are null, stop the cmp
|
|
|
|
jr z, .end ; The positive result will be carried to the
|
|
|
|
; caller
|
2019-04-15 01:11:13 +10:00
|
|
|
inc hl
|
|
|
|
inc de
|
|
|
|
djnz .loop
|
2019-04-15 07:01:28 +10:00
|
|
|
; We went through all chars with success, but our current Z flag is
|
|
|
|
; unset because of the cp 0. Let's do a dummy CP to set the Z flag.
|
|
|
|
cp a
|
2019-04-15 01:11:13 +10:00
|
|
|
|
|
|
|
.end:
|
|
|
|
pop de
|
|
|
|
pop hl
|
|
|
|
pop bc
|
|
|
|
; Because we don't call anything else than CP that modify the Z flag,
|
|
|
|
; our Z value will be that of the last cp (reset if we broke the loop
|
|
|
|
; early, set otherwise)
|
|
|
|
ret
|
|
|
|
|
|
|
|
; Transforms the character in A, if it's in the a-z range, into its upcase
|
|
|
|
; version.
|
|
|
|
upcase:
|
|
|
|
cp 'a'
|
|
|
|
ret c ; A < 'a'. nothing to do
|
|
|
|
cp 'z'+1
|
|
|
|
ret nc ; A >= 'z'+1. nothing to do
|
|
|
|
; 'a' - 'A' == 0x20
|
|
|
|
sub 0x20
|
|
|
|
ret
|
2019-04-15 03:56:04 +10:00
|
|
|
|