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:
Madeline Busig 2024-03-25 17:13:29 -06:00
parent b00a52ea9b
commit 1159162e50
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_udiv10(uint32_t x);
uint32_t mtl_umod10(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_utostr(uint32_t x, char* buf);
uint32_t mtl_itostr(uint32_t x, char* buf, size_t buf_size, char** buf_out); uint32_t mtl_utostrx(uint32_t x, char* buf);
uint32_t mtl_itostrx(uint32_t x, char* buf, size_t buf_size, char** buf_out); uint32_t mtl_itostr(int32_t x, char* buf);
uint32_t mtl_itostrx(int32_t x, char* buf);
#endif #endif
} }

View File

@ -2,244 +2,350 @@
.include "mtl/armv4t/asm/math.s" .include "mtl/armv4t/asm/math.s"
.section .rodata
.align 1
hex_digits_lo:
.string "0123456789abcdef"
hex_digits_up:
.string "0123456789ABCDEF"
.section .iwram, "ax", %progbits .section .iwram, "ax", %progbits
.arm .arm
.align 2 .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 ASCII representation of the given signed value X to the buffer,
* writes the beginning address of the string to an output ptr, and returns * returns the number of characters written. The value is written in decimal
* the number of characters written. The value is written in hexidecimal * format and is null terminated. The string will always begin at the start of
* format. * the buffer.
* *
* r0 = x * r0 = x
* r1 = buffer start * r1 = buffer
* 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
* ret= num of characters written * ret= num of characters written
*/ */
.global mtl_itostr .global mtl_itostr
.type mtl_itostr STT_FUNC .type mtl_itostr STT_FUNC
mtl_itostr: mtl_itostr:
// Store variables // Branch immediately if the number is positive
push {r4-r8} lsrs r12, r0, $31
// r8 = 1 if negative, 0 if positive. Negate if negative beq mtl_utostr
lsrs r8, r0, $31 // Write the negative sign to the string
mvnne r0, r0 mvn r0, r0
addne r0, $1 add r0, $1
// Move buffer to end // Get the absolute value of the number
add r1, r2 mov r12, '-'
sub r1, $1 strb r12, [r1], $1
// Add null terminator and decrement // Write abs(x) to the string
mov r6, $0 push {fp, lr}
str r6, [r1], $-1 bl mtl_utostr
// Zero num of characters pop {fp, lr}
mov r7, $0 // One more character was written to the string ('-')
.Lsdecode_digit: add r0, $1
// Increment number of characters bx lr
add r7, $1
// Use % 10 to get the least significant digit /*
umod10 r6, r0, r12 * Writes the ASCII representation of the given signed value X to the buffer,
// Write the ascii digit to the string * returns the number of characters written. The value is written in hexadecimal
add r5, r6, '0' * format and is null terminated. The string will always begin at the start of
strb r5, [r1], $-1 * the buffer.
// Divide by 10 to remove the least significant digit *
udiv10 r6, r0, r12 * r0 = x
mov r0, r6 * r1 = buffer
// Repeat if there are more digits left * ret= num of characters written
bne .Lsdecode_digit */
// Check if number is negative .global mtl_itostrx
cmp r8, $0 .type mtl_itostrx STT_FUNC
// Add the negative sign to the string and increase number of mtl_itostrx:
// characters written if needed // Branch immediately if the number is positive
movne r6, '-' lsrs r12, r0, $31
strbne r6, [r1] beq mtl_utostrx
addne r7, $1 // Write the negative sign to the string
// If the number was positive, currently positioned one char before str mov r12, '-'
// If the number was negative, currently at the correct position strb r12, [r1], $1
// Move one character forward if needed // Get the absolute value of the number
addeq r1, $1 mvn r0, r0
// Write the start of the string to the pointer add r0, $1
str r1, [r3] // Write abs(x) to the string
// Return the number of characters written push {fp, lr}
mov r0, r7 bl mtl_utostrx
// Restore variables pop {fp, lr}
pop {r4-r8} // 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 bx lr