Recursion 在ASM中实现递归,无需过程
我试图用一种类似ASM的简化语言实现函数和递归,这种语言没有过程。只有简单的jumpz、jump、push、pop、add和mul类型的命令 以下是命令:Recursion 在ASM中实现递归,无需过程,recursion,assembly,callstack,Recursion,Assembly,Callstack,我试图用一种类似ASM的简化语言实现函数和递归,这种语言没有过程。只有简单的jumpz、jump、push、pop、add和mul类型的命令 以下是命令: (所有变量和文字都是整数) set(设置现有变量的值或声明并初始化新变量),例如(set x 3) push(将值推送到堆栈上。可以是变量或整数),例如(push 3)或(push x) pop(将堆栈弹出到变量中)例如(pop x) 添加(将第二个参数添加到第一个参数),例如(添加x1)或(添加xy) mul(与加法相同,但用于乘法) 跳
(所有变量和文字都是整数)
- set(设置现有变量的值或声明并初始化新变量),例如(set x 3)
- push(将值推送到堆栈上。可以是变量或整数),例如(push 3)或(push x)
- pop(将堆栈弹出到变量中)例如(pop x)
- 添加(将第二个参数添加到第一个参数),例如(添加x1)或(添加xy)
- mul(与加法相同,但用于乘法)
- 跳转(跳转到特定的代码行),例如,(跳转3)将跳转到第3行或(跳转x)将跳转到#等于x值的行
- jumpz(如果第二个参数等于零,则跳到行号),例如(jumpz 3 x)或(jumpz x)
这似乎不适合递归。参数和中间值需要放在堆栈上,我认为相同地址的堆栈上的多个实例将由递归调用产生,这很好。下一个代码在“John Smith Assembly”中递归地将数字“base”提升为幂“exponent”: 为了测试它,让我们手动运行它:
LINE | BASE EXPONENT RESULT RETURNLINE STACK
------|---------------------------------------
1 | 2
2 | 4
3 | 1
4 | 4
5 | 8
6 | 8
7 |
36 |
37 | 2
38 | 3
39 | 39
40 | 43
41 | 43(1)
42 |
36 |
37 | 4
38 | 2
39 | 39
40 | 43
41 | 43(2)
42 |
36 |
37 | 8
38 | 1
39 | 39
40 | 43
41 | 43(3)
42 |
36 |
37 | 16
38 | 0
39 | 39
40 | 43
41 | 43(4)
42 |
36 |
43 | 43(4)
44 |
43 | 43(3)
44 |
43 | 43(2)
44 |
43 | 43(1)
44 |
43 | 8
44 |
8 |
1 - set base 2 ;RAISE 2 TO ...
2 - set exponent 4 ;... EXPONENT 4 (2^4=16).
3 - set result 1 ;MUST BE 1 IN ORDER TO MULTIPLY.
4 - set returnLine IP ;IP = 4.
5 - add returnLine 7 ;RETURNLINE = 4+7.
6 - push returnLine ;PUSH 11.
7 - push base ;FIRST PARAMETER.
8 - push result ;SECOND PARAMETER.
9 - push exponent ;THIRD PARAMETER.
10 - jump 36 ;FUNCTION CALL.
...
;POWER FUNCTION.
36 - pop exponent ;THIRD PARAMETER.
37 - pop result ;SECOND PARAMETER.
38 - pop base ;FIRST PARAMETER.
39 - jumpz 49 exponent ;FINISH IF EXPONENT IS ZERO.
40 - mul result base ;RESULT = ( RESULT * BASE ).
41 - add exponent -1 ;RECURSIVE CONTROL VARIABLE.
42 - set returnLine IP ;IP = 42.
43 - add returnLine 7 ;RETURN LINE = 42+7.
44 - push returnLine ;PUSH 49.
45 - push base
46 - push result
47 - push exponent
48 - jump 36 ;RECURSIVE CALL.
49 - pop returnLine
50 - jump returnLine
;POWER END.
编辑:堆栈上现在的函数的参数(未手动运行):
您的asm确实提供了足够的工具来实现通常的过程调用/返回序列。您可以推送一个回信地址并作为呼叫跳转,也可以弹出一个回信地址(跳转到临时位置)并作为ret
间接跳转到该地址。我们可以只做call
和ret
宏。(除了在宏中生成正确的返回地址很棘手之外;我们可能需要一个标签(push ret_addr
),或者类似于set tmp,IP
/add tmp,4
/push tmp
/跳转目标函数
)。简言之,这是可能的,我们应该用一些语法糖来包装它,这样我们在研究递归时就不会陷入这种困境
使用正确的语法糖,您可以在汇编中实现Fibonacci(n)
,这将实际为x86和您的玩具机器进行汇编
您正在考虑修改静态(全局)变量的函数。递归需要局部变量,因此对函数的每个嵌套调用都有自己的局部变量副本。您的机器没有寄存器,而是(显然是无限的)命名静态变量(如x
和y
)。如果要像MIPS或x86那样编程,并复制现有的调用约定,只需使用一些命名变量,如eax
,ebx
,…,或r0
r31
寄存器体系结构使用寄存器的方式。
然后,您可以像在普通调用约定中一样实现递归,即调用方或被调用方使用push
/pop
在堆栈上保存/恢复寄存器,以便可以重用。函数返回值进入寄存器。函数参数应该放在寄存器中。另一个丑陋的选择是在返回地址之后推送它们(创建调用方会从堆栈调用约定中清除arg),因为您没有堆栈相对寻址模式来像x86那样(在堆栈上的返回地址之上)访问它们。或者,您可以像大多数RISCcall
指令(对于分支和链接,通常称为bl
或类似的指令)一样,在a中传递返回地址,而不是像x86的call
那样推送它。(因此,非叶调用方在进行另一次调用之前必须将传入的lr
推送到堆栈上)
一个(愚蠢且缓慢的)天真实现的递归可能会执行以下操作:
int Fib(int n) {
if(n<=1) return n; // Fib(0) = 0; Fib(1) = 1
return Fib(n-1) + Fib(n-2);
}
## valid implementation in your toy language *and* x86 (AMD64 System V calling convention)
### Convenience macros for the toy asm implementation
# pretend that the call implementation has some way to make each return_address label unique so you can use it multiple times.
# i.e. just pretend that pushing a return address and jumping is a solved problem, however you want to solve it.
%define call(target) push return_address; jump target; return_address:
%define ret pop rettmp; jump rettmp # dedicate a whole variable just for ret, because we can
# As the first thing in your program, set eax, 0 / set ebx, 0 / ...
global Fib
Fib:
# input: n in edi.
# output: return value in eax
# if (n<=1) return n; // the asm implementation of this part isn't interesting or relevant. We know it's possible with some adds and jumps, so just pseudocode / handwave it:
... set eax, edi and ret if edi <= 1 ... # (not shown because not interesting)
add edi, -1
push edi # save n-1 for use after the recursive call
call Fib # eax = Fib(n-1)
pop edi # restore edi to *our* n-1
push eax # save the Fib(n-1) result across the call
add edi, -1
call Fib # eax = Fib(n-2)
pop edi # use edi as a scratch register to hold Fib(n-1) that we saved earlier
add eax, edi # eax = return value = Fib(n-1) + Fib(n-2)
ret
intfib(intn){
if(nMargaret的pastebin稍微修改,以便在我的解释器中运行此语言:(无限循环问题,可能是由于我的转录错误)
成功转录Peter的斐波那契计算器:
String[] x = new String[] {
//n is our input, which term of the sequence we want to calculate
"set n 5",
//temp variable for use throughout the program
"set temp 0",
//call fib
"set temp IP",
"add temp 4",
"push temp",
"jump fib",
//program is finished, prints return value and jumps to end
"print returnValue",
"jump end",
//the fib function, which gets called recursively
":fib",
//if this is the base case, then we assert that f(0) = f(1) = 1 and return from the call
"jumple base n 1",
"jump notBase",
":base",
"set returnValue n",
"pop temp",
"jump temp",
":notBase",
//we want to calculate f(n-1) and f(n-2)
//this is where we calculate f(n-1)
"add n -1",
"push n",
"set temp IP",
"add temp 4",
"push temp",
"jump fib",
//return from the call that calculated f(n-1)
"pop n",
"push returnValue",
//now we calculate f(n-2)
"add n -1",
"set temp IP",
"add temp 4",
"push temp",
"jump fib",
//return from call that calculated f(n-2)
"pop n",
"add returnValue n",
//this is where the fib function ultimately ends and returns to caller
"pop temp",
"jump temp",
//end label
":end"
};
好的,我放了一个示例程序。是的,从第36行到第38行的块基本上是fB,但理想情况下,我希望能够做一些类似于递归因子的事情。所有的比较都必须使用jumpz命令来完成。现在研究一下:)这不是基本上是迭代吗?它做的更多的是“重复乘以2”,而不是“用2^(n-1)的结果乘以2”我想。我可能想得太远了。我在想,当你到达第一个2*f(n-1)时,你把2放在堆栈上..也许还有堆栈上的n-1..还有地址。但是我很难理解如何用这个pri让递归“递归”
int Fib(int n) {
if(n<=1) return n; // Fib(0) = 0; Fib(1) = 1
return Fib(n-1) + Fib(n-2);
}
## valid implementation in your toy language *and* x86 (AMD64 System V calling convention)
### Convenience macros for the toy asm implementation
# pretend that the call implementation has some way to make each return_address label unique so you can use it multiple times.
# i.e. just pretend that pushing a return address and jumping is a solved problem, however you want to solve it.
%define call(target) push return_address; jump target; return_address:
%define ret pop rettmp; jump rettmp # dedicate a whole variable just for ret, because we can
# As the first thing in your program, set eax, 0 / set ebx, 0 / ...
global Fib
Fib:
# input: n in edi.
# output: return value in eax
# if (n<=1) return n; // the asm implementation of this part isn't interesting or relevant. We know it's possible with some adds and jumps, so just pseudocode / handwave it:
... set eax, edi and ret if edi <= 1 ... # (not shown because not interesting)
add edi, -1
push edi # save n-1 for use after the recursive call
call Fib # eax = Fib(n-1)
pop edi # restore edi to *our* n-1
push eax # save the Fib(n-1) result across the call
add edi, -1
call Fib # eax = Fib(n-2)
pop edi # use edi as a scratch register to hold Fib(n-1) that we saved earlier
add eax, edi # eax = return value = Fib(n-1) + Fib(n-2)
ret
set n 3
push n
set initialCallAddress IP
add initialCallAddress 4
push initialCallAddress
jump fact
set finalValue 0
pop finalValue
print finalValue
jump 100
:fact
set rip 0
pop rip
pop n
push rip
set temp n
add n -1
jumpz end n
push n
set link IP
add link 4
push link
jump fact
pop n
mul temp n
:end
pop rip
push temp
jump rip
String[] x = new String[] {
//n is our input, which term of the sequence we want to calculate
"set n 5",
//temp variable for use throughout the program
"set temp 0",
//call fib
"set temp IP",
"add temp 4",
"push temp",
"jump fib",
//program is finished, prints return value and jumps to end
"print returnValue",
"jump end",
//the fib function, which gets called recursively
":fib",
//if this is the base case, then we assert that f(0) = f(1) = 1 and return from the call
"jumple base n 1",
"jump notBase",
":base",
"set returnValue n",
"pop temp",
"jump temp",
":notBase",
//we want to calculate f(n-1) and f(n-2)
//this is where we calculate f(n-1)
"add n -1",
"push n",
"set temp IP",
"add temp 4",
"push temp",
"jump fib",
//return from the call that calculated f(n-1)
"pop n",
"push returnValue",
//now we calculate f(n-2)
"add n -1",
"set temp IP",
"add temp 4",
"push temp",
"jump fib",
//return from call that calculated f(n-2)
"pop n",
"add returnValue n",
//this is where the fib function ultimately ends and returns to caller
"pop temp",
"jump temp",
//end label
":end"
};