Assembly 调试迭代斐波那契(手动从C转换为RISC-V)

Assembly 调试迭代斐波那契(手动从C转换为RISC-V),assembly,fibonacci,riscv,Assembly,Fibonacci,Riscv,我正在学习汇编,所以我尝试将此c代码转换为RISC-v汇编代码: int Fib_Iter(int x){ int Fib_f, Fib_s, temp, Result; Fib_f = 0; Fib_s = 1; if (x == 0) Result = Fib_f; else if (x == 1) Result = Fib_s; else { while (x >= 2) { temp = Fib_f + Fib_s;

我正在学习汇编,所以我尝试将此c代码转换为RISC-v汇编代码:

int Fib_Iter(int x){

int Fib_f, Fib_s, temp, Result;
Fib_f = 0;
Fib_s = 1;
if (x == 0)
    Result = Fib_f;
else if (x == 1)
    Result = Fib_s;
else
{
    while (x >= 2)
    {
        temp = Fib_f + Fib_s;
        Fib_f = Fib_s;
        Fib_s = temp;
        x--;
    }
    Result = Fib_s;
}
return Result;
}
   fib_iter:
    #temp in x5,
    #f_f in x6
    #f_s in x7
    #x in x12
    #res in x11
    #x28 =1
    #x29=2
    addi sp,sp,-16
    sw x5,0(sp)
    sw x6,4(sp)
    sw x7,8(sp)
    sw x11,12(sp)              #saving x5,x6,x7,x11 in the stack
    addi x28,x0,1             #adding 1 to x28 to use it in the 2ndif statment
    addi x29,x0,2              #adding 2 to x28 to use it in the 3rd if statment
    bne x12,x0,lb1             #first if statment to check if x==0
    add x11,x6,x0               #if x !=0 make result(x11) equal to (fb_f)x6
    lb1:
    bne x12,x28,lb2           #2nd if statement to check if x==1
    add x11,x7,x0            #if x !=1 make result(x11) equal to (fb_s)x6
    lb2:                      #if it equals to 1 start the loop
    loop:
    add x5,x6,x7             #just an add's 
    addi x6,x7,0
    addi x7,x5,0
    addi x12,x12,-1
    bge x12,x29,loop       #check if x >2 if yes go to the loop else 
    addi x11,x7,0          #result(x11)=x7
    lw x5,0(sp)            #load all the registers from the stack
    lw x6,4(sp)
    lw x7,8(sp)
    lw x11,12(sp)
    addi sp,sp,16         #return the stack pointer to its original place
    jalr x0,0(x1)        
这是我的RISC-V汇编代码:

int Fib_Iter(int x){

int Fib_f, Fib_s, temp, Result;
Fib_f = 0;
Fib_s = 1;
if (x == 0)
    Result = Fib_f;
else if (x == 1)
    Result = Fib_s;
else
{
    while (x >= 2)
    {
        temp = Fib_f + Fib_s;
        Fib_f = Fib_s;
        Fib_s = temp;
        x--;
    }
    Result = Fib_s;
}
return Result;
}
   fib_iter:
    #temp in x5,
    #f_f in x6
    #f_s in x7
    #x in x12
    #res in x11
    #x28 =1
    #x29=2
    addi sp,sp,-16
    sw x5,0(sp)
    sw x6,4(sp)
    sw x7,8(sp)
    sw x11,12(sp)              #saving x5,x6,x7,x11 in the stack
    addi x28,x0,1             #adding 1 to x28 to use it in the 2ndif statment
    addi x29,x0,2              #adding 2 to x28 to use it in the 3rd if statment
    bne x12,x0,lb1             #first if statment to check if x==0
    add x11,x6,x0               #if x !=0 make result(x11) equal to (fb_f)x6
    lb1:
    bne x12,x28,lb2           #2nd if statement to check if x==1
    add x11,x7,x0            #if x !=1 make result(x11) equal to (fb_s)x6
    lb2:                      #if it equals to 1 start the loop
    loop:
    add x5,x6,x7             #just an add's 
    addi x6,x7,0
    addi x7,x5,0
    addi x12,x12,-1
    bge x12,x29,loop       #check if x >2 if yes go to the loop else 
    addi x11,x7,0          #result(x11)=x7
    lw x5,0(sp)            #load all the registers from the stack
    lw x6,4(sp)
    lw x7,8(sp)
    lw x11,12(sp)
    addi sp,sp,16         #return the stack pointer to its original place
    jalr x0,0(x1)        
但是当使用维纳斯模拟器时,我在寄存器11中没有得到正确的值


当我使用值4来调用它时,得到了4,但正确的答案是3。

您有C中的伪代码,这很好

评论:

在到汇编的转换中缺少一些语句。 if-then-else语句不正确:只应执行then/else中的一个,并且您必须通过从then部分避开else部分来告诉处理器这一点。 寄存器的使用与标准调用约定不同。我们希望第一个整数参数在a0中,因此我们希望您的x在a0中。返回值也应该在a0中。不需要保存t0、t1、t2、a1-它们像t3、t4一样被调用,您不保存也不必保存它们。 如果您想要有自己的调用约定,这很好,但是您在a1中返回一个值,并且从堆栈中恢复a1,而这些在一起是没有意义的

见内联评论:

    int Fib_Iter(int x) {   <------------- x is passed in a0
        int Fib_f, Fib_s, temp, Result;
        Fib_f = 0;          <------------- where is this line in the assembly??
        Fib_s = 1;          <------------- where is this line in the assembly??
        if (x == 0)
            Result = Fib_f;
        else if (x == 1)
            Result = Fib_s;
        else
        {
            while (x >= 2)
            {
                temp = Fib_f + Fib_s;
                Fib_f = Fib_s;
                Fib_s = temp;
                x--;
            }
            Result = Fib_s;
        }
        return Result;   <--------- return value goes in a0
    }
   fib_iter:
    #temp in x5,
    #f_f in x6
    #f_s in x7
    #x in x12       <---- x should be found in a0
    #res in x11     <---- return value should be put in a0 (at the end of the function)
    #x28 =1
    #x29=2
    addi sp,sp,-16  <---- no stack space needed
    sw x5,0(sp)     <---- no need to save t0
    sw x6,4(sp)     <---- no need to save t1
    sw x7,8(sp)     <---- no need to save t2
    sw x11,12(sp)   <---- no need to save a1
    addi x28,x0,1
    addi x29,x0,2
    bne x12,x0,lb1
    add x11,x6,x0   <---- then part, good
                    <---- missing code to skip else part
                       after executing a then part the code
                       (should skip the else part)
                       should resume the logically next thing after the if
    lb1:
    bne x12,x28,lb2
    add x11,x7,x0
                    <---- missing code to skip else part
    lb2:
    loop:
    add x5,x6,x7
    addi x6,x7,0
    addi x7,x5,0
    addi x12,x12,-1
    bge x12,x29,loop
    addi x11,x7,0
    lw x5,0(sp)      <---- no need to reload t0
    lw x6,4(sp)      <---- no need to reload t1
    lw x7,8(sp)      <---- no need to reload t2
    lw x11,12(sp)    <---- no need to reload a1 (this also clobbers current a1 return value)
    addi sp,sp,16    <---- no stack space is needed
    jalr x0,0(x1)
见内联评论:

    int Fib_Iter(int x) {   <------------- x is passed in a0
        int Fib_f, Fib_s, temp, Result;
        Fib_f = 0;          <------------- where is this line in the assembly??
        Fib_s = 1;          <------------- where is this line in the assembly??
        if (x == 0)
            Result = Fib_f;
        else if (x == 1)
            Result = Fib_s;
        else
        {
            while (x >= 2)
            {
                temp = Fib_f + Fib_s;
                Fib_f = Fib_s;
                Fib_s = temp;
                x--;
            }
            Result = Fib_s;
        }
        return Result;   <--------- return value goes in a0
    }
   fib_iter:
    #temp in x5,
    #f_f in x6
    #f_s in x7
    #x in x12       <---- x should be found in a0
    #res in x11     <---- return value should be put in a0 (at the end of the function)
    #x28 =1
    #x29=2
    addi sp,sp,-16  <---- no stack space needed
    sw x5,0(sp)     <---- no need to save t0
    sw x6,4(sp)     <---- no need to save t1
    sw x7,8(sp)     <---- no need to save t2
    sw x11,12(sp)   <---- no need to save a1
    addi x28,x0,1
    addi x29,x0,2
    bne x12,x0,lb1
    add x11,x6,x0   <---- then part, good
                    <---- missing code to skip else part
                       after executing a then part the code
                       (should skip the else part)
                       should resume the logically next thing after the if
    lb1:
    bne x12,x28,lb2
    add x11,x7,x0
                    <---- missing code to skip else part
    lb2:
    loop:
    add x5,x6,x7
    addi x6,x7,0
    addi x7,x5,0
    addi x12,x12,-1
    bge x12,x29,loop
    addi x11,x7,0
    lw x5,0(sp)      <---- no need to reload t0
    lw x6,4(sp)      <---- no need to reload t1
    lw x7,8(sp)      <---- no need to reload t2
    lw x11,12(sp)    <---- no need to reload a1 (this also clobbers current a1 return value)
    addi sp,sp,16    <---- no stack space is needed
    jalr x0,0(x1)

您有C语言的伪代码,这很好

评论:

在到汇编的转换中缺少一些语句。 if-then-else语句不正确:只应执行then/else中的一个,并且您必须通过从then部分避开else部分来告诉处理器这一点。 寄存器的使用与标准调用约定不同。我们希望第一个整数参数在a0中,因此我们希望您的x在a0中。返回值也应该在a0中。不需要保存t0、t1、t2、a1-它们像t3、t4一样被调用,您不保存也不必保存它们。 如果您想要有自己的调用约定,这很好,但是您在a1中返回一个值,并且从堆栈中恢复a1,而这些在一起是没有意义的

见内联评论:

    int Fib_Iter(int x) {   <------------- x is passed in a0
        int Fib_f, Fib_s, temp, Result;
        Fib_f = 0;          <------------- where is this line in the assembly??
        Fib_s = 1;          <------------- where is this line in the assembly??
        if (x == 0)
            Result = Fib_f;
        else if (x == 1)
            Result = Fib_s;
        else
        {
            while (x >= 2)
            {
                temp = Fib_f + Fib_s;
                Fib_f = Fib_s;
                Fib_s = temp;
                x--;
            }
            Result = Fib_s;
        }
        return Result;   <--------- return value goes in a0
    }
   fib_iter:
    #temp in x5,
    #f_f in x6
    #f_s in x7
    #x in x12       <---- x should be found in a0
    #res in x11     <---- return value should be put in a0 (at the end of the function)
    #x28 =1
    #x29=2
    addi sp,sp,-16  <---- no stack space needed
    sw x5,0(sp)     <---- no need to save t0
    sw x6,4(sp)     <---- no need to save t1
    sw x7,8(sp)     <---- no need to save t2
    sw x11,12(sp)   <---- no need to save a1
    addi x28,x0,1
    addi x29,x0,2
    bne x12,x0,lb1
    add x11,x6,x0   <---- then part, good
                    <---- missing code to skip else part
                       after executing a then part the code
                       (should skip the else part)
                       should resume the logically next thing after the if
    lb1:
    bne x12,x28,lb2
    add x11,x7,x0
                    <---- missing code to skip else part
    lb2:
    loop:
    add x5,x6,x7
    addi x6,x7,0
    addi x7,x5,0
    addi x12,x12,-1
    bge x12,x29,loop
    addi x11,x7,0
    lw x5,0(sp)      <---- no need to reload t0
    lw x6,4(sp)      <---- no need to reload t1
    lw x7,8(sp)      <---- no need to reload t2
    lw x11,12(sp)    <---- no need to reload a1 (this also clobbers current a1 return value)
    addi sp,sp,16    <---- no stack space is needed
    jalr x0,0(x1)
见内联评论:

    int Fib_Iter(int x) {   <------------- x is passed in a0
        int Fib_f, Fib_s, temp, Result;
        Fib_f = 0;          <------------- where is this line in the assembly??
        Fib_s = 1;          <------------- where is this line in the assembly??
        if (x == 0)
            Result = Fib_f;
        else if (x == 1)
            Result = Fib_s;
        else
        {
            while (x >= 2)
            {
                temp = Fib_f + Fib_s;
                Fib_f = Fib_s;
                Fib_s = temp;
                x--;
            }
            Result = Fib_s;
        }
        return Result;   <--------- return value goes in a0
    }
   fib_iter:
    #temp in x5,
    #f_f in x6
    #f_s in x7
    #x in x12       <---- x should be found in a0
    #res in x11     <---- return value should be put in a0 (at the end of the function)
    #x28 =1
    #x29=2
    addi sp,sp,-16  <---- no stack space needed
    sw x5,0(sp)     <---- no need to save t0
    sw x6,4(sp)     <---- no need to save t1
    sw x7,8(sp)     <---- no need to save t2
    sw x11,12(sp)   <---- no need to save a1
    addi x28,x0,1
    addi x29,x0,2
    bne x12,x0,lb1
    add x11,x6,x0   <---- then part, good
                    <---- missing code to skip else part
                       after executing a then part the code
                       (should skip the else part)
                       should resume the logically next thing after the if
    lb1:
    bne x12,x28,lb2
    add x11,x7,x0
                    <---- missing code to skip else part
    lb2:
    loop:
    add x5,x6,x7
    addi x6,x7,0
    addi x7,x5,0
    addi x12,x12,-1
    bge x12,x29,loop
    addi x11,x7,0
    lw x5,0(sp)      <---- no need to reload t0
    lw x6,4(sp)      <---- no need to reload t1
    lw x7,8(sp)      <---- no need to reload t2
    lw x11,12(sp)    <---- no need to reload a1 (this also clobbers current a1 return value)
    addi sp,sp,16    <---- no stack space is needed
    jalr x0,0(x1)

为了记录在案,你的C可以简化。您不需要单独检查x==0和x==1;如果x<2返回x,则可以执行此操作;返回0或1

我假设您不打算处理负输入,所以unsigned可能是一个不错的选择。C返回1表示负x,到达循环但运行0次迭代,Fib_s保持不变。但我认为这并不重要

asm中的最小实现可能比您的版本简单得多。这是一个叶函数,没有对其他函数的调用,因此我们可以对所有内容使用调用阻塞的临时寄存器。我使用for寄存器来帮助跟踪传统上用于arg传递和call clobbered与call preserved的寄存器

事实上,我从clang那里得到了很好的asm,因为这个C:

int Fib_Iter(int x){
    if (x < 2) 
        return x;

    int Fib_f = 0, Fib_s = 1;
    while (x >= 2) {
        int temp = Fib_f + Fib_s;
        Fib_f = Fib_s;
        Fib_s = temp;
        x--;
    }
    return Fib_s;
}
同样的逻辑应该适用于unsigned,避免返回负x的奇怪。clang对无符号类型的编译有些不同,但我认为这不是必需的

tmpcount=x+1可能可以避免对bge使用ble反向操作数而不是blt,因此我们可以直接使用2和x,从而节省另一条指令

斐波那契展开得很好:a+=b;b+=a;每一步执行一条指令,而不是3条。在每一步检查分支条件实际上对于静态代码大小是最好的,对于动态指令计数也是最好的。相关:一个存储斐波那契值数组的函数,包括一个展开版本,每个循环只检查一次分支条件,在进入循环之前使用智能启动处理奇数和偶数

当然,如果您正在优化非微小n,那么可以在log2n移位和乘法/加法步骤中计算Fibonaccin,而不是n加法

这是一个仅重复循环条件的展开。不过,要验证循环退出逻辑的正确性是非常重要的,因此它比较短,但并不简单

# arg:   unsigned n  in  a0
# returns:    Fib(n) in  a0

fib_unrolled:
        addi    t2, zero, 2
        bltu    a0, t2, .Lsmall_n         # if(n<2) return n
                # otherwise fall through
        mv      t0, zero               # a=0
        addi    t1, zero, 1            # b=1
             # known: x>=2  (unsigned) before first iteration
.Lloop:                                # do{
        beq     a0, t2, .first_out          # if(n==2) return a+b;
        addi    a0, a0, -2                  # n-=2
        add     t0, t0, t1                  # a += b
        add     t1, t1, t0                  # b += a
        bgeu    a0, t2, .Lloop         # }while(n >= 2);

        mv      a0, t1
.Lsmall_n:
        ret

.Lfirst_out:
        add      a0, t0, t1      # add instead of mv so the beq can be earlier
        ret      
一个更聪明的展开:循环中的一个分支。基于计数为奇数或偶数初始化两个变量 如果我们想总是做偶数的加法,我们可以安排从0,1或1,0开始,这取决于n&1。从1,0开始意味着我们首先做1+=0,基本上跳过一次迭代。我最初是为此想出这个把戏的

clang在这里做的不是最佳工作,在我们有条件跳入的循环中浪费了一些复制指令。在C语言中,编译器通常很难做到这一点,但对于手工编写的asm,这有时是一个有用的技巧。因此,这里有一个手写版本没有那么糟糕:

fib_unroll2_branchy_v2:
        addi    t2, a0, -3            # n -= 3  (leaving a copy of the orig)
        bleu    t2, a0, .Lsmall_n     # if( (n-3) > n) detect wrapping, i.e. n<=2

        andi    t0, t2, 1             # i = n&1
        addi    a0, zero, 1           # b = retval, replacing orig_n
        addi    a1, zero, 1           # a
        beqz    t0, .Lodd_entry       # even i means orig_n was odd

.Lloop:                              # do{
        add     a1, a1, a0            # a += b
.Lodd_entry:
        addi    t0, t0, 2             # i += 2
        add     a0, a0, a1            # b += a
        bleu    t0, t2, .Lloop       # }while(i <= n);
        ret

.Lsmall_n
        snez    a0, a0                # return orig_n != 0 handles n<3
        ret
可能有一些优化我错过了。在fib_unroll2_更简单的无分支的情况下,最好找到一些ILP,而不是除了最终的n-=2之外的基本上一个长的依赖链,或者通过更少的迭代而不是转动lo的前半部分来实现循环终止 这个版本只需要最终结果,不需要像我的x86答案那样将所有Fib值存储到数组中


即使是branchy_v2版本也感觉像是一个比我们真正想要初始化i更长的dep链,但在一个非超宽的管道上也可以。请注意,您的C可以简化。您不需要单独检查x==0和x==1;如果x<2返回x,则可以执行此操作;返回0或1

我假设您不打算处理负输入,所以unsigned可能是一个不错的选择。C返回1表示负x,到达循环但运行0次迭代,Fib_s保持不变。但我认为这并不重要

asm中的最小实现可能比您的版本简单得多。这是一个叶函数,没有对其他函数的调用,因此我们可以对所有内容使用调用阻塞的临时寄存器。我使用for寄存器来帮助跟踪传统上用于arg传递和call clobbered与call preserved的寄存器

事实上,我从clang那里得到了很好的asm,因为这个C:

int Fib_Iter(int x){
    if (x < 2) 
        return x;

    int Fib_f = 0, Fib_s = 1;
    while (x >= 2) {
        int temp = Fib_f + Fib_s;
        Fib_f = Fib_s;
        Fib_s = temp;
        x--;
    }
    return Fib_s;
}
同样的逻辑应该适用于unsigned,避免返回负x的奇怪。clang对无符号类型的编译有些不同,但我认为这不是必需的

tmpcount=x+1可能可以避免对bge使用ble反向操作数而不是blt,因此我们可以直接使用2和x,从而节省另一条指令

斐波那契展开得很好:a+=b;b+=a;每一步执行一条指令,而不是3条。在每一步检查分支条件实际上对于静态代码大小是最好的,对于动态指令计数也是最好的。相关:一个存储斐波那契值数组的函数,包括一个展开版本,每个循环只检查一次分支条件,在进入循环之前使用智能启动处理奇数和偶数

当然,如果您正在优化非微小n,那么可以在log2n移位和乘法/加法步骤中计算Fibonaccin,而不是n加法

这是一个仅重复循环条件的展开。不过,要验证循环退出逻辑的正确性是非常重要的,因此它比较短,但并不简单

# arg:   unsigned n  in  a0
# returns:    Fib(n) in  a0

fib_unrolled:
        addi    t2, zero, 2
        bltu    a0, t2, .Lsmall_n         # if(n<2) return n
                # otherwise fall through
        mv      t0, zero               # a=0
        addi    t1, zero, 1            # b=1
             # known: x>=2  (unsigned) before first iteration
.Lloop:                                # do{
        beq     a0, t2, .first_out          # if(n==2) return a+b;
        addi    a0, a0, -2                  # n-=2
        add     t0, t0, t1                  # a += b
        add     t1, t1, t0                  # b += a
        bgeu    a0, t2, .Lloop         # }while(n >= 2);

        mv      a0, t1
.Lsmall_n:
        ret

.Lfirst_out:
        add      a0, t0, t1      # add instead of mv so the beq can be earlier
        ret      
一个更聪明的展开:循环中的一个分支。基于计数为奇数或偶数初始化两个变量 如果我们想总是做偶数的加法,我们可以安排从0,1或1,0开始,这取决于n&1。从1,0开始意味着我们首先做1+=0,基本上跳过一次迭代。我最初是为此想出这个把戏的

clang在这里做的不是最佳工作,在我们有条件跳入的循环中浪费了一些复制指令。在C语言中,编译器通常很难做到这一点,但对于手工编写的asm,这有时是一个有用的技巧。因此,这里有一个手写版本没有那么糟糕:

fib_unroll2_branchy_v2:
        addi    t2, a0, -3            # n -= 3  (leaving a copy of the orig)
        bleu    t2, a0, .Lsmall_n     # if( (n-3) > n) detect wrapping, i.e. n<=2

        andi    t0, t2, 1             # i = n&1
        addi    a0, zero, 1           # b = retval, replacing orig_n
        addi    a1, zero, 1           # a
        beqz    t0, .Lodd_entry       # even i means orig_n was odd

.Lloop:                              # do{
        add     a1, a1, a0            # a += b
.Lodd_entry:
        addi    t0, t0, 2             # i += 2
        add     a0, a0, a1            # b += a
        bleu    t0, t2, .Lloop       # }while(i <= n);
        ret

.Lsmall_n
        snez    a0, a0                # return orig_n != 0 handles n<3
        ret
可能有一些优化我错过了。在fib_unroll2_更简单的无分支的情况下,最好找到一些ILP,而不是除了最终n-=2之外的基本上一个长的依赖链,或者通过更少的迭代来实现循环终止,而不是将循环的前半部分变成no-op。这个版本只需要最终的结果,不需要像我的x86答案那样将每个Fib值存储到数组中


即使是branchy_v2版本也感觉像是一个比我们真正想要初始化i更长的dep链,但在一个不超宽的管道上也可以。

你得到了什么价值?你希望得到什么价值?请考虑用一些注释注释代码。现在,由于格式不好,很难阅读,由于没有注释,也很难理解。当我使用值4调用它时,我得到了4,但正确的答案是3,好的,我会将其编辑为您得到了什么值?你希望得到什么价值?请考虑用一些注释注释代码。现在,它很难阅读,因为格式不好,也很难理解,因为没有注释。当我使用值4调用它时,我得到了4,但正确的答案是3,好吧,我会将它编辑为一个pthanks,很多兄弟,我有一个问题需要跳过的缺失代码是什么?其他部分我真的不知道如何跳过它。我想程序会自己跳过它,这太愚蠢了。你需要无条件地更改控制流。这是用一个分支完成的,但是你还必须知道分支到哪里,这是整个if语句之后的逻辑上的下一件事,基本上是}这是功能权限的结尾。非常感谢你,非常好的人。哦,RISC V中的无条件分支通常被称为跳跃。谢谢,非常兄弟,我有一个问题需要跳过的缺失代码是什么?其他部分我真的不知道如何跳过它。我想程序会自己跳过它,这太愚蠢了。你需要无条件地更改控制流。这是用一个分支完成的,但你也必须知道
分支到哪里,这是整个if语句后面的逻辑上的下一个东西,基本上是}这是功能权限的结尾非常感谢你,非常好的人,RISC V中的无条件分支通常被称为跳跃。谢谢,伙计,这太有用了,非常感谢!谢谢,伙计,太有用了,谢谢!