$ra 注册被调用者保存还是调用者保存在 mips 中?

Nir*_*iru 3 architecture mips cpu-registers

我读过保留的寄存器是调用者保存的,非保留的寄存器是被调用者保存的。但在我看来,$ra,一个保留的寄存器,是调用者保存的,因为调用者保存了它必须返回的地址。任何人都可以解释我缺少什么吗?

Cra*_*tey 6

我读过保留的寄存器是调用者保存的,非保留的寄存器是被调用者保存的。

这可能不是陈述事物的最佳方式,并且可能是混淆的根源。这是一个更好的方法:

的函数(即,被叫方)必须保留 $s0-$s7,全局指针 $gp,堆栈指针 $sp,和帧指针 $fp

所有其他寄存器都可以由它认为合适的函数更改。

例如,当fncA调用 时fncB,它会:

jal    fncB
Run Code Online (Sandbox Code Playgroud)

返回地址放在[hardwired]寄存器中 $ra

最后,通常fncB通过 返回jr $ra。

但是,fncB可以在指令中使用任何寄存器jr,所以它可以:

move $v0,$ra
li   $ra,0
jr   $v0
Run Code Online (Sandbox Code Playgroud)

$ra被调用者为调用者保留真的没有意义。$ra是被调用函数将[通常]找到返回地址的地方,但如果愿意,它可以移动它。

在 中fncA,它可以:

jal   fncB
jal   fncB
Run Code Online (Sandbox Code Playgroud)

$ra两种情况下的价值都会不同,因此谈论$ra为调用者的利益而保留是没有意义的[因为没有]。

但在我看来 $ra,一个保留的寄存器

保存?通过谁?调用者不需要该值[也不关心它会发生什么,只要被调用者返回到正确的位置]。被调用函数并没有要保存$ra的调用者。它必须为自己保留返回地址[但不一定在$ra]。

因此,认为$ra由调用者或被调用者保留可能是不正确的

... 是调用者保存的,因为调用者保存了它必须返回的地址。

当调用者 [via jal] 在 中设置返回地址时$ra,它确实没有保存在保存寄存器 [在堆栈上] 的意义上它。

如果fncB调用另一个函数,fncC它通常会保留$ra并通常将其保存在堆栈中。但是,如果需要,它可以以其他方式保存寄存器内容。

此外,jalr指令可以被用来代替jal[和是非常大的地址跨度。所以,fncA可以这样做:

la    $t0,fncB
jalr  $t0
Run Code Online (Sandbox Code Playgroud)

但是,这实际上只是以下内容的简写:

la    $t0,fncB
jalr  $ra,$t0
Run Code Online (Sandbox Code Playgroud)

但是,如果fncB知道它是如何被调用的(即我们以不同的方式编写函数),我们可以使用不同的寄存器来保存返回地址:

la    $t0,fncB
jalr  $t3,$t0
Run Code Online (Sandbox Code Playgroud)

这里$t3将保存返回地址。这是一个非标准的调用约定(即不符合 ABI)。

我们可能有一个fncD完全符合 ABI的函数。但是,它可能会调用其他函数不会调用的几个内部函数(例如fncD1, fncD2, ...)。fncD可以自由地使用它选择的任何非标准调用约定来调用这些函数。

例如,它可以$t0-$t6用于函数参数而不是$a0-$a3. 如果fncD保留$s0-s7在外边缘,则这些可用于 的函数参数fncD1。

该只说是绝对硬寄存器$zero和$ra。因为$ra这只是因为它在jal指令中是硬连线/隐含的。如果我们只使用jalr,我们可以$ra像普通寄存器一样释放$t0。

其余的寄存器不是由 CPU 架构决定的,而只是由 ABI 约定决定的。

如果我们用 100% 汇编程序编写程序,编写我们自己的所有函数,我们可以使用我们希望的任何约定。例如,我们可以使用$t0堆栈指针寄存器而不是$sp. 那是因为 mips 架构没有推送/弹出指令,其中$sp寄存器是隐式的。它只有lw/sw并且我们可以使用我们想要的任何寄存器。

这是一个程序,演示了您可以执行的一些标准和非标准操作:

    .data
msg_jal1:   .asciiz     "fncjal1\n"
msg_jal2:   .asciiz     "fncjal2\n"
msg_jalx:   .asciiz     "fncjalx\n"
msg_jaly:   .asciiz     "fncjaly\n"
msg_jalz:   .asciiz     "fncjalz\n"
msg_jalr1:  .asciiz     "fncjalr1\n"
msg_jalr2:  .asciiz     "fncjalr2\n"
msg_post:   .asciiz     "exit\n"

    .text
    .globl  main
main:
    # for the jal instruction, the return address register is hardwired to $ra
    jal     fncjal1

    # but, once called, a function may destroy it at will
    jal     fncjal2

    # double level call
    jal     fncjalx

    # jalr takes two registers -- this is just a shorthand for ...
    la      $t0,fncjalr1
    jalr    $t0

    # ... this
    la      $t0,fncjalr1
    jalr    $ra,$t0

    # we may use any return address register we want (subject to our ABI rules)
    la      $t0,fncjalr2
    jalr    $t3,$t0

    # show we got back alive
    li      $v0,4
    la      $a0,msg_post
    syscall

    li      $v0,10                  # syscall for exit program
    syscall

# fncja11 -- standard function
fncjal1:
    li      $v0,4
    la      $a0,msg_jal1
    syscall
    jr      $ra                     # do return

# fncja12 -- standard function that returns via different register
fncjal2:
    li      $v0,4
    la      $a0,msg_jal2
    syscall

    # grab the return address
    # we can preserve this in just about any register we wish (e.g. $a0) as
    # long as the jr instruction below matches
    move    $v0,$ra

    # zero out the standard return register
    # NOTES:
    # (1) this _is_ ABI conforming
    # (2) caller may _not_ assume $ra has been preserved
    # (3) _we_ need to preserve the return _address_ but we may do anything
    #     we wish to the return _register_
    li      $ra,0

    jr      $v0                     # do return

# fncja1x -- standard function that calls another function
fncjalx:
    # preserve return address
    addi    $sp,$sp,-4
    sw      $ra,0($sp)

    li      $v0,4
    la      $a0,msg_jalx
    syscall

    jal     fncjal1
    jal     fncjal2

    # restore return address
    lw      $ra,0($sp)
    addi    $sp,$sp,4

    jr      $ra                     # do return

# fncja1y -- standard function that calls another function with funny return
fncjaly:
    # preserve return address
    addi    $sp,$sp,-4
    sw      $ra,0($sp)

    li      $v0,4
    la      $a0,msg_jaly
    syscall

    jal     fncjal1
    jal     fncjal2

    # restore return address
    lw      $a0,0($sp)
    addi    $sp,$sp,4

    jr      $a0                     # do return

# fncjalz -- non-standard function that calls another function
fncjalz:
    move    $t7,$ra                 # preserve return address

    li      $v0,4
    la      $a0,msg_jalz
    syscall

    jal     fncjal1
    jal     fncjal2

    jr      $t7                     # do return

# fncjalr1 -- standard function [called via jalr]
fncjalr1:
    li      $v0,4
    la      $a0,msg_jalr1
    syscall
    jr      $ra                     # do return

# fncjalr2 -- non-standard function [called via jalr]
fncjalr2:
    li      $v0,4
    la      $a0,msg_jalr2
    syscall
    jr      $t3                     # do return
Run Code Online (Sandbox Code Playgroud)

这个程序的输出是:

fncjal1
fncjal2
fncjalx
fncjal1
fncjal2
fncjalr1
fncjalr1
fncjalr2
exit
Run Code Online (Sandbox Code Playgroud)