具有不定数量参数的函数 MIPS

Function with indefinite number of arguments MIPS

我是 MIPS 的新手,如果这是一个愚蠢的问题,请原谅。 我有以下练习作为作业,如果有任何想法和起点,我将不胜感激。

我应该创建一个函数来接收参数 n(数字的数量),然后是 n 个数字,然后对 return 所述数字的总和和 return 进行计算在一个堆栈中。 我将如何开始呢?我在想这个函数可能有 4 个以上的数字,而实际数字的数量不同这一事实让我感到困惑。 函数参数应如下所示:(int n, int number1, int number2....etc).

我可以将数字存储在堆栈中,然后将堆栈用作函数中的参数吗?如果可以,我该怎么做?

更新: 所以我现在的想法(在我收到的帮助下)看起来像这样:

sum:
addu $t3,$sp,16   #add to t3 address of sp+16
addu $a1,$a1,$a2   #adding sum to a1,a1 is first element a2 second and a3 third
addu $a1,$a1,$a3
li $t0,4          #start with i=4
bge $t0,$a0,end_for   #while i<n
lw $v0,0($t3)     #load v0 with value in stack
addu $a1,$v0,$a1  #add to sum
addi $t3,$t3,4   #increment stack and go up for next element
addi $t0,$t0,1
end_for:
li $v0,1
move $a0,$a0
syscall
jr $ra

我尝试按原样组装它,但我的 MARS 停止响应。关于原因的任何线索?

在正常的 MIPS 调用约定中,第 4 个之后的参数已经存储在调用堆栈中,由您的调用者放置在那里。

标准调用约定在堆栈 args 之前留下填充,您可以在其中存储寄存器 args 以创建所有 args 的连续数组。 This PDF has a diagram, and see also MIPS function call with more than four arguments

这在 x86-64 Windows 中通常称为 "shadow space"。但是由于 MIPS jal 不会将任何内容存储到内存中(与将 return 地址压入堆栈的 x86 不同,MIPS 将 return 地址放入 $lr 中),即使调用约定不包括此阴影 space 函数仍然可以先调整 SP,然后存储与堆栈参数相邻的寄存器参数。所以我能看到的唯一好处是在无需调整堆栈指针的情况下为微型函数提供额外的刮擦 space。这不如在 x86-64 上有用,在 x86-64 上,没有它就不容易创建 args 数组。


或者您可以剥离处理 $a1 .. $a3 的前 3 个总和迭代(再次假设标准 MIPS 调用约定,前 4 个参数在寄存器中,$a0int n).

如果你还没有n,那么循环堆栈参数。


您可以编写一个 C 函数并查看优化的编译器输出,就像这样

#include <stdarg.h>
int sumargs(int n, ...) {
    va_list args;
    va_start(args, n);

    int sum=0;
    for (int i=0 ; i<n ; i++){
        sum += va_arg(args, int);
    }
    va_end(args);
    return sum;
}

va_startva_arg 不是真正的函数;他们将扩展为一些内联代码。 va_start(args,n)n 之后的参数传递寄存器转储到影子 space 中(与堆栈参数连续,如果有的话)。

不幸的是,MIPS gcc 不支持使用 $a0 和 $t0 等名称的 -mregnames 选项,但 google 发现 a nice table of register name<->number

MIPS asm output from the Godbolt compiler explorer

# gcc5.4 -O3 -fno-delayed-branch  
sumargs(int, ...):
      # on entry: SP points 16 bytes below the first non-register arg, if there is one.
        addiu   $sp,$sp,-16  # reserve another 16 bytes
        addiu   ,$sp,20    # create a pointer to the base of this array
        sw      ,20($sp)   # dump $a1..$a3 into the shadow space
        sw      ,24($sp)
        sw      ,28($sp)    
        sw      ,8($sp)    # spill the pointer into scratch space for some reason?
        blez    ,$L4       # check if the loop should run 0 times.
        nop                  # branch-delay slot.  (MARS can simulate a MIPS without delayed branches, so I told gcc to fill the slots with nops)

        move    ,[=11=]        # i=0
        move    ,[=11=]        # $v0 = sum = 0
$L3:                         # do {
        lw      ,0()
        addiu   ,,1        # i++
        addu    ,,       # sum += *arg_pointer
        addiu   ,,4        # arg_pointer++  (4 bytes)
        bne     ,,$L3    # } while(i != n)
        nop                  # fill the branch-delay slot

$L2:
        addiu   $sp,$sp,16
        j                 # return (with sum in $v0)
        nop

$L4:
        move    ,[=11=]                     # return 0
        b       $L2
        nop

do {}while(--n) 上循环会更有效率。 gcc在编译for循环时没有做这件事,这是一个遗漏的优化。