Assembly 调试迭代斐波那契(手动从C转换为RISC-V)
我正在学习汇编,所以我尝试将此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;
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中的无条件分支通常被称为跳跃。谢谢,伙计,这太有用了,非常感谢!谢谢,伙计,太有用了,谢谢!