在数组的每个元素中存储一个值:将C do{}while循环转换为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处的未定义符号;主要(由我的教授提供)有一个调用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的目的是什么?

好的,有一些问题。

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

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

sll正在对其中有零值的寄存器进行移位,因此inst没有执行任何操作。

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

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

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

在你的blt之后就没有回报了,所以即使循环有效,它也会"从世界的边缘掉下来"。每个调用的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没有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代码。

如果C代码表示类似int x = a[0]的内容,我会使用lw吗?

没错。


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

也就是说,只有最简单形式的ifif (x >= y) goto label。即使是if (x < y) j = 10也是禁止进入的。

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

没有复杂的表达式。只有简单的,如x = yx += yx = y + z。因此,a = b + c + d将过于复杂。

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

字节指针和int指针之间的实际差异只有在执行加载/存储操作时才重要: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:

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

不完全是。需要记住的关键是,在C中,当我们向指针添加一个[或用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设置为int的计数器,因此它递增I并迭代数组

否。sll只是MIPS的"左移逻辑"指令,当您需要等效于C的<<运算符时,可以使用它。

您所想的是如何使用sll来实现这种效果。

要迭代int的数组,我们将索引增加1,但也必须将数组指针增加4。这是我的第一个asm示例所做的。终止条件为CCD_ 70。

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

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

最新更新