mirror of
https://github.com/hsoft/collapseos.git
synced 2024-11-08 12:48:05 +11:00
38333e9e07
* 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.
164 lines
4.2 KiB
NASM
164 lines
4.2 KiB
NASM
; *** Consts ***
|
|
; maximum number of bytes to receive as args in all commands. Determines the
|
|
; size of the args variable.
|
|
.equ PARSE_ARG_MAXCOUNT 3
|
|
|
|
; *** 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
|
|
|
|
|
|
; Parses 2 characters of the string pointed to by HL and returns the numerical
|
|
; value in A. If the second character is a "special" character (<0x21) we don't
|
|
; error out: the result will be the one from the first char only.
|
|
; HL is set to point to the last char of the pair.
|
|
;
|
|
; On success, the carry flag is reset. On error, it is set.
|
|
parseHexPair:
|
|
push bc
|
|
|
|
ld a, (hl)
|
|
call parseHex
|
|
jr c, .end ; error? goto end, keeping the C flag on
|
|
rla \ rla \ rla \ rla ; let's push this in MSB
|
|
ld b, a
|
|
inc hl
|
|
ld a, (hl)
|
|
cp 0x21
|
|
jr c, .single ; special char? single digit
|
|
call parseHex
|
|
jr c, .end ; error?
|
|
or b ; join left-shifted + new. we're done!
|
|
; C flag was set on parseHex and is necessarily clear at this point
|
|
jr .end
|
|
|
|
.single:
|
|
; If we have a single digit, our result is already stored in B, but
|
|
; we have to right-shift it back.
|
|
ld a, b
|
|
and 0xf0
|
|
rra \ rra \ rra \ rra
|
|
dec hl
|
|
|
|
.end:
|
|
pop bc
|
|
ret
|
|
|
|
; Parse arguments at (HL) with specifiers at (DE) into (IX).
|
|
;
|
|
; Args specifiers are a series of flag for each arg:
|
|
; Bit 0 - arg present: if unset, we stop parsing there
|
|
; Bit 1 - is word: this arg is a word rather than a byte. Because our
|
|
; destination are bytes anyway, this doesn't change much except
|
|
; for whether we expect a space between the hex pairs. If set,
|
|
; you still need to have a specifier for the second part of
|
|
; the multibyte.
|
|
; Bit 2 - optional: If set and not present during parsing, we don't error out
|
|
; and write zero
|
|
;
|
|
; Bit 3 - String argument: If set, this argument is a string. A pointer to the
|
|
; read string, null terminated (max 0x20 chars) will
|
|
; be placed in the next two bytes. This has to be the
|
|
; last argument of the list and it stops parsing.
|
|
; Sets A to nonzero if there was an error during parsing, zero otherwise.
|
|
parseArgs:
|
|
push bc
|
|
push de
|
|
push hl
|
|
push ix
|
|
|
|
; init the arg value to a default 0
|
|
xor a
|
|
ld (ix), a
|
|
ld (ix+1), a
|
|
ld (ix+2), a
|
|
ld b, PARSE_ARG_MAXCOUNT
|
|
.loop:
|
|
ld a, (hl)
|
|
; is this the end of the line?
|
|
or a ; cp 0
|
|
jr z, .endofargs
|
|
|
|
; Get the specs
|
|
ld a, (de)
|
|
bit 0, a ; do we have an arg?
|
|
jr z, .error ; not set? then we have too many args
|
|
|
|
ld c, a ; save the specs for multibyte check later
|
|
bit 3, a ; is our arg a string?
|
|
jr z, .notAString
|
|
; our arg is a string. Let's place HL in our next two bytes and call
|
|
; it a day. Little endian, remember
|
|
ld (ix), l
|
|
ld (ix+1), h
|
|
jr .success ; directly to success: skip endofargs checks
|
|
|
|
.notAString:
|
|
call parseHexPair
|
|
jr c, .error
|
|
|
|
; we have a good arg and we need to write A in (IX).
|
|
ld (ix), a
|
|
|
|
; Good! increase counters
|
|
inc de
|
|
inc ix
|
|
inc hl ; get to following char (generally a space)
|
|
|
|
; Our arg is parsed, our pointers are increased. Normally, HL should
|
|
; point to a space *unless* our argspec indicates a multibyte arg.
|
|
bit 1, c
|
|
jr nz, .nospacecheck ; bit set? no space check
|
|
; do we have a proper space char (or null char)?
|
|
ld a, (hl)
|
|
or a
|
|
jr z, .endofargs
|
|
cp ' '
|
|
jr nz, .error
|
|
inc hl
|
|
.nospacecheck:
|
|
djnz .loop
|
|
; If we get here, it means that our next char *has* to be a null char
|
|
ld a, (hl)
|
|
or a ; cp 0
|
|
jr z, .success ; zero? great!
|
|
jr .error
|
|
|
|
.endofargs:
|
|
; We encountered our null char. Let's verify that we either have no
|
|
; more args or that they are optional
|
|
ld a, (de)
|
|
or a
|
|
jr z, .success ; no arg? success
|
|
bit 2, a
|
|
jr z, .error ; if unset, arg is not optional. error
|
|
; success
|
|
.success:
|
|
xor a
|
|
jr .end
|
|
.error:
|
|
inc a
|
|
.end:
|
|
pop ix
|
|
pop hl
|
|
pop de
|
|
pop bc
|
|
ret
|