将C ++函数转换为MIPS程序集

时间:2016-02-14 23:11:09

标签: c++ mips qtspim

我需要将这个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”和“数据/堆栈读取错误地址: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的目的是什么?

1 个答案:

答案 0 :(得分:1)

好的,有一些问题。

更正后的代码位于底部。实际上有两种方法可以实现,基于C代码的翻译需要的字面值。你的概念问题的一部分可能是你试图结合两种方法的部分。

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

sll正在移动一个零值的寄存器,因此inst不会做任何事情。

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

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

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

blt之后没有回复,所以即使循环有效,它也会“脱离世界的边缘”#34;。每个调用 asm例程[一个像jal set调用的例程]必须有一个显式返回语句,即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所做的强大的索引寻址模式。

所以,这是&#34;正确&#34;可能取决于你的教授是多么坚固。

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

为了完整起见,这是我在测试时创建的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代码,它也是我编写循环的方式。

  

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

是的,确实。

另一种原型asm的方法是将C代码转换为非常愚蠢的C&#34;。

即,只有最简单形式的ifif (x >= y) goto label。即使if (x < y) j = 10也不受限制。

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

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

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

字节指针和int指针之间的实际差异仅在执行加载/存储操作时很重要:lw/sw用于intlb/sb用于字节。

所以,这里我的第二个C函数记录为&#34; dumb&#34;:

// 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中,当我们向指针添加一个[或用i索引它]时,编译器将生成一个增量/加法指令,用于添加sizeof type 指针定义为。

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

这是一个强大的方便&#34; 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设置为int的计数器,因此它递增i并且还迭代数组

没有。 sll仅仅是MIPS&#34;&#34;左移逻辑&#34;当您需要相应的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指令产生更复杂的结果的。