相当努力。
回答您的问题:您可以简单地在function 条目处建立一个堆栈框架,并在最后从中恢复。
我确实不得不稍微改变 $s0 的用途来存储一个中间值并将其添加到堆栈帧中的存储值中(即堆栈帧现在是 3 个字而不是 2 个字)。
无论如何,这是更正后的代码。我尝试对其进行注释(IMO,在 asm 中,没有“太多 cmets”之类的东西)[请原谅无偿的风格清理]:
.data
prompt1: .asciiz "Enter the value for the recursive function f(n) = 2f(n-1)+3f(n-2)+1: "
prompt2: .asciiz "Result: "
numberEntered: .word 0
answer: .word 0
.text
main:
# Ask for the value
li $v0,4
la $a0,prompt1
syscall
# enter value
li $v0,5
syscall
sw $v0,numberEntered # stores the number
# call the function
lw $a0,numberEntered
jal function
sw $v0,answer
# Print out the result
li $v0,4
la $a0,prompt2
syscall
lw $a0,answer
li $v0,1
syscall
li $v0,10
syscall
.globl function
# function -- calculation
# v0 -- return value
# a0 -- current n value
# s0 -- intermediate result
function:
subi $sp,$sp,12 # establish our stack frame
sw $ra,8($sp) # save return address
sw $a0,4($sp) # save n
sw $s0,0($sp) # save intermediate result
# base case
# NOTE: with the addition of n-2, the "beq" was insufficient
li $v0,1
ble $a0,$zero,done
# calling f(n-1)
sub $a0,$a0,1 # get n-1
jal function
# NOTE: these could be combined into a single instruction
mul $v0,$v0,2 # get 2f(n-1)
move $s0,$v0 # save it
# calling f(n-2)
sub $a0,$a0,1 # get n-2
jal function
mul $v0,$v0,3 # get 3f(n-2)
# get 2f(n-1) + 3f(n-2) + 1
add $v0,$s0,$v0
add $v0,$v0,1
done:
lw $ra,8($sp) # restore return address
lw $a0,4($sp) # restore n
lw $s0,0($sp) # restore intermediate result
addi $sp,$sp,12 # pop stack frame
jr $ra # returns
更新:
这个解决方案比我想象的要简单。
这可能是因为堆栈帧在 asm [或 C] 中完成的方式。
考虑一个现代 C 程序:
int
whatever(int n)
{
int x;
// point A
x = other(1);
// do lots more stuff ...
{
// point B
int y = other(2);
// do lots more stuff ...
// point C
n *= (x + y);
}
// do lots more stuff ...
n += ...;
return n;
}
C 编译器将在进入whatever 时建立一个堆栈帧,为x 保留空间 和 y,即使y 只是设置很晚。大多数 C 程序员都没有意识到这一点。
在解释型语言(例如java、python 等)中,y 的空间未被保留直到point B 处的代码被执行。而且,当y“超出范围”[在point C之后]时,他们[通常]会释放它。
大多数 C 程序员认为具有范围声明 [如 y] 可以节省堆栈空间。 (即)在作用域块中,编译器将增加堆栈帧大小以容纳 y,并在不再需要 y 时再次缩小它。
这完全是不正确的。 C 编译器将为 所有 函数作用域变量设置堆栈框架并保留空间,即使它们是在函数后期或在内部作用域内声明的 [如 y]。
这正是我们在您的function 中所做的。即使直到函数中间我们才需要/使用 $s0 的堆栈空间 [在偏移 0 处],这也是正确的。
所以,我猜测您使用其他语言的经验确实 [有效地] 动态保留空间或关于 C 的普遍智慧可能使您找到了一个更复杂的模型来表达您的想法必需的。因此,您最初的问题是:我是否必须“收起”堆栈两次?
我还应该提到function 的调用约定不符合 ABI。如果它是自包含的(即不调用其他任何东西),这完全很好。也就是说,在 asm 中,对于“叶”函数,我们可以定义任何我们想要的。
原因是$a0 调用被callee 修改/删除。我们从堆栈中保存/恢复它,但调用另一个函数可能会搞砸。补救方法就是使用另一个寄存器来保存值[或保存/恢复到堆栈帧中的额外位置],因此如果function 最终调用其他内容,则需要做更多工作。