assembly64-bitriscvbigintrars-simulator

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


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.


Solution

  • 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?