使用MIPS汇编语言了解递归

时间:2012-01-26 00:24:19

标签: assembly recursion mips spim

我在课堂上,我们已经/正在使用汇编语言进行递归。 我觉得我理解递归,但是越多人试图向我解释它,我就越觉得它远离它。

无论如何,我们的最后一张幻灯片有一个功能(在C?中),老师说他会在课堂上报道,但要求我们的学生在课堂上展示其余的课程。我觉得他一直在看着我,我害怕看起来很蠢。

你能帮助我用MIPS写这段代码并帮助我理解吗? IDK,如果这太难了

用MIPS汇编语言编写,找到fix(i,x),其中fix(i,x)是 递归地定义为:

int fix(int i, int x) // assume i > 0, x > 0
{
    if (x>0)
        return fix(i,x-1);
    else if (i>0)
        return fix(i-1, i-1)+1;
    else
        return 0;
}

谢谢你们,明天我的班级,我仍然希望他永远不会拜访我;但我想真正了解这一材料。

注意:这将是一个附加0学分的课堂练习。我觉得班上的每个人都知道怎么做。

3 个答案:

答案 0 :(得分:2)

汇编语言与递归无关,由于C语言和调用约定和实现,它恰好起作用。只需在汇编程序中实现C并且不关心递归。我想我在一个宠物项目http://github.com/dwelch67/lsasim上谈到了这个问题,除非我把它改成了,最后一课是手动将C中的递归转换为汇编程序。它不是mips所以不用担心这是一个家庭作业问题。

无论如何,启动的关键是简单地在程序集中实现C.

例如,您有一个带输入参数的函数。

int fix(int i, int x)

您需要声明自己的调用约定或使用现有约定来实现C这意味着您需要输入参数的某个位置,或者将它们推入堆栈或将它们放入寄存器中。假设没有优化,您可以在整个函数中保留这些变量并在最后进行清理。因此,如果您需要将ANY函数调用到代码中的任何位置(递归,调用相同的函数,是ANY的一个非常小的子集,但属于该类别并且不是特殊的),您需要保留这些变量。如果调用约定将这些放入堆栈中,那么您已经完成了,如果调用约定将这些放入寄存器中,那么您需要在调用之前保留它们并在之后恢复

push i
push x
implement call to function
pop x
pop i

并继续实施该功能。

就是这样,其余的将照顾好自己。

如果您碰巧注意到您创建的函数作为示例,则在调用此函数中的函数之后没有需要保留输入变量的路径。输入变量被修改并用作下一次调用的输入。因此,对C代码实现的优化是不必担心保留这些变量。只需修改它们并传递它们。在调用约定中使用寄存器是为这个特定函数执行此操作的最简单方法。这是编译器在优化时无论如何都会做的事情(如果使用基于寄存器的调用约定则不保留)。

如果这是所谓的尾部优化,你也可以进行尾部优化。通常在调用函数时,您可以使用通常执行的任何操作来执行“调用”,这与简单的跳转或分支不同,因为在某处保留了返回值。并且有一些返回函数可以撤消此函数并在调用后返回指令。嵌套调用意味着嵌套返回值,跟踪所有这些值。在这种情况下,虽然你执行函数执行路径的最后一件事是调用另一个函数,但你可以改为(取决于指令集)分支到函数而不必嵌套另一组返回值。以臂指令集为例:

有些代码调用第一个函数:

bl firstfun:

在臂bl中表示分支链接。寄存器14将填入返回值,程序计数器将填入函数的地址,firstfun。

通常如果你需要从一个函数调用一个函数,你需要保存r14,这样你可以从该函数返回,而不进行尾部优化:

firstfun:
 ...
 push {r14}
 bl secondfun
 pop {r14}
 bx r14
 ...
secondfun:
  bx r14

bx lr只意味着分支到r14中的内容,在这种情况下是返回。优化看起来像这样,重要的是要注意在第一个函数中调用第二个函数是从第一个函数返回之前做的最后一件事。这是优化的关键。

firstfun:
 ...
 b secondfun
 ...
secondfun:
  bx r14

b只是意味着分支,而不是分支链接只是修改pc并且不修改r14或任何其他寄存器或堆栈。两个实现的执行在功能上是相同的,外部函数调用firstfun并且在正确的执行路径中存在返回(bx r14)。

其他人已经指出,由于您将原始调用者返回零,因此此代码可能会完全优化为零。

fix:
  return 0

答案 1 :(得分:1)

虽然我反对只给出家庭作业问题的答案,但这里是找到fix(i, x)的等效函数,完成了示例调用(这个版本比C版本更有效):

fix:
        bgtz RetI
        xor $v0, $v0, $v0
        jr $ra
RetI:
        move $v0, $a0
        jr $ra

# example of calling fix
main:
        la $a0, 42
        la $a1, 21
        jal fix

在尝试编写代码之前,让我们先了解一下这些函数的作用:)

答案 2 :(得分:1)

将它分成3个部分,如上面的C.通过寄存器传递值i和x,并在运行if检查并修改寄存器后自行调用。上面不应该超过30行的汇编程序。如果我是一个更好的MIPS编码器,我会为你做。它看起来像(是伪装配的)


fix:
  compare r0, 0
  branch greater than x_positive
  subtract r1,r1,1
  call fix
  return;
//  or just jump fix instead

x_positive:
  compare r1, 0
  branch greater than i_positive
  subtract r0, r0, 1
  subtract r1, r1, 1
  call fix
  return
// or just  jmp fix

i_positive:
  move return register, 0
  return

有趣的是,这将始终返回0,如C中所示:)


  fix:
    move return_register, 0
    return