有人能给予我一个例子,说明如何递归将在ARM大会只列出了这里的指令(为视觉)?
我试图做一个递归的斐波那契和阶乘函数的类。我知道递归是一个调用函数的函数,但我不知道如何在ARM中模拟它。
https://salmanarif.bitbucket.io/visual/supported_instructions.html
如果链接不起作用,我使用visUAL,这些是我可以使用的唯一指令:
- MOV
- MVN
- ADR
- LDR
- 添加
- ADC
- SUB
- SBC
- RSB
- RSC
- 和
- 提高采收率
- BIC
- ORR
- LSL
- LSR
- ASR
- ROR
- RRX
- CMP
- CMN
- TST
- TEQ
- LDR
- LDM
- 扫描隧道显微镜
- B
- BL
- 灌装
- 结束
这不会为R4加载旧的值,所以R4只是在每次函数调用自身时加倍。
;VisUAL initializess all registers to 0 except for R13/SP, which is -16777216
MOV R4, #0
MOV R5, #1
MOV r0, #4
MOV LR, #16 ;tells program to move to 4th instruction
FIB
STMDB SP!, {R4-R6, LR} ;Stores necessary values on stack (PUSH command)
LDR R4, [SP] ;Loads older value for R4 from memory
ADD R4, R4, R5 ;Adds R5 to R4
STR R4, [SP], #8 ;stores current value for R4 to memory
MOV R5, R4 ;Makes R5 = R4
CMP R4, #144 ;If R4 >= 144:
BGE POP ;Branch to POP
MOV PC, LR ;Moves to STMDB(PUSH) statement
POP
LDMIA SP!, {R4-R6, LR} ;Pops registers off stack
END ;ends program
字符串
2条答案
按热度按时间muk1a3rh1#
您需要使用堆栈、STMDB和LDMIA指令。在真实的的ARM工具上,使用“统一”符号,它们也有助记符PUSH和POP。
Fibonnaci和factorial不是很好的例子,因为它们不“需要”递归。但让我们假装他们知道。我会选择Fibonacci,因为你没有穆尔指令!?您要执行以下操作:
字符串
它应该以包含fib(6)== 8的R 0终止。当然,这段代码效率很低,因为它重复调用FIB获取相同的值。
STM是必需的,因此您可以使用寄存器r4、r5,因为另一个函数调用可以更改r 0-r3和LR。推LR和弹出PC就像B LR。如果你调用C代码,你应该压入偶数个寄存器来保持SP 64位对齐(这里我们不需要这么做;忽略R6)。
yhxst69z2#
一些其他递归函数:
字符串
构建/拆卸
型
如果你不理解它,那么它是值得的。让一个工具帮你做这件事是作弊吗?
push是stm的伪指令,pop是ldm的伪指令,所以你可以使用它们。
我使用了一个静态局部变量,我称之为局部全局变量,它位于.data中,而不在堆栈中(在本例中是.bss,因为我将其设置为零)
型
第一个TO加载将该值加载到R3中。
调用约定规定r 0将包含进入函数的第一个参数(有例外,但在这种情况下是正确的)。
所以我们从内存中获取z,r 0已经有了参数x,所以我们把x加到z上,并保存到内存中
编译器不按顺序进行比较,因为谁知道性能原因,add和str写的不修改标志,所以没关系,
如果x不等于零,它分支到28,这就执行了so(x-1)调用,从内存中读回r3(调用约定说r 0-r3是volatile的,你可以随意修改它们,而不必保留它们,所以我们在r3中的z版本可能已经被破坏了,但r4被任何被调用者保留了,所以我们把z读回r3。我们将r4和返回地址从堆栈中取出,我们用z准备返回寄存器r 0并进行返回。
如果x等于零(18上的bne失败,我们运行1c,然后20,然后24),那么我们将z(r3版本)复制到r 0中,r 0是用于根据该编译器使用的调用约定(arms推荐)从该函数返回的寄存器。并返回。
链接器将把z的地址填充到偏移量0x 40,这是一个对象,而不是最终的二进制...
型
这里的重点不是欺骗和使用编译器,这里的重点是递归函数没有什么神奇之处,当然不是如果你遵循调用约定或任何你喜欢的术语。
例如
如果你有参数r 0是第一个,r1是第二个,直到r3(如果它们合适,让你的代码这样做,你有四个或更少的参数)返回值是在r 0如果它适合你需要推lr在堆栈上,因为你将调用另一个函数r4在uppreserve如果你需要修改它们,如果你想要一些本地存储,或者通过相应地修改堆栈指针来使用堆栈(或者执行push/stms)。你可以看到gcc将寄存器中的内容保存到堆栈中,然后在函数期间使用寄存器,至少有几个局部变量值,除此之外,它需要在堆栈上进行多次操作,sp相对。当你进行递归调用时,你按照调用约定像任何其他正常函数一样进行,如果你需要在调用之前保存r 0-r3,那么在寄存器r4或更高的寄存器中或在堆栈上这样做,在函数返回后恢复。你可以看到,把你想在函数调用之前和之后保存的值放在寄存器r4或更高的寄存器中会更容易。编译器可以在分支之前完成r 0的比较,这样读起来更容易。同样,在弹出之前,也可以将返回值mov到r 0
我没有输入参数,所以如果我要求更新一些的话,我的gcc版本看起来是armv 4 t。
型
你可以看到回报读起来容易一点
尽管错过了优化,但是它可以执行LDR_R0,[R4]并保存指令。或者使尾端保持原样,且BNE可以是BEq到30(movr 0,r3; pop{r4,pc}并共享退出。
可读性更强一点
型
编辑
让我们来看看最后一个。
型
一个堆栈就像一个迪克西杯保持器人,这可能是在你的时间。一个杯保持器你把一个杯子拉下来,下一个和其余的杯子留在杯架里,你可以把一个推回那里。
所以堆栈是函数的临时存储,在cup上写入一个数据项,然后将其推到保持器中(从调用者保存r4)写入另一个项并将其推到holder中(lr,从调用者返回地址)。我们在这里每个函数只使用了两个项目,所以每个函数我可以把两个杯子推到保持器上,每个函数的调用我得到两个新的和唯一的存储位置来存储这些本地信息。当我退出函数时,我将两个杯子从保持器中拉出来,并使用它们的值(并丢弃它们)。这在某种程度上是递归的关键,堆栈为每个调用提供了新的本地存储,与之前对同一函数的调用分开,如果没有其他的话,你需要一个返回地址(尽管做了一些更简单的递归例子,当优化时没有足够聪明地从它中产生一个循环)。
ldr_rd,[rn]把括号看作是在那个地址处的项,因此读取rn中的地址处的存储器并将那个值保存在rd中。
str rd,[rn]一个混乱的arm指令作为其余的第一个参数是左边的equals(add r1,r2,r3 r1 = r2 + r3,ldr r1,[r4] r1 = [r4])这一个是backward [rn] = rd将rd中的值存储到地址r4所描述的内存位置,一级间接。
stmdb sp!,意味着在做任何事情之前递减堆栈指针4字节乘以列表中寄存器的数量,然后将第一个最低编号的寄存器写入[sp+0],然后写入[sp+4],依此类推,最后一个将比sp的起始值小4。!表示函数结束时sp为递减后的值。你可以使用ldm/stm来完成除了栈的push和pops之外的事情。就像memcpy,但那是另一个故事。。
所有这些都可以在infocenter.arm.com上找到arm文档,你应该已经有了(arm架构参考手册,如果你还没有读过的话,armv 5是首选的第一本)。