2.4
:material-circle-edit-outline: 约 412 个字 :fontawesome-solid-code: 27 行代码 :material-clock-time-two-outline: 预计阅读时间 2 分钟
Procedures
汇编语言的程序编写
Procedure Call Instructions
- Instruction for procedures:
jal ( jump-and-link )
- Address of following instruction put in x1
- Jumps to target address
- Procedure return:
jalr (jump and link register)
- Like
jal
, but jumps to 0 + address in x1 - Use x0 as
rd
(x0 cannot be changed) - Can also be used for computed jumps
- Like
Stack
- Registers for procedure calling
a0 ~ a7(x10-x17)
: eight argument registers to pass parameters & return valuesra/x1
:one return address register to return to origin point- 上面的caller就是用这个
- Stack grow from higher address to lower address
- 约定起始位置在高位,往低位扩张
- Push: \(sp= sp - 8\)
addi sp,sp,-8; sd …,8(sp);
- Pop: \(sp = sp + 8\)
ld …,8(sp); addi sp,sp,8;
- 8 是字长
- Stack pointer
sp
叶程序不会再调用别的函数
红虚线内是运算部分,和栈无关
将数据放入堆栈的好处是可以临时释放寄存器给运算过程使用
t0 ~ t6和s0 ~ s11是同名标记,是人为规定的,前者不需要保存,后者需要保存
保护现场的事情是被调用者callee负责的,就是保存这些寄存器里面的数据,被调用时首先进行保存,运行结束后还给caller
调用程序的人有权利保护t0~t6,只要它觉得重要,当然这个不是必须的
- 可以加一个Frame pointer
fp
,指向栈的基地址,用于检索不同的栈
Nested Procedures
嵌套程序,以求阶乘函数为例:
#n in a0, res in a0
fact:
addi sp, sp, -16 # adjust stack for 2 items
sd ra, 8(sp) # save the return address: x1
sd a0, 0(sp) # save the argument n: x10
addi t0, a0, -1 # x5 = n - 1
bge t0, zero, L1 # if n >= 1, go to L1 (else)
addi a0, zero, 1 # return 1 if n < 1
addi sp, sp, 16 # Recover sp (Why not recover x1 and x10?)
jalr zero, 0(ra) # return to caller
L1:
addi a0, a0, -1 # n >= 1: argument gets (n - 1)
jal ra, fact # call fact with (n - 1)
add t1, a0, zero # move result of fact(n - 1) to x6 (t1)
ld a0, 0(sp) # return from jal: restore argument n
ld ra, 8(sp) # restore the return address
add sp, sp, 16 # adjust stack pointer to pop 2 items
mul a0, a0, t1 # return n * fact(n - 1)
jalr zero, 0(ra) # return to the caller
Memory Layout
一个程序在内存中是这样的
左边一串二进制是地址
- Text 存放所有的指令代码,PC从Text开始执行一条条指令
- Static data: global variables
- Dynamic data: heap
- Stack: automatic storage
- 这就是为什么stack是从高位往低位扩展