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 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 * ret= num of characters written
*/ */
.global mtl_utostrx .global mtl_itostr
.type mtl_utostrx STT_FUNC .type mtl_itostr STT_FUNC
mtl_utostrx: mtl_itostr:
// Store variables // Branch immediately if the number is positive
push {r4-r7} lsrs r12, r0, $31
// Move buffer to end beq mtl_utostr
add r1, r2 // Write the negative sign to the string
sub r1, $1 mvn r0, r0
// Add null terminator and decrement add r0, $1
mov r6, $0 // Get the absolute value of the number
str r6, [r1], $-1 mov r12, '-'
// Load digit address strb r12, [r1], $1
ldr r4, =hex_digits_lo // Write abs(x) to the string
// Zero num of characters push {fp, lr}
mov r7, $0 bl mtl_utostr
.Ldecode_digitx: pop {fp, lr}
// Increment number of characters // One more character was written to the string ('-')
add r7, $1 add r0, $1
// Get least significant octet bx lr
and r6, r0, 0xF
// Write the ascii digit to the string /*
ldrb r5, [r4, r6] * Writes the ASCII representation of the given signed value X to the buffer,
strb r5, [r1], $-1 * returns the number of characters written. The value is written in hexadecimal
// Shift to remove the least significant octet * format and is null terminated. The string will always begin at the start of
lsrs r0, $4 * the buffer.
// Repeat if there are more digits left *
bne .Ldecode_digitx * r0 = x
// Move to the start of the string and account for "0x", * r1 = buffer
// currently one character before * ret= num of characters written
sub r1, $1 */
//add r1, $1 .global mtl_itostrx
// Write "0x" to the beginning of the string .type mtl_itostrx STT_FUNC
mov r6, '0' mtl_itostrx:
strb r6, [r1] // Branch immediately if the number is positive
mov r6, 'x' lsrs r12, r0, $31
strb r6, [r1, $1] beq mtl_utostrx
// Write the start of the string to the pointer // Write the negative sign to the string
str r1, [r3] mov r12, '-'
// Return the number of characters written, account for "0x" strb r12, [r1], $1
add r0, r7, $2 // Get the absolute value of the number
// Restore variables mvn r0, r0
pop {r4-r7} 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 bx lr
/* /*
* Writes the ASCII representation of the given unsigned value X to the buffer, * 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 * returns the number of characters written. The value is written in decimal
* the number of characters written. The value is written in decimal format. * 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 * r0 = x
* r1 = buffer start * r1 = buffer
* r2 = buffer size
* r3 = string start out ptr
* ret= num of characters written * ret= num of characters written
*/ */
.global mtl_utostr .global mtl_utostr
.type mtl_utostr STT_FUNC .type mtl_utostr STT_FUNC
mtl_utostr: mtl_utostr:
// Store variables // Store variables
push {r4-r7} push {r4-r6}
// Move buffer to end // Temporary used to calculate 10^P
add r1, r2 mov r5, $10
sub r1, $1 // Minimum of 1 digit
// Add null terminator and decrement mov r3, $1
mov r6, $0 // If less than 10, one digit, otherwise more
str r6, [r1], $-1 mov r4, $10
// Zero num of characters cmp r0, r4
mov r7, $0 blo .Ldigits_1
.Ldecode_digit: // Now 2 digits
// Increment number of characters add r3, $1
add r7, $1 // If less than 100, two digits, otherwise more
// Use % 10 to get the least significant digit mul r4, r5
umod10 r6, r0, r12 cmp r0, r4
// Write the ascii digit to the string blo .Ldigits_2
add r5, r6, '0' // Repeat...
strb r5, [r1], $-1 add r3, $1
// Divide by 10 to remove the least significant digit mul r4, r5
udiv10 r6, r0, r12 cmp r0, r4
mov r0, r6 blo .Ldigits_3
// Repeat if there are more digits left add r3, $1
bne .Ldecode_digit mul r4, r5
// Move to the start of the string, currently one char before cmp r0, r4
add r1, $1 blo .Ldigits_4
// Write the start of the string to the pointer add r3, $1
str r1, [r3] mul r4, r5
// Return the number of characters written cmp r0, r4
mov r0, r7 blo .Ldigits_5
// Restore variables add r3, $1
pop {r4-r7} mul r4, r5
bx lr 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
* 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.
*
* 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 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] strb r6, [r1]
mov r6, 'x' // Return number of digits written
strb r6, [r1, $1] mov r0, r3
// 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 // Restore variables
pop {r4-r8} pop {r4-r6}
bx lr 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 * Writes the ASCII representation of the given unsigned value X to the buffer,
* the number of characters written. The value is written in decimal format. * 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
* r0 = x * the buffer.
* r1 = buffer start *
* r2 = buffer size * Instead of using a loop like the previous implementation, this implementation
* r3 = string start out ptr * uses a manually unrolled loop. This implementation also writes the digits
* ret= num of characters written * 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.
.global mtl_itostr *
.type mtl_itostr STT_FUNC * r0 = x
mtl_itostr: * r1 = buffer
// Store variables * ret= num of characters written
push {r4-r8} */
// r8 = 1 if negative, 0 if positive. Negate if negative .global mtl_utostrx
lsrs r8, r0, $31 .type mtl_utostrx STT_FUNC
mvnne r0, r0 mtl_utostrx:
addne r0, $1 // Store variables
// Move buffer to end push {r4-r6}
add r1, r2 // Write "0x" to the string
sub r1, $1 mov r6, '0'
// Add null terminator and decrement strb r6, [r1], $1
mov r6, $0 mov r6, 'x'
str r6, [r1], $-1 strb r6, [r1], $1
// Zero num of characters
mov r7, $0 // Minimum of 3 characters (including "0x")
.Lsdecode_digit: mov r3, $3
// Increment number of characters // If less than 0x10, one digit, otherwise more
add r7, $1 mov r4, $0x10
// Use % 10 to get the least significant digit cmp r0, r4
umod10 r6, r0, r12 blo .Lxdigits_1
// Write the ascii digit to the string // Now four characters
add r5, r6, '0' add r3, $1
strb r5, [r1], $-1 // If less than 0x100, two digits, otherwise more
// Divide by 10 to remove the least significant digit lsl r4, $4
udiv10 r6, r0, r12 cmp r0, r4
mov r0, r6 blo .Lxdigits_2
// Repeat if there are more digits left // Repeat...
bne .Lsdecode_digit add r3, $1
// Check if number is negative lsl r4, $4
cmp r8, $0 cmp r0, r4
// Add the negative sign to the string and increase number of blo .Lxdigits_3
// characters written if needed add r3, $1
movne r6, '-' lsl r4, $4
strbne r6, [r1] cmp r0, r4
addne r7, $1 blo .Lxdigits_4
// If the number was positive, currently positioned one char before str add r3, $1
// If the number was negative, currently at the correct position lsl r4, $4
// Move one character forward if needed cmp r0, r4
addeq r1, $1 blo .Lxdigits_5
// Write the start of the string to the pointer add r3, $1
str r1, [r3] lsl r4, $4
// Return the number of characters written cmp r0, r4
mov r0, r7 blo .Lxdigits_6
// Restore variables add r3, $1
pop {r4-r8} 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