$ra register callee saved 还是 caller saved in mips?

Whether $ra register callee saved or caller saved in mips?

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

当您使用指令 jaljalr 调用子例程时,return 地址存储在 $ra 中,因此如果您已经在子例程中,您将丢失该值,因此当 return 使用 ret 指令时,您可能有一个 Segmentation fault。所以在调用一个子程序之前(或者更一般地说,在使用 jalrjal 之前),你应该保存 $ra 寄存器

I've read that preserved registers are caller saved and non preserved registers are callee saved.

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

A function (i.e. callee) must preserve $s0-$s7, the global pointer $gp, the stack pointer $sp, and the frame pointer $fp

所有其他寄存器都可以由函数根据需要更改。

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

jal    fncB

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

最后,通常,fncB returns 通过 jr $ra.

但是,fncB 可以在 jr 指令中使用 any 寄存器,因此它可以:

move $v0,$ra
li   $ra,0
jr   $v0

calleecaller保留$ra真的没有任何意义。 $ra 是被调用函数 [通常] 找到 return 地址的地方,但如果需要,它可以移动它。

fncA 中,它可以做:

jal   fncB
jal   fncB

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

But it seems to me that $ra, a preserved register

保留? ?调用者不需要该值 [也不关心它会发生什么,只要被调用者 return 到达正确的位置即可]。被调用的函数 而不是 必须为调用者保留 $ra 。它必须为自己保留 return 地址 [但不一定在 $ra] 中。

因此,认为 $ra 由调用者 被调用者

保存可能是不正确的

... is caller saved as the caller saves the address to which it have to return.

当调用者[通过jal] 设置 $ra中的return地址时,它确实不是保存它是在[堆栈上]保存寄存器的意义上。

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

此外,可以使用 jalr 指令代替 jal [并且 用于非常大的地址跨度]。所以,fncA 可以做:

la    $t0,fncB
jalr  $t0

但是,这实际上只是一个 shorthand 用于:

la    $t0,fncB
jalr  $ra,$t0

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

la    $t0,fncB
jalr  $t3,$t0

此处 $t3 将保存 return 地址。这是一个非标准调用约定(即不符合 ABI)。

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

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

的函数参数

只有个绝对硬连线的寄存器是$zero$ra。对于 $ra 这只是 因为它在 jal 指令中是 hardwired/implicit。如果我们只使用 jalr,我们可以将 $ra 释放为像 $t0.

这样的普通寄存器

其余寄存器不是由CPU体系结构规定的,而仅仅是ABI约定。

如果我们用 100% 的汇编程序编写程序,编写我们自己的所有函数,我们可以使用我们希望的 任何 约定。例如,我们可以使用 $t0 作为我们的堆栈指针寄存器而不是 $sp。那是因为 mips 架构有 no push/pop 指令,其中 $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

这个程序的输出是:

fncjal1
fncjal2
fncjalx
fncjal1
fncjal2
fncjalr1
fncjalr1
fncjalr2
exit