Change armv4t integer to string conversion to use an unrolled loop

This implementation also writes the digits from left to right instead of
right to left. Using this method we can write the string to the
beginning of the buffer and still avoid reversing the string. It also
has the benefit of being slightly slower than the previous
implementation. The function's signature changed as well because there
is no longer a reason to pass the buffer size or a pointer to output the
start of the string.
This commit is contained in:
Myles Busig 2024-03-25 17:13:29 -06:00
parent bd1efb0bf9
commit dbdf8b6e3c
2 changed files with 337 additions and 230 deletions

View File

@ -17,10 +17,11 @@ void mtl_hybridmove(void* dst, const void* src, size_t num8);
uint32_t mtl_udiv10(uint32_t x);
uint32_t mtl_umod10(uint32_t x);
uint32_t mtl_utostr(uint32_t x, char* buf, size_t buf_size, char** buf_out);
uint32_t mtl_utostrx(uint32_t x, char* buf, size_t buf_size, char** buf_out);
uint32_t mtl_itostr(uint32_t x, char* buf, size_t buf_size, char** buf_out);
uint32_t mtl_itostrx(uint32_t x, char* buf, size_t buf_size, char** buf_out);
uint32_t mtl_utostr(uint32_t x, char* buf);
uint32_t mtl_utostrx(uint32_t x, char* buf);
uint32_t mtl_itostr(int32_t x, char* buf);
uint32_t mtl_itostrx(int32_t x, char* buf);
#endif
}

View File

@ -2,244 +2,350 @@
.include "mtl/armv4t/asm/math.s"
.section .rodata
.align 1
hex_digits_lo:
.string "0123456789abcdef"
hex_digits_up:
.string "0123456789ABCDEF"
.section .iwram, "ax", %progbits
.arm
.align 2
/*
* Writes the ASCII representation of the given unsigned value X to the buffer,
* writes the beginning address of the string to an output ptr, and returns
* the number of characters written. The value is written in hexidecimal
* format.
*
* r0 = x
* r1 = buffer start
* r2 = buffer size
* r3 = string start out ptr
* ret= num of characters written
*/
.global mtl_utostrx
.type mtl_utostrx STT_FUNC
mtl_utostrx:
// Store variables
push {r4-r7}
// Move buffer to end
add r1, r2
sub r1, $1
// Add null terminator and decrement
mov r6, $0
str r6, [r1], $-1
// Load digit address
ldr r4, =hex_digits_lo
// Zero num of characters
mov r7, $0
.Ldecode_digitx:
// Increment number of characters
add r7, $1
// Get least significant octet
and r6, r0, 0xF
// Write the ascii digit to the string
ldrb r5, [r4, r6]
strb r5, [r1], $-1
// Shift to remove the least significant octet
lsrs r0, $4
// Repeat if there are more digits left
bne .Ldecode_digitx
// Move to the start of the string and account for "0x",
// currently one character before
sub r1, $1
//add r1, $1
// Write "0x" to the beginning of the string
mov r6, '0'
strb r6, [r1]
mov r6, 'x'
strb r6, [r1, $1]
// Write the start of the string to the pointer
str r1, [r3]
// Return the number of characters written, account for "0x"
add r0, r7, $2
// Restore variables
pop {r4-r7}
bx lr
/*
* Writes the ASCII representation of the given unsigned value X to the buffer,
* writes the beginning address of the string to an output ptr, and returns
* the number of characters written. The value is written in decimal format.
*
* r0 = x
* r1 = buffer start
* r2 = buffer size
* r3 = string start out ptr
* ret= num of characters written
*/
.global mtl_utostr
.type mtl_utostr STT_FUNC
mtl_utostr:
// Store variables
push {r4-r7}
// Move buffer to end
add r1, r2
sub r1, $1
// Add null terminator and decrement
mov r6, $0
str r6, [r1], $-1
// Zero num of characters
mov r7, $0
.Ldecode_digit:
// Increment number of characters
add r7, $1
// Use % 10 to get the least significant digit
umod10 r6, r0, r12
// Write the ascii digit to the string
add r5, r6, '0'
strb r5, [r1], $-1
// Divide by 10 to remove the least significant digit
udiv10 r6, r0, r12
mov r0, r6
// Repeat if there are more digits left
bne .Ldecode_digit
// Move to the start of the string, currently one char before
add r1, $1
// Write the start of the string to the pointer
str r1, [r3]
// Return the number of characters written
mov r0, r7
// Restore variables
pop {r4-r7}
bx lr
/*
* Writes the ASCII representation of the given signed value X to the buffer,
* writes the beginning address of the string to an output ptr, and returns
* the number of characters written. The value is written in hexidecimal
* format.
* returns the number of characters written. The value is written in decimal
* format and is null terminated. The string will always begin at the start of
* the buffer.
*
* r0 = x
* r1 = buffer start
* r2 = buffer size
* r3 = string start out ptr
* ret= num of characters written
*/
.global mtl_itostrx
.type mtl_itostrx STT_FUNC
mtl_itostrx:
// Store variables
push {r4-r8}
// r8 = 1 if negative, 0 if positive. Negate if negative
lsrs r8, r0, $31
mvnne r0, r0
addne r0, $1
// Move buffer to end
add r1, r2
sub r1, $1
// Add null terminator and decrement
mov r6, $0
str r6, [r1], $-1
// Load digit address
ldr r4, =hex_digits_lo
// Zero num of characters
mov r7, $0
.Lsdecode_digitx:
// Increment number of characters
add r7, $1
// Get least significant octet
and r6, r0, 0xF
// Write the ascii digit to the string
ldrb r5, [r4, r6]
strb r5, [r1], $-1
// Shift to remove the least significant octet
lsrs r0, $4
// Repeat if there are more digits left
bne .Lsdecode_digitx
// Move to the start of the string and account for "0x",
// currently one character before
sub r1, $1
// Write "0x" to the beginning of the string
mov r6, '0'
strb r6, [r1]
mov r6, 'x'
strb r6, [r1, $1]
// Check if number is negative
cmp r8, $0
// Add the negative sign to the string and increase number of
// characters written if needed
subne r1, $1
movne r6, '-'
strbne r6, [r1]
addne r7, $1
// Write the start of the string to the pointer
str r1, [r3]
// Return the number of characters written, account for "0x"
add r0, r7, $2
// Restore variables
pop {r4-r8}
bx lr
/*
* Writes the ASCII representation of the given signed value X to the buffer,
* writes the beginning address of the string to an output ptr, and returns
* the number of characters written. The value is written in decimal format.
*
* r0 = x
* r1 = buffer start
* r2 = buffer size
* r3 = string start out ptr
* r1 = buffer
* ret= num of characters written
*/
.global mtl_itostr
.type mtl_itostr STT_FUNC
mtl_itostr:
// Store variables
push {r4-r8}
// r8 = 1 if negative, 0 if positive. Negate if negative
lsrs r8, r0, $31
mvnne r0, r0
addne r0, $1
// Move buffer to end
add r1, r2
sub r1, $1
// Add null terminator and decrement
mov r6, $0
str r6, [r1], $-1
// Zero num of characters
mov r7, $0
.Lsdecode_digit:
// Increment number of characters
add r7, $1
// Use % 10 to get the least significant digit
umod10 r6, r0, r12
// Write the ascii digit to the string
add r5, r6, '0'
strb r5, [r1], $-1
// Divide by 10 to remove the least significant digit
udiv10 r6, r0, r12
mov r0, r6
// Repeat if there are more digits left
bne .Lsdecode_digit
// Check if number is negative
cmp r8, $0
// Add the negative sign to the string and increase number of
// characters written if needed
movne r6, '-'
strbne r6, [r1]
addne r7, $1
// If the number was positive, currently positioned one char before str
// If the number was negative, currently at the correct position
// Move one character forward if needed
addeq r1, $1
// Write the start of the string to the pointer
str r1, [r3]
// Return the number of characters written
mov r0, r7
// Restore variables
pop {r4-r8}
// Branch immediately if the number is positive
lsrs r12, r0, $31
beq mtl_utostr
// Write the negative sign to the string
mvn r0, r0
add r0, $1
// Get the absolute value of the number
mov r12, '-'
strb r12, [r1], $1
// Write abs(x) to the string
push {fp, lr}
bl mtl_utostr
pop {fp, lr}
// One more character was written to the string ('-')
add r0, $1
bx lr
/*
* Writes the ASCII representation of the given signed value X to the buffer,
* returns the number of characters written. The value is written in hexadecimal
* format and is null terminated. The string will always begin at the start of
* the buffer.
*
* r0 = x
* r1 = buffer
* ret= num of characters written
*/
.global mtl_itostrx
.type mtl_itostrx STT_FUNC
mtl_itostrx:
// Branch immediately if the number is positive
lsrs r12, r0, $31
beq mtl_utostrx
// Write the negative sign to the string
mov r12, '-'
strb r12, [r1], $1
// Get the absolute value of the number
mvn r0, r0
add r0, $1
// Write abs(x) to the string
push {fp, lr}
bl mtl_utostrx
pop {fp, lr}
// One more character was written to the string ('-')
add r0, $1
bx lr
/*
* Writes the ASCII representation of the given unsigned value X to the buffer,
* returns the number of characters written. The value is written in decimal
* format and is null terminated. The string will always begin at the start of
* the buffer.
*
* Instead of using a loop like the previous implementation, this implementation
* uses a manually unrolled loop. This implementation also writes the digits
* from left to right instead of right to left. Using this method we can write
* the string to the beginning of the buffer and still avoid reversing the string.
*
* r0 = x
* r1 = buffer
* ret= num of characters written
*/
.global mtl_utostr
.type mtl_utostr STT_FUNC
mtl_utostr:
// Store variables
push {r4-r6}
// Temporary used to calculate 10^P
mov r5, $10
// Minimum of 1 digit
mov r3, $1
// If less than 10, one digit, otherwise more
mov r4, $10
cmp r0, r4
blo .Ldigits_1
// Now 2 digits
add r3, $1
// If less than 100, two digits, otherwise more
mul r4, r5
cmp r0, r4
blo .Ldigits_2
// Repeat...
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_3
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_4
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_5
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_6
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_7
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_8
add r3, $1
mul r4, r5
cmp r0, r4
blo .Ldigits_9
// 10 digits
add r3, $1
.Ldigits_10:
// Divide by 10^9 to get the most significant digit
udiv1000000000 r5, r0, r12
// Write the digit to the string
add r6, r5, '0'
strb r6, [r1], $1
// Subtract the most significant digit
ldr r6, =1000000000
mul r6, r5
sub r0, r6
.Ldigits_9:
// Repeat...
udiv100000000 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
ldr r6, =100000000
mul r6, r5
sub r0, r6
.Ldigits_8:
udiv10000000 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
ldr r6, =10000000
mul r6, r5
sub r0, r6
.Ldigits_7:
udiv1000000 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
ldr r6, =1000000
mul r6, r5
sub r0, r6
.Ldigits_6:
udiv100000 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
ldr r6, =100000
mul r6, r5
sub r0, r6
.Ldigits_5:
udiv10000 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
ldr r6, =10000
mul r6, r5
sub r0, r6
.Ldigits_4:
udiv1000 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
mov r6, $1000
mul r6, r5
sub r0, r6
.Ldigits_3:
udiv100 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
mov r6, $100
mul r6, r5
sub r0, r6
.Ldigits_2:
udiv10 r5, r0, r12
add r6, r5, '0'
strb r6, [r1], $1
mov r6, $10
mul r6, r5
sub r0, r6
.Ldigits_1:
add r6, r0, '0'
strb r6, [r1], $1
// Write the null terminator
mov r6, $0
strb r6, [r1]
// Return number of digits written
mov r0, r3
// Restore variables
pop {r4-r6}
bx lr
/*
* Writes the ASCII representation of the given unsigned value X to the buffer,
* returns the number of characters written. The value is written in hexadecimal
* format and is null terminated. The string will always begin at the start of
* the buffer.
*
* Instead of using a loop like the previous implementation, this implementation
* uses a manually unrolled loop. This implementation also writes the digits
* from left to right instead of right to left. Using this method we can write
* the string to the beginning of the buffer and still avoid reversing the string.
*
* r0 = x
* r1 = buffer
* ret= num of characters written
*/
.global mtl_utostrx
.type mtl_utostrx STT_FUNC
mtl_utostrx:
// Store variables
push {r4-r6}
// Write "0x" to the string
mov r6, '0'
strb r6, [r1], $1
mov r6, 'x'
strb r6, [r1], $1
// Minimum of 3 characters (including "0x")
mov r3, $3
// If less than 0x10, one digit, otherwise more
mov r4, $0x10
cmp r0, r4
blo .Lxdigits_1
// Now four characters
add r3, $1
// If less than 0x100, two digits, otherwise more
lsl r4, $4
cmp r0, r4
blo .Lxdigits_2
// Repeat...
add r3, $1
lsl r4, $4
cmp r0, r4
blo .Lxdigits_3
add r3, $1
lsl r4, $4
cmp r0, r4
blo .Lxdigits_4
add r3, $1
lsl r4, $4
cmp r0, r4
blo .Lxdigits_5
add r3, $1
lsl r4, $4
cmp r0, r4
blo .Lxdigits_6
add r3, $1
lsl r4, $4
cmp r0, r4
blo .Lxdigits_7
// Eight digits
add r3, $1
.Lxdigits_8:
// Right shift to get the most significant digit
lsr r5, r0, $28
// Get digit character
add r6, r5, '0'
// If the digit > 9, change to a lowercase letter
cmp r6, '9'
addhi r6, $0x27
// Write the digit
strb r6, [r1], $1
// Subtract the digit
lsl r5, $28
sub r0, r5
.Lxdigits_7:
// Repeat...
lsr r5, r0, $24
add r6, r5, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
lsl r5, $24
sub r0, r5
.Lxdigits_6:
lsr r5, r0, $20
add r6, r5, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
lsl r5, $20
sub r0, r5
.Lxdigits_5:
lsr r5, r0, $16
add r6, r5, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
lsl r5, $16
sub r0, r5
.Lxdigits_4:
lsr r5, r0, $12
add r6, r5, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
lsl r5, $12
sub r0, r5
.Lxdigits_3:
lsr r5, r0, $8
add r6, r5, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
lsl r5, $8
sub r0, r5
.Lxdigits_2:
lsr r5, r0, $4
add r6, r5, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
lsl r5, $4
sub r0, r5
.Lxdigits_1:
add r6, r0, '0'
cmp r6, '9'
addhi r6, $0x27
strb r6, [r1], $1
// Write the null terminator
mov r6, $0
strb r6, [r1]
// Return the number of digits written
mov r0, r3
// Restore variables
pop {r4-r6}
bx lr