将值存储到数组的每个元素中:将 C do{}while 循环转换为 MIPS asm

Store a value into every element of an array: converting a C do{}while loop to MIPS asm

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

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

其中数组的地址在$a0,n在$a1,v在$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”和“data/stack 中的错误地址读取: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 的目的是什么?

好的,有一些问题。

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

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

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

要用 a0 加载 t1,您需要使用 add $t1,$a0,0 [或 add $t1,$a0,$zero]

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

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

在你的 blt 之后没有 return 所以即使循环有效,它也会 "fall off the edge of the world"。每个 called asm 例程 [像 jal set 一样被调用的例程] 必须有一个 explicit return 语句,它是 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 没有 x86 asm 所具有的强大索引寻址模式)。

所以,"correct" 可能取决于你的教授有多坚持。


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


为了完整起见,这里是我在测试时创建的 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

更新:

If in the loop a[i++] = v, would I place the add $t0, $t0, 1 line before the 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指令一一对应了。

When would I use the sll? I was reading examples and I noticed people usually do sll $t1, $t0, 2 when they're going to use a counter to go through an array.

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

Would I use the lw if the C code said something like int x = a[0]?

是的,完全正确。


另一种制作 asm 原型的方法是将 C 代码转换为 "very dumb C".

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

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

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

注册变量同时用作整数值和字节指针。因此,当添加到用作指针的寄存器时,就像添加到字节指针一样,因此要递增 int 数组,您必须添加 4.

字节指针和 int 指针之间的实际区别仅在您执行 load/store 操作时才重要:lw/sw 用于 intlb/sb 用于字节。

所以,这是我的第二个 C 函数,重新编码为 "dumb":

// 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:

In your first implementation, is add $a0, $a0, 4 the equivalent to using sll in the second implementation?

不完全是。要记住的关键是在 C 中,当我们将 1 添加到指针 [或使用 i 对其进行索引时,编译器将生成一条 increment/add 指令,该指令将 sizeof 添加到 type 指针定义为。

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

这是 C 编译器提供的一个强大的"convenience",因为它允许数组、指针、索引可以互换使用。

在 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

From what I understand, using sll sets i as a counter for ints so it increments i and also iterates the array

没有。 sll 只是 MIPS 的 "shift left logical" 指令,当您需要 C 的 << 运算符时可以使用它。

你在想的是sll如何使用来达到那个效果。

为了遍历 int 的数组,我们将 index 递增 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] 很简单(即 dumb)。他们一次只做一件事。如果语句足够复杂,单个 C 赋值语句可以生成大约 20 条指令。 asm 指令的组合方式产生了更复杂的结果。