我需要将这个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代码的翻译需要有多字面。你的部分概念问题可能是你试图将这两种方法的部分结合起来。
根据评论反馈[重复](例如li
和addi
),您将原始代码中的前两条指令合并为一条。但是,如果只使用一个,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没有x86
asm那样强大的索引寻址模式)。
所以,哪一个是"正确的"可能取决于你的教授有多固执
旁注:注意我的两个例子之间的风格变化。也就是说,抛开代码更改不谈,添加一些空行以提高清晰度。
为了完整起见,下面是我在测试时创建的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"。
也就是说,只有最简单形式的if
:if (x >= y) goto label
。即使是if (x < y) j = 10
也是禁止进入的。
没有函数范围的变量或函数参数变量——只有作为寄存器名的全局变量。
没有复杂的表达式。只有简单的,如x = y
、x += y
或x = y + z
。因此,a = b + c + d
将过于复杂。
寄存器变量同时充当整数值和字节指针。因此,当添加到用作指针的寄存器时,就像添加到字节指针一样,所以要在int
数组中递增,必须添加4
。
字节指针和int
指针之间的实际差异只有在执行加载/存储操作时才重要:lw/sw
表示int
,lb/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 *dptr
,dptr += 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指令的组合方式产生了更复杂的结果。