【问题标题】:Function with indefinite number of arguments MIPS具有无限个参数 MIPS 的函数
【发布时间】:2019-01-18 09:47:20
【问题描述】:

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

我应该创建一个函数来接收参数 n(数字的数量),然后是 n 个数字,然后处理返回所述数字的总和并将其返回到堆栈中。 我该如何开始呢?我认为该函数可能有 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 停止响应。有什么线索吗?

【问题讨论】:

  • 你确定教授不是要你使用数组吗?您可以使用 varadic 函数来执行此操作,但如果调用约定传递寄存器中的第一个参数并且堆栈中没有归位,则实现需要一些预处理,否则会变得混乱。 MIPS 传递寄存器中的第一个参数,堆栈中的其余参数没有归位(我想),所以最好先澄清 n 个数字是如何传递的,然后再注册一个 varic 实现。
  • @MargaretBloom 是的,他说它应该相当于 C 中的 add(3,1,2,3), add (2,1,5) 之类的东西(第一个参数告诉我们接下来会有多少数字)
  • @MargaretBloom:MIPS 使用链接寄存器而不是推送返回地址,因此您可以将寄存器 args 与堆栈 args 连续存储以形成 args 数组。事实上,它像 Windows x64 一样留下阴影空间。大概这么小的函数可以省去操作堆栈指针?例如所以他们可以在调用另一个函数之前将$lr 存储在那里?但是不,他们必须为被调用者保留影子空间,所以它只有助于需要溢出某些东西的叶子函数,我认为。似乎是一个糟糕的设计(尤其是与有一个红色区域相比)。也许是一些放松的约定?
  • @PeterCordes 关于$lr 的好点子。我实际上没想到 MIPS 会有影子/归位空间。总而言之,这比预期的要容易。
  • @MargaretBloom:是的,我也很惊讶地发现它有影子/家庭空间,就像我在回答中所说的那样。没有它,它已经微不足道了,不像 x64,所以它的价值要低得多(我认为红色区域会是一个更好的选择)。但即使没有这些,剥离前 3 次迭代也很容易。可以是带有 2 个循环的单独数组,也可以是完全剥离/展开。

标签: assembly mips mars-simulator


【解决方案1】:

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

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

这在 x86-64 Windows 中通常称为“阴影空间”。但是由于 MIPS jal 不会将任何内容存储到内存中(与将返回地址压入堆栈的 x86 不同,MIPS 将返回地址放在 $lr 中),即使调用约定不包含此影子空间函数仍然可以先调整 SP,然后将寄存器 args 与堆栈 args 连续存储。所以我能看到的唯一好处是给微小的函数额外的暂存空间,而无需调整堆栈指针。这不如在 x86-64 上有用,在 x86-64 上,没有它就很难创建一个 args 数组。


或者您可以剥离处理 $a1 .. $a3 的前 3 个 sum 迭代(再次假设标准 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 之后的参数传递寄存器转储到影子空间(与堆栈参数连续,如果有的话)。

不幸的是,MIPS gcc 不支持 -mregnames 选项来使用像 $a0 和 $t0 这样的名称,但谷歌发现 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   $3,$sp,20    # create a pointer to the base of this array
        sw      $5,20($sp)   # dump $a1..$a3 into the shadow space
        sw      $6,24($sp)
        sw      $7,28($sp)    
        sw      $3,8($sp)    # spill the pointer into scratch space for some reason?
        blez    $4,$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    $5,$0        # i=0
        move    $2,$0        # $v0 = sum = 0
$L3:                         # do {
        lw      $6,0($3)
        addiu   $5,$5,1        # i++
        addu    $2,$2,$6       # sum += *arg_pointer
        addiu   $3,$3,4        # arg_pointer++  (4 bytes)
        bne     $4,$5,$L3    # } while(i != n)
        nop                  # fill the branch-delay slot

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

$L4:
        move    $2,$0                     # return 0
        b       $L2
        nop

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

【讨论】:

  • 关于你的第二个选项,我在考虑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&lt;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 end_for: li $v0,1 move $a0,$a0 syscall jr $ra
  • (忘记为 i++ 添加 addi $t0,$t0,1)
  • @valentinegeorge:cmets 中的代码在没有换行符或任何格式的情况下是不可读的。尤其是当它包含 cmets 时。自己测试一下。
  • 好的,抱歉,现在通过写一个带有适当换行符的答案来修复它
猜你喜欢
  • 1970-01-01
  • 2011-01-18
  • 2019-08-16
  • 2022-07-27
  • 1970-01-01
  • 2015-01-03
  • 2018-10-02
  • 2017-08-29
  • 1970-01-01
相关资源
最近更新 更多