如何避免 MIPS 汇编中的重复系统调用和移动?

How to avoid repetitive syscalls and moves in MIPS Assembly?

这是我的代码片段,如您所见,这三者之间的唯一区别是从 $v0 移动到 $t0、$t1 或 $t2。有谁知道有没有办法简化这些。

                        .text
    main:               #start of program
    loop:
    #read the ints (store them in t0, t1, t2)
    la  $a0, input_msg  #load input_msg into $a0 to prepare for syscall
    
    li  $v0, 4      #set $v0 to 4, which will print what $a0 points at
    syscall         #make the syscall
    li  $v0, 5      #prepare $v0 for reading an int
    syscall         #read the int
    move    $t0, $v0    #move the information from $v0 to $t0
    
    li  $v0, 4      #set $v0 to 4, which will print what $a0 points at
    syscall         #make the syscall
    li  $v0, 5      #prepare $v0 for reading an int
    syscall         #read the int
    move    $t1, $v0    #move the information from $v0 to $t1
    
    li  $v0, 4      #set $v0 to 4, which will print what $a0 points at
    syscall         #make the syscall
    li  $v0, 5      #prepare $v0 for reading an int
    syscall         #read the int
    move    $t2, $v0    #move the information from $v0 to $t1

没有办法简化;这已经是最小的了,除非你想写一个循环存储到内存中的数组1.

任何系统调用都需要将函数代码放入 $v0

因此,另一个系统调用需要重新调整 $v0 寄存器的用途,导致先前的值丢失,除非它保存在别处。

这是汇编/机器代码与其他编程语言的区别之一。其他语言的逻辑变量具有名称、类型、scope/lifetime,并在运行时保持值,相同的值直到改变。局部变量将在系统或库调用中保持其值(除非明确通过引用传递,或者在比 C 更高级的语言中被关闭)。

而在机器代码中我们有物理存储,即寄存器和内存,尤其是寄存器经常被重新利用。所以,这是编译器和汇编程序员在将我们算法的逻辑变量映射到物理存储机器代码时必须处理的事情。


脚注 1:但是如果您在循环中存储,则必须在要使用它们时重新加载它们。这将意味着执行更多的指令,但如果循环开销 + 重新加载开销小于循环体的 3 个副本中的 2 个,则可能意味着更小的代码大小。在这种情况下,无论哪种方式都接近收支平衡。

正如 Nate 所建议的那样,您可以将重复的内容包装在宏中以保持源代码更整洁,同时生成相同的机器指令。那么您的来源可能看起来像 la $a0, input_msgPROMPT_AND_READ_INT $t0,然后又是 $t1,等等


然而,除此之外,这就是 MIPS 和 RISC V 以及其他一些 ABI 的定义方式,因此在这些环境中没有办法解决这个问题。但这些并不是定义事物的唯一方式。

我们可以想象另一种 ABI 架构,其中函数(或系统调用)的结果被推送到 call/thread 堆栈。这意味着可以进行多次调用并且结果将累积(在堆栈上)。虽然这对于某些情况可能非常好,但对其他情况却不利,分析表明,做最少的事情:在寄存器中返回值,平均而言是最好的。让调用者在需要时将 function/syscall 结果压入堆栈,在性能上实际上等同于总是这样做,然后,在不需要时,不这样做可以节省内存引用(写入堆栈,然后从stack) 这也使代码 shorter/denser (也是已知的胜利)。