Optimizing a faster isdigit()
juj opened this issue · comments
The implementation of isdigit()
reads
llvm-mos-sdk/mos-platform/common/c/ctype.c
Lines 20 to 23 in b3c6a27
which generates the following assembly:
0000 AA TAX
0001 A9 00 LDA #$00
0003 E0 30 CPX #$30
0005 90 0D BCC $0014
0007 E0 3A CPX #$3A
0009 90 05 BCC $0010
000B A9 00 LDA #$00
000D 4C 12 00 JMP $0012
0010 A9 01 LBB0_3 LDA #$01
0012 29 01 LBB0_4 AND #$01
0014 A2 00 LBB0_5 LDX #$00
0016 A8 TAY
0017 F0 03 BEQ $001C
0019 A9 01 LDA #$01
001B 60 RTS
001C A9 00 LBB0_7 LDA #$00
001E 60 RTS
that is 31 bytes.
By resorting to unsigned wraparound (and changing function signature for simplicity of presentation in this ticket), the C code can be written in the following form:
bool is_digit(unsigned char ch)
{
ch -= '0';
return ch <= 9;
}
to get
0000 18 CLC
0001 69 D0 ADC #$D0
0003 C9 0A CMP #$0A
0005 90 03 BCC $000A
0007 A9 00 LDA #$00
0009 60 RTS
000A LBB0_2
000A A9 01 LDA #$01
000C 60 RTS
for 13 bytes.
Although, following the observation in llvm-mos/llvm-mos#388, doing >
operations is faster than doing <
operations, since >
operations can avoid a EOR
operator. So the above C code could instead be written as
bool is_digit2(unsigned char ch)
{
ch -= ':';
return ch >= 245;
}
for the same effect. This can then be turned into
bool is_digit3(unsigned char ch)
{
bool digit;
asm(R"(
CLC
ADC #198
CMP #246
LDA #00
ROL A
)":"=a"(digit):"a"(ch):"c");
return digit;
}
that would ideally be the smallest
0000 18 CLC
0001 69 C6 ADC #$C6
0003 C9 F6 CMP #$F6
0005 A9 00 LDA #$00
0007 2A ROL A
0008 29 01 AND #$01 ; redundant
000A 60 RTS
for 11 bytes. Although compiler insists on emitting the AND #$01
here (llvm-mos/llvm-mos#389), so the above could be two bytes shorter still.
I would suggest replacing ':`` with
'9' + 1in
isdigit_2`, like so:
bool is_digit2(unsigned char ch)
{
ch -= ('9' + 1);
return ch >= 245;
}
and adapting isdigit_3
accordingly. While it is a good assumption in most character sets to assume that '0'
through '9'
are contiguous and in order, ':'
coming immediately after '9'
is specific to ASCII, which 65XX platforms (especially game consoles) don't always use.