collapseos/apps/lib/parse.asm

241 lines
5.4 KiB
NASM
Raw Normal View History

; *** Requirements ***
; lib/util
; *** Code ***
; Parse the hex char at A and extract it's 0-15 numerical value. Put the result
; in A.
;
; On success, the carry flag is reset. On error, it is set.
parseHex:
; First, let's see if we have an easy 0-9 case
add a, 0xc6 ; maps '0'-'9' onto 0xf6-0xff
sub 0xf6 ; maps to 0-9 and carries if not a digit
ret nc
and 0xdf ; converts lowercase to uppercase
add a, 0xe9 ; map 0x11-x017 onto 0xFA - 0xFF
sub 0xfa ; map onto 0-6
ret c
; we have an A-F digit
add a, 10 ; C is clear, map back to 0xA-0xF
ret
; Parse the decimal char at A and extract it's 0-9 numerical value. Put the
; result in A.
;
; On success, the carry flag is reset. On error, it is set.
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
; Also, zero flag set if '0'
; parseDecimalDigit has been replaced with the following code inline:
; add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
; sub 0xff-9 ; maps to 0-9 and carries if not a digit
; Parse string at (HL) as a decimal value and return value in DE under the
; same conditions as parseLiteral.
; Sets Z on success, unset on error.
; To parse successfully, all characters following HL must be digits and those
; digits must form a number that fits in 16 bits. To end the number, both \0
; and whitespaces (0x20 and 0x09) are accepted. There must be at least one
; digit in the string.
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
parseDecimal:
push hl ; --> lvl 1
ld a, (hl)
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
add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
sub 0xff-9 ; maps to 0-9 and carries if not a digit
jr c, .error ; not a digit on first char? error
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
exx ; preserve bc, hl, de
ld h, 0
ld l, a ; load first digit in without multiplying
ld b, 3 ; Carries can only occur for decimals >=5 in length
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
.loop:
exx
inc hl
ld a, (hl)
exx
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
; inline parseDecimalDigit
add a, 0xff-'9' ; maps '0'-'9' onto 0xf6-0xff
sub 0xff-9 ; maps to 0-9 and carries if not a digit
jr c, .end
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
add hl, hl ; x2
ld d, h
ld e, l ; de is x2
add hl, hl ; x4
add hl, hl ; x8
add hl, de ; x10
ld d, 0
ld e, a
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
add hl, de
jr c, .end ; if hl was 0x1999, it may carry here
djnz .loop
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
inc b ; so loop only executes once more
; only numbers >0x1999 can carry when multiplied by 10.
ld de, 0xE666
ex de, hl
add hl, de
ex de, hl
jr nc, .loop ; if it doesn't carry, it's small enough
exx
inc hl
ld a, (hl)
exx
add a, 0xd0 ; the next line expects a null to be mapped to 0xd0
.end:
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
; Because of the add and sub in parseDecimalDigit, null is mapped
; to 0x00+(0xff-'9')-(0xff-9)=-0x30=0xd0
sub 0xd0 ; if a is null, set Z
; a is checked for null before any errors
push hl ; --> lvl 2, result
exx ; restore original bc
pop de ; <-- lvl 2, result
pop hl ; <-- lvl 1, orig
ret z
; A is not 0? Ok, but if it's a space, we're happy too.
2019-11-21 12:58:26 +11:00
jp isWS
.error:
pop hl ; <-- lvl 1, orig
jp unsetZ
; Parse string at (HL) as a hexadecimal value without the "0x" prefix and
; return value in DE.
; HL is advanced to the character following the last successfully read char.
; Sets Z on success.
parseHexadecimal:
ld a, (hl)
call parseHex
jp c, unsetZ ; we need at least one char
push bc
ld de, 0
ld b, 0
.loop:
; we push to B to verify overflow
rl e \ rl d \ rl b
rl e \ rl d \ rl b
rl e \ rl d \ rl b
rl e \ rl d \ rl b
or e
ld e, a
; did we overflow?
ld a, b
or a
jr nz, .end ; overflow, NZ already set
; next char
inc hl
ld a, (hl)
call parseHex
jr nc, .loop
cp a ; ensure Z
.end:
pop bc
ret
; Parse string at (HL) as a binary value (010101) without the "0b" prefix and
; return value in E. D is always zero.
; Sets Z on success.
parseBinaryLiteral:
push bc
push hl
call strlen
or a
jr z, .error ; empty, error
cp 9
jr nc, .error ; >= 9, too long
; We have a string of 8 or less chars. What we'll do is that for each
; char, we rotate left and set the LSB according to whether we have '0'
; or '1'. Error out on anything else. C is our stored result.
ld b, a ; we loop for "strlen" times
ld c, 0 ; our stored result
.loop:
rlc c
ld a, (hl)
inc hl
cp '0'
jr z, .nobit ; no bit to set
cp '1'
jr nz, .error ; not 0 or 1
; We have a bit to set
inc c
.nobit:
djnz .loop
ld e, c
cp a ; ensure Z
jr .end
.error:
call unsetZ
.end:
pop hl
pop bc
ret
; Parses the string at (HL) and returns the 16-bit value in DE. The string
; can be a decimal literal (1234), a hexadecimal literal (0x1234) or a char
; literal ('X').
;
; As soon as the number doesn't fit 16-bit any more, parsing stops and the
; number is invalid. If the number is valid, Z is set, otherwise, unset.
parseLiteral:
ld de, 0 ; pre-fill
ld a, (hl)
cp 0x27 ; apostrophe
jr z, .char
cp '0'
jr z, .hexOrBin
jp parseDecimal
; Parse string at (HL) and, if it is a char literal, sets Z and return
; corresponding value in E. D is always zero.
;
; A valid char literal starts with ', ends with ' and has one character in the
; middle. No escape sequence are accepted, but ''' will return the apostrophe
; character.
.char:
push hl
inc hl
inc hl
cp (hl)
jr nz, .charEnd ; not ending with an apostrophe
inc hl
ld a, (hl)
or a ; cp 0
jr nz, .charEnd ; string has to end there
; Valid char, good
dec hl
dec hl
ld e, (hl)
cp a ; ensure Z
.charEnd:
pop hl
ret
.hexOrBin:
inc hl
ld a, (hl)
inc hl ; already place it for hex or bin
cp 'x'
jr z, .hex
cp 'b'
jr z, .bin
; special case: single '0'. set Z if we hit have null terminating.
or a
.hexOrBinEnd:
dec hl \ dec hl ; replace HL
ret ; Z already set
.hex:
push hl
call parseHexadecimal
pop hl
jr .hexOrBinEnd
.bin:
call parseBinaryLiteral
jr .hexOrBinEnd