1

我需要将此 C++ 函数转换为 MIPS 程序集:

int set(int a[], int n, int v)
{
    int i;
    i = 0;
    do {
      a[i++] = v;
    } while ( i < n);
    return i;
}

其中数组的地址是 in $a0, n 是 in $a1, v 是 in $a2。这是我在 MIPS 中编写函数的尝试,但我得到“指令在 0x00400054 处引用未定义符号”。在 main (由我的教授提供)中有一个jal set应该调用该set函数的调用,我很确定我的错误与此有关。我也不知道我是否成功转换了函数。这是我的尝试:

.text
set:
    li      $t0, 0
    addi    $t0, $t0, 0
    sll     $t1, $t0, 2
    add     $t1, $t1, $a0
L1: lw      $t1, 4($t1)
    sw      $a2, 0($t1)
    blt     $t0, $a1, L1

我正在使用 QTSPIM,如果这很重要的话。感谢您的帮助,如果您对 MIPS 编程有任何建议,那也太好了。

更新:

现在正在链接文件,但我得到一个“PC=0x004000f0 发生异常”和“数据/堆栈读取中的错误地址:0x00000000”的无限循环。这是我更新的文件:

.text
.globl set
set:    addi    $t0, $t0, 0     #i = 0;
         sll    $t1, $t0, 2     #offsets 4 * i
         add    $t1, $t1, $a0       #pointer to a[i]
L1:       lw    $t1, 4($t1)     #a[i++]
          sw    $a2, 0($t1)     #a[i++] = v
         blt    $t0, $a1, L1        #continue the loop as long as i < n

为什么我的代码必须在.globl?目的是.text什么?

4

1 回答 1

2

好的,有一些问题。

更正后的代码在底部。实际上有两种方法可以做到这一点,具体取决于 C 代码的翻译需要的文字程度。您的部分概念问题可能是您试图将这两种方法的部分结合起来。

您根据评论反馈[重复](例如lithen addi)将原始代码中的前两条指令合并为一条。但是,如果只使用一个,这li是正确的,因为addi将寄存器添加到自身,但您不能依赖初始值为零。

sll正在移动一个其中具有零值的寄存器,因此 inst 没有做任何事情。

要加载t1a0您可以使用add $t1,$a0,0[or add $t1,$a0,$zero]

无用lw[C 代码不加载a,为什么要 asm?]。

但是,我改变了这一切,因为循环仍然无法正常工作。

在你之后没有回报,blt所以即使循环有效,它也会“从世界的边缘掉下来”。每个被调用jal setasm例程 [一个像jr $ra

注意:在 MIPS asm 中,a*[参数寄存器]可以由被调用者修改,因此循环a0而不是t1(即调用者期望它们将被丢弃)


无论如何,这是更正的代码[请原谅无偿的风格清理]:

    .text

    .globl  set
set:
    li      $t0,0                   # i = 0
L1:
    sw      $a2,0($a0)              # a[i] = v
    add     $a0,$a0,4               # advance pointer
    add     $t0,$t0,1               # ++i
    blt     $t0,$a1,L1              # continue the loop as long as i < n
    jr      $ra                     # return

如果你原来的 C 函数是这样的:

int
set(int *a, int n, int v)
{
    int *end;

    end = a + n;
    for (;  a < end;  ++a)
        *a = v;

    return n;
}

那么,这将是一个更字面的翻译:

    .text

    .globl  set
set:
    sll     $a1,$a1,2               # convert int count to byte length
    add     $a1,$a0,$a1             # end = a + length

L1:
    sw      $a2,0($a0)              # *a = v
    add     $a0,$a0,4               # ++a
    blt     $a0,$a1,L1              # continue the loop as long as a < end

    jr      $ra                     # return

IMO,这两种方法都是原始 C 函数的可接受实现。第一个更直接,因为它保留了索引变量的[概念] i。但是,它有一个额外的指令,第二个没有。

优化器可能会生成相同的代码(即第二个 asm),而不管它正在翻译哪个 C 函数(MIPS 没有x86asm 所具有的强大的索引寻址模式)。

所以,哪个是“正确的”可能取决于你的教授有多坚持。


旁注:请注意我的两个示例之间的样式变化。也就是说,除了代码更改之外,添加一些空白行以提高清晰度。


为了完整起见,这是main我在测试时创建的函数:

    .data
arr:    .space  400                 # allocate more space than count

    .text

    .globl  main
main:
    la      $a0,arr                 # get array pointer
    li      $a1,10                  # get count
    li      $a2,3257                # value to store

    jal     set

    li      $v0,1                   # exit syscall number
    syscall

更新:

如果在循环a[i++] = v中,我会将add $t0, $t0, 1行放在前面sw $a2, 0($a0)吗?

不,如果 C 代码是a[++i] = v. 或许,最好的方法是先简化 C 代码。

a[i++] = v实际上是:

a[i] = v;
i += 1;

而且,a[++i] = v实际上是:

i += 1;
a[i] = v;

现在,C 代码行和 asm 指令之间存在一一对应的关系。

我什么时候使用sll?我正在阅读示例,我注意到人们通常sll $t1, $t0, 2在使用计数器遍历数组时会这样做。

是的。如果您仔细查看我的第二个实现,它会sll以这种方式使用。此外,即使给定原始 C 代码,这也是我编写循环的方式。

lw如果 C 代码说类似的话,我会使用int x = a[0]吗?

对,就是这样。


另一种制作 asm 原型的方法是将 C 代码转换为“非常愚蠢的 C”。

也就是说,只有if最简单的形式:if (x >= y) goto label. 甚至if (x < y) j = 10是禁区。

没有函数作用域变量或函数参数变量——只有作为寄存器名称的全局变量。

没有复杂的表达。只有简单的,如x = y,x += yx = y + z。这样,a = b + c + d就太复杂了。

寄存器变量既可以用作整数值,也可以用作字节指针。因此,当添加到用作指针的寄存器时,就像添加到字节指针一样,因此要通过int数组递增,您必须添加4.

字节指针和指针之间的实际区别int仅在您执行加载/存储操作时才重要:lw/swforintlb/sbfor byte。

所以,这是我的第二个 C 函数重新编码为“哑”:

// RETURNS: number of elements changed
int
set(void)
// a0 -- "a" (pointer to int array)
// a1 -- "n" (number of elements in "a")
// a2 -- "v" (value to set into "a")
{

    v0 = a1;                            // set return value

    a1 <<= 2;                           // convert int count to byte length
    a1 += a0;                           // point to one past end of array

L1:
    *(int *)a0 = a2;                    // store v at current array location
    a0 += 4;                            // point to next array element
    if (a0 < a1) goto L1;               // loop back until done

    return;
}

更新#2:

在您的第一个实现中,是否add $a0, $a0, 4等同sll于在第二个实现中使用?

不完全的。要记住的关键是在 C 中,当我们向指针添加 1 [或用 索引它i] 时,编译器将生成一个递增/添加指令,该指令将指针定义为 sizeof类型添加。

也就是说,对于int *iptr,指定iptr += 1将生成add $a0,$a0,4因为sizeof(int)是 4。如果我们有double *dptrdptr += 1编译器将生成add $a0,$a0,8因为sizeof(double)是 8

这是 C 编译器提供的强大“便利”,因为它允许数组、指针、索引可以互换使用。

在 asm 中,我们必须手动完成 C 编译器会自动为我们做的事情。

考虑以下内容:我们有一个值,它是数组中元素数量的计数,称为它count。现在,我们想知道数组将占用多少字节。我们称之为len. 这是确定各种类型的C代码:

char *arr;
len = count * sizeof(char);
len = count * 1;
len = count << 0;
//  sll $a1,$a1,0

short *arr;
len = count * sizeof(short);
len = count * 2;
len = count << 1;
//  sll $a1,$a1,1

int *arr;
len = count * sizeof(int);
len = count * 4;
len = count << 2;
//  sll $a1,$a1,2

double *arr;
len = count * sizeof(double);
len = count * 8;
len = count << 3;
//  sll $a1,$a1,3

据我了解,使用 sll 将 i 设置为整数的计数器,因此它增加 i 并迭代数组

sll,仅仅是 MIPS 的“逻辑左移”指令,当你需要相当于 C 的<<运算符时,你可以使用它。

您正在考虑的是如何sll 使用来实现该效果。

要遍历 的数组int,我们将索引增加 1,但我们还必须将数组指针增加 4。这就是我的第一个 asm 示例所做的。终止条件为index >= count

在我的第二个 asm 示例中,我通过将元素计数转换为字节长度(通过ssl)消除了单独的索引变量,并添加了数组地址。现在$a1有了数组最后一个元素的地址 + 1,终止条件为current_address >= last_element_plus_1. 请注意 current_address ( $a0) 仍然必须增加 4 ( add $a0,$a0,4)

要记住的一件重要的事情是 asm 指令[特别是 MIPS] 很简单(即)。他们一次只做一件事。如果语句足够复杂,一条 C 赋值语句可以生成大约 20 条指令。组合 asm 指令的方式会产生更复杂的结果。

于 2016-02-15T02:03:03.050 回答