0

I'm learning how to convert RISC-V assembly code to C, and I don't understand this conversion. A few questions I have:

  1. why is t1 being initialized to 6 instead of 0?
  2. We're using bne to compare t1 and t2, but we were taught to use the opposite, meaning the C code would be while (t1 == t0). It appears this opposite technique was used for if (t1 ==0). Why?
  3. Lastly, why is addi t0, t0, -1 used instead of sub?

Any insight is so greatly appreciated. My classroom environment is fast paced and not very question friendly, so I'm nervous to ask in lecture.

main:
    # Tests simple looping behavior
    li t0, 60
    li t1, 0
loop:
    addi t1, t1, 5
    addi t0, t0, -1
    bne t1, t0, loop
    bne t1, zero, success
failure:
    li a0, 0
    li a7, 93 
    ecall
    
success:
    li a0, 42 
    li a7, 93
    ecall

This is the answer I was given:

int main(){

    int t0 = 60;
    int t1 = 6;
    
    while(t1 != t0){
        t1 = t1 + 5;
        t0 = t0 - 1;
    }

    if(t1 == 0){
        int a0 = 0;
        return 0;
    }else{
        int a0 = 42;    
        return 0;    
    }
}

We were taught to use the opposite of bne/beq when converting C to RISC-V, so it's confusing why the 'correct' C conversion for this RISC-V assembly would include while (t1 != t0).

Initializing t1 to 6 also doesn't make any sense to me. It looks to be clearly loaded to 0 with 'li t1, 0'.

2
  • 1
    The C doesn't match the RISC-V assembly very well. You're right, the asm clearly does t1 = 0 not t1 = 6. Perhaps a typo or OCR error if this material was printed out and scanned back in or something. Those ecalls are exit(0) and exit(42), not return statements. The rest looks right, though. Try it yourself, single-stepping through the asm vs. through the C program, watching registers or variables change, respectively. It should be clear that execution stays in the loop until they're equal, i.e. while they're not equal. Commented Nov 13, 2023 at 1:17
  • The assembly code is doing do ... while () whereas the C code is doing while () { /* do */ ...}. The difference is that the do/while will execute one iteration before checking the exit condition, whereas the while/do code will check the exit condition before the first iteration. So, here's another example of how these two (C vs. assembly) are not really identical. Commented Nov 13, 2023 at 1:21

1 Answer 1

0

The C doesn't match the RISC-V assembly very well.
You're right, the asm clearly does t1 = 0 not t1 = 6. Perhaps a typo or OCR error if this material was printed out and scanned back in or something. Or maybe someone just changed their mind about the starting point for the loops but forgot to update one of the versions. 0 and 6 do both lead to the loop terminating without wrapping around (or actually C signed-overflow undefined behaviour since this isn't unsigned.)

Also, those ecalls are _exit(0) and _exit(42), not return statements. The asm doesn't use its return address.


The rest looks right, though. Try it yourself, single-stepping through the asm vs. through the C program, watching registers or variables change, respectively. It should be clear that execution stays in the loop until they're equal, i.e. while they're not equal.

The most direct C representation for an idiomatic asm loop like that is do{}while(t1 != t0); with the condition at the bottom like the asm has. (Showing it as a for or while loop depends on the initializers making the condition true so the loop body runs at least one iteration.) See Why are loops always compiled into "do...while" style (tail jump)?

(3) Try changing the asm to use subi with an immediate 1. Most assemblers will reject that because RISC-V doesn't have subi, except maybe as a pseudo-instruction. It doesn't have a FLAGS / condition-code register, so subtracting is exactly equivalent to adding a negative, and RISC-V always sign-extends immediate operands. The only thing it would gain from a subi opcode is being able to change the value by +4096 .. -4095 instead of addi's range of -4096 .. +4095 with its 12-bit immediate. That's obviously not worth having another opcode for.

MIPS is very similar to RISC-V in this way (no FLAGS and not having a hardware subi); see What is the "relationship" between addi and subi? about that and the ISA vs. software assembler pseudo-instruction design choices.

Sign up to request clarification or add additional context in comments.

3 Comments

I appreciate this so much; you've cleared up a lot. Your point regarding return vs exit for the ecalls makes sense, but on the lecture slides he said "ecall is just a return <code> call in C". I don't know why he would suggest that. I think the given problem vs solution says a lot.
@test1: C does guarantee that returning a value from main is equivalent to exit() with that value, and C doesn't have "destructors" or anything that could make more code run when returning vs. calling another function or system call. So it's different code that has the same eventual effect.
Except the libc exit function does call any functions registered with atexit and flushes stdio buffers before exiting. But a raw exit system call is like C _exit which doesn't do that. See Using printf in assembly leads to empty output when piping, but works on the terminal / x86 Assembly - printf doesn't print without "\n". Also re exit() vs. _exit vs. raw asm ecall exit on Linux, see Syscall implementation of exit()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.