gpt4 book ai didi

c - C编程为MIPS汇编(用于循环)

转载 作者:行者123 更新时间:2023-11-30 19:52:18 33 4
gpt4 key购买 nike

我正在尝试将此C代码转换为MIPS程序集,但不确定是否正确。有人能帮我吗?请

问题:假设a,b,i和j的值分别在寄存器$ s0,$ s1,$ t0和$ t1中。同样,假定寄存器$ s2保持数组D的基地址

C代码:

for(i=0; i<a; i++)
for(j=0; j<b; j++)
D[4*j] = i + j;


我在MIPS ASSEMBLY上的尝试

     add $t0, $t0, $zero   # i = 0
add $t1, $t1, $zero # j = 0
L1 : slt $t2, $t0, $s0 # i<a
beq $t2, $zero, EXIT # if $t2 == 0, Exit
add $t1, $zero, $zero # j=0
addi $t0, $t0, 1 # i ++
L2 : slt $t3, $t1, $s1 # j<b
beq $t3, $zero, L1, # if $t3 == 0, goto L1
add $t4, $t0, $t1 # $t4 = i+j
muli $t5, $t1, 4 # $t5 = $t1 * 4
sll $t5, $t5, 2 # $t5 << 2
add $t5, $t5, $s2 # D + $t5
sw $t4, $t5($s2) # store word $t4 in addr $t5(D)
addi $t0, $t1, 1 # j ++
j L2 # goto L2
EXIT :

最佳答案

add $t0, $t0, $zero # i = 0不,不会更改$t0,保留以前所做的任何垃圾。也许您打算使用addi $t0, $zero, 0

此外,MIPS没有2寄存器寻址模式(用于整数加载/存储),只有16-bit-constant ($reg)$t5($s2)是非法的。您需要单独的addu指令,或者更好的是指针增加。

(对于指针数学运算,应使用addu而不是add;如果地址计算从地址空间的低半到高半交叉,这不是错误。)



在C语言中,另一个线程在编写对象时正在读取对象是未定义的行为,因此我们可以优化外循环的实际循环。除非D的类型是_Atomic int *Dvolatile int *D,否则问题中未指定。

内部循环每次都与外部循环计数器无关地写入相同的元素,因此我们可以使用i = a-1优化外部循环并仅执行最终的外部迭代。除非a <= 0,否则我们必须跳过外循环主体,即不执行任何操作。

优化除最后一个商店之外的所有商店到每个位置的过程称为“消除死商店”。较早的外循环迭代中的存储是“无效”的,因为它们被覆盖而没有读取其值。



通常,您希望将循环条件放在循环的底部,因此循环分支是一个bne $t0, $t1, top_of_loop。 (MIPS具有bne作为本机硬件指令;除非第二个寄存器为blt,否则$zero只是伪指令。)所以我们想将j<b优化为j!=b,因为我们知道我们在计数向上。

在循环之前放置一个条件分支,以检查是否需要运行零次。例如如果blez $s0, after_loop,则b <= 0跳过内循环主体。

asm中惯用的for(i=0 ; i<a ; i++)循环在C语言中看起来像这样(或对此有所变化)。

 if(a<=0) goto end_of_loop;
int i=0;
do{ ... }while(++i != a);


或者,如果循环内未使用 i,则 i=ado{}while(--i)。 (即添加 -1并使用 bnez)。尽管MIPS在 i!=a上可以像在 i!=0上一样高效地分支,但与大多数带有FLAGS寄存器的体系结构不同,在递减计数的位置保存比较指令。



D[4*j]表示我们在一个字数组中跨了16个字节。分别使用乘以4和以2移位是疯狂的冗余。只需将指针保存在单独的寄存器中,就可以在每次迭代中将其递增16,就像C编译器一样。



我们不知道 D的类型,也不知道其他任何变量。如果它们中的任何一个都是窄无符号整数,则可能需要实现8或16位截断/换行。

但是您的实现假定它们都是 intunsigned,所以让我们开始吧。

我假设没有分支延迟插槽的MIPS,例如默认情况下MARS模拟。

在设置最终值的最后一次外循环迭代中, i+ja-1开头(j = 0)。它最多可运行 j=b-1,因此最大值为 a-1 + b-1

在编写任何asm之前,将问题简化为我们需要存储的值以及需要将它们存储在其中的位置,这意味着我们编写的asm更加简单且易于调试。

您可以通过在C源代码中进行转换并在C中进行单元测试来检查其中大多数转换的有效性。

# int a: $s0
# int b: $s1
# int *D: $s2

# Pointer to D[4*j] : $t0
# int i+j : $t1
# int a-1 + b : $t2 loop bound

blez $s0, EXIT # if(a<=0) goto EXIT
blez $s1, EXIT # if(b<=0) goto EXIT
# now we know both a and b loops run at least once so there's work to do

addiu $t1, $s0, -1 # tmp = a-1 // addu because the C source doesn't do this operation, so we must not fault on signed overflow here. Although that's impossible because we already excluded negatives
addu $t2, $t1, $s1 # tmp_end = a-1 + b // one past the max we store
add $t0, $s2, $zero # p = D // to avoid destroying the D pointer? Otherwise increment it.

inner: # do {
sw $t1, ($t0) # tmp = i+j
addiu $t1, $t1, 1 # tmp++;
addiu $t0, $t0, 16 # 4*sizeof(*D) # could go in the branch-delay slot
bne $t1, $t2, inner # }while(tmp != tmp_end)
EXIT:


我们可以先进行增量,然后再存储,然后将 a-2a+b-2用作 tmptmp_end的初始化程序。在某些实际的流水线/超标量MIPS CPU上,最好避免将增量放在读取它的 bne之前。 (将指针增量移动到分支延迟插槽后)。当然,您实际上会展开以节省工作,例如使用 sw $t1, 16($t0)32($t0) / 48($t0)

再次在具有分支延迟的实际MIPS上,您将移动 $t0..2的一些初始值以填充早期 blez指令中的分支延迟时隙,因为它们不能相邻。

如您所见,至少可以说您的版本过于复杂。问题中没有什么要说的是我们不必将每个C表达式分别音译为asm,并且C的全部要点是“假设”规则,该规则允许进行这样的优化。

关于c - C编程为MIPS汇编(用于循环),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19461669/

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com