I am new to assembly, but could anyone teach me how to read 64 bit from console in 32 bit RISC-V?
.eqv SYS_EXITO, 10
.eqv CON_PRTSTR, 4
.eqv CON_PRTINT, 1
.eqv CON_RDINT, 5
.eqv BUFSIZE, 100
.data
prompt:
.asciz "Read 64 bit integer:"
result:
.asciz "Output:"
buf:
.space BUFSIZE
.text
main:
la a0, prompt
li a7, CON_PRTSTR
ecall
la a0, buf
li a1, BUFSIZE
li a7, CON_RDINT
ecall
Then we I input 4294967295, the following error occured.
Error in /private/var/folders/bf/t4py6npj0v38grsvrgvq1dx00000gn/T/hsperfdata_sotarosuzuki/riscv1.asm line 24: Runtime exception at 0x00400020: invalid integer input (syscall 5)
So, should I read the integers as string and convert it to integer? I have searched for this solution, but I cannot find it.
CodePudding user response:
Yeah, if you can't use the toy system calls, read a string and do total = total*10 digit
on it, where digit = c-'0'
. You'll need to do extended-precision multiply, so it's probably easier to do extended-precision shifts like (total << 3) (total << 1)
.
Check compiler output on Godbolt. For example, GCC using shifts, clang using mul
/mulhu
(high unsigned) for the lo * lo
32x32=>64-bit partial product, and a mul
for the high half cross product (hi * lo
). It's fewer instructions, but depends on a RISC-V CPU with a fast multiplier to be faster than shift/or.
(RISC-V extended-precision addition is inconvenient since it doesn't have a carry flag, you need to emulate carry-out as unsigned sum = a b;
carry = sum<a;
)
#include <stdint.h>
uint64_t strtou64(unsigned char*p){
uint64_t total = 0;
unsigned digit = *p - '0'; // peeling the first iteration is usually good in asm
while (digit < 10) { // loop until any non-digit character
total = total*10 digit;
p ; // *p was checked before the loop or last iteration
digit = *p - '0'; // get a digit ready for the loop branch
}
return total;
}
Clang's output is shorter, so I'll show it:
# rv32gc clang 14.0 -O3
strtou64:
mv a2, a0
lbu a0, 0(a0) # load the first char
addi a3, a0, -48 # *p - '0'
li a0, 9
bltu a0, a3, .LBB0_4 # return 0 if the first char is a non-digit
li a0, 0 # should have done these before the branch
li a1, 0 # so a separate ret wouldn't be needed
addi a2, a2, 1 # p
li a6, 10 # multiplier constant
.LBB0_2: # do{
mulhu a5, a0, a6 # high half of (lo(total) * 10)
mul a1, a1, a6 # hi(total) * 10
add a1, a1, a5 # add the high-half partial products
mul a5, a0, a6 # low half of (lo(total) * 10)
lbu a4, 0(a2) # load *p
add a0, a5, a3 # lo(total) = lo(total*10) digit
sltu a3, a0, a5 # carry-out from that
add a1, a1, a3 # propagate carry into hi(total)
addi a3, a4, -48 # digit = *p - '0'
addi a2, a2, 1 # p done after the load; clang peeled one pointer increment before the loop
bltu a3, a6, .LBB0_2 # }while(digit < 10)
ret
.LBB0_4:
li a0, 0 # return 0 special case
li a1, 0 # because clang was dump and didn't load these regs before branching
ret
If you want to go with GCC's shift/or strategy, it should be straightforward to see how that slots in to the same logic clang is using. You can look at compiler output for a function like return u64 << 3
to see which instructions are part of that.
And BTW, I wrote the C with compiling to decent asm in mind, making it easy for the compiler to transform it into a do{}while
loop with the condition at the bottom. I based it on the x86 asm in my answer on NASM Assembly convert input to integer?