assembly 如何将递归C++代码转换为RISC-V汇编代码,以及如何在递归C++代码的RISC-V汇编代码中打印字符串和数字?

wqlqzqxt  于 2023-05-29  发布在  其他
关注(0)|答案(1)|浏览(152)
#include <iostream>

using namespace std;
//Reference: https://www.geeksforgeeks.org/print-sums-subsets-given-set/

//==============testcase1==============
int arr[] = { 5, 4, 3 };
int n = 3;
//output: 12 9 8 5 7 4 3 0
//=====================================

//==============testcase2==============
//int arr[] = {2, 3};
//int n = 2;
//output: 5 2 3 0
//=====================================

// Prints sums of all subsets of arr[l..r]
void subsetSums(int arr[], int l, int r, int sum)
{
    // Print current subset
    if (l > r) {
        cout << sum << " ";
        return;
    }

    // Subset including arr[l]
    subsetSums(arr, l + 1, r, sum + arr[l]);

    // Subset excluding arr[l]
    subsetSums(arr, l + 1, r, sum);
}

// Driver code
int main()
{
    cout << "output: ";
    subsetSums(arr, 0, n - 1,0);
    return 0;
}

我想转换成R64I汇编代码。这就是我所做的。

# Reference: https://www.geeksforgeeks.org/print-sums-subsets-given-set/
.data
.align 4
# =========testcase1===========
arr: .word 5, 4, 3
n: .word 3
str: .string "output: "
space: .string " "
# output: 12 9 8 5 7 4 3 0
# ==============================

# =========testcase2===========
#arr: .word 2, 3
#n: .word 2
#str: .string "output: "
#space: .string " "
# output: 5 2 3 0
# ==============================

.text
.global _start
# Start your coding below, don't change anything upper except testing different testcase

_start:
   #la t0, aa
   #auipc t0, 0x1
   #nop
   #nop
   #addi t0, t0, 148
   #nop
   #nop
   li a7, 4
   la a0, str
   ecall
   #load arr, 0, n, 0
   la a0, arr
   la a1, 0
   la a2, n
   la a3, 0
   jal subsetSums
   j end       # Jump to end of program
subsetSums:
   mv t0, a0
   mv t1, a1
   mv t2, a3
   # if l > r print sum
   blt a2, a1, sum
   add t3, a1, 0
   slli t3, t3, 2
   addi t3, a0, t3
   lw t4, 0(t3)
   add a3, t2, t3
   addi a1, t1, 1
   jal subsetSums
   addi a1, t1, 1
   add a3, t2, 0
   jal subsetSums

sum:
   mv t0, a3
   # Print sum
   li a7, 1
   addi a0, t0, 0
   ecall
   #Print 
   li a7, 4
   la a0, space
   ecall

   lw   ra, 0(sp) # Reload return address from stack
   addi sp, sp, 4 # Restore stack pointer
   jr x1

end:nop

我想做两个测试。第一个来自arr:.word 5,4,3并得到输出:12 9 8 5 7 4 3 0.但是,我不知道如何打印字符串和数字。我已经学习Hello World两年了。我以前从来没有学过汇编代码,也不知道我在做什么。编辑2:谢谢你的回复。我已经修复了如何输出字符串和数字,但我得到了一个错误,在这段代码

subsetSums:
   mv t0, a0
   mv t1, a1
   mv t2, a3
   # if l > r print sum
   blt a2, a1, sum
   #BUG
   add t3, a1, 0
   slli t3, t3, 2
   addi t3, a0, t3
   lw t4, 0(t3)
   add a3, t2, t3
   addi a1, t1, 1
   jal subsetSums
   addi a1, t1, 1
   add a3, t2, 0
   jal subsetSums
   #BUG

似乎我不能把正确的参数放在我的函数中。至于为什么我在end:中使用nop,我必须这样做才能跟随我的教授。

pkwftd7m

pkwftd7m1#

把递归看作是某种唯一的问题,这是一个red herring
虽然它需要知识,但简单而正确的方法是遵循调用约定。只要环境有一个运行时调用堆栈(所有现代CPU环境都有),那么递归就可以简单地看作函数调用的子集--一个没有特殊要求的子集!
调用约定说明了一个函数应该如何调用另一个函数,并且这两个函数可以在单独的编译单元中(这意味着它们不能看到彼此的内部代码序列和它们的寄存器选择),并且只要函数签名不改变(变量和类型的参数列表,以及返回值和类型),被调用者可以在不需要改变其调用者的情况下改变/版本。
重申一下,递归没有向标准调用约定添加额外的规范或要求。
(It然而,一个简单的直接递归函数与它自己在同一个编译单元中,如果我们知道某些函数的内部寄存器使用情况,我们可以应用违反标准调用约定的自定义调用优化(假设该函数也不参与标准调用,例如来自另一个编译单元的调用)。但是,如果你的目的/目标是学习正确的函数调用方式,例如:按照标准约定,这样的优化是比您的目标更高级的主题。)
更详细地说,调用约定说明了如何:

  • 传递和接收参数和返回值
  • 为了函数的调用者的利益,必须保留哪些寄存器
  • 以及哪些登记册不需要保存
  • 而且是对称的,为了你的功能
  • 函数调用保留哪些寄存器
  • 什么寄存器会被函数调用破坏
  • 如何使用堆栈空间进行寄存器保存、值保存或本地存储

从这些规范和对函数中使用的变量的分析中,我们可以确定哪些值需要保留以使函数自身受益,可能包括返回地址,参数变量和局部变量(包括临时变量)。
这样,你就可以有一个递归的fib,如下所示:

int fib(int n) {
    if (n <= 1) return 1;
    return fib(n+1) + fib(n+2);
}

并且,按照调用约定,其汇编/机器代码将与以下非递归函数相同(用递归fib替换非递归fib2)。

int fib(int n) {                   // non-recursive fib function
    if (n <= 1) return 1;
    return fib2(n+1) + fib2(n+2);  // uses fib2 instead of itself
}

因此,建议是简单地遵循调用约定,不要担心递归。

相关问题