Could anyone help me to read 64 bit from console in 32 bit RISC-V

772 views Asked by At

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.

1

There are 1 answers

0
Peter Cordes On BEST ANSWER

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. It of course follows the standard calling convention, taking the pointer in a0, and returning a 64-bit integer in a pair of registers, a1:a0:

# 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               # total in a1: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 dumb 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?