我汇编了一个简单的c程序来进行mips,并尝试理解汇编代码。通过与C代码进行比较,我几乎了解了它,但是仍然遇到了一些问题。
我使用mips-gcc生成汇编代码:$ mips-gcc -S -O2 -fno-delayed-branch -I / usr / include lab3_ex3.c -o lab3_ex3.s
这是我对汇编代码如何工作的猜测:
main
是程序的入口。
$6
是源数组的地址。
$7
是目标数组的地址。
$3
是源数组的大小。
[$2
是变量k
,并初始化为0。
$L3
是循环
[$5
和$4
是source[k]
和dest[k]
的地址。
[sw $3,0($5)
等效于将source[k]
存储在$3
中。
[lw $3,4($4)
等效于将source[k]
分配给dest[k]
。
[addiu $2,$2,4
等效于k++
。
bne $3, $0, $L3
表示如果source[k]
为零,则退出循环,否则跳至标签$L3
。
$L2
只是做一些清理工作。
将$2
设置为零。
跳转到$31
(返回地址)。
我的问题是:
.frame $sp,0,$31
做什么?lw $3,4($4)
代替lw $3,0($4)
%lo(source)($6)
表示什么意思? ($ hi和$ lo $寄存器用于乘法运算,为什么在这里使用它们?)谢谢。
C
int source[] = {3, 1, 4, 1, 5, 9, 0};
int dest[10];
int main ( ) {
int k;
for (k=0; source[k]!=0; k++) {
dest[k] = source[k];
}
return 0;
}
组装
.file 1 "lab3_ex3.c"
.section .mdebug.eabi32
.previous
.section .gcc_compiled_long32
.previous
.gnu_attribute 4, 1
.text
.align 2
.globl main
.set nomips16
.ent main
.type main, @function
main:
.frame $sp,0,$31 # vars= 0, regs= 0/0, args= 0, gp= 0
.mask 0x00000000,0
.fmask 0x00000000,0
lui $6,%hi(source)
lw $3,%lo(source)($6)
beq $3,$0,$L2
lui $7,%hi(dest)
addiu $7,$7,%lo(dest)
addiu $6,$6,%lo(source)
move $2,$0
$L3:
addu $5,$7,$2
addu $4,$6,$2
sw $3,0($5)
lw $3,4($4)
addiu $2,$2,4
bne $3,$0,$L3
$L2:
move $2,$0
j $31
.end main
.size main, .-main
.globl source
.data
.align 2
.type source, @object
.size source, 28
source:
.word 3
.word 1
.word 4
.word 1
.word 5
.word 9
.word 0
.comm dest,40,4
.ident "GCC: (GNU) 4.4.1"
首先,main
,$L3
和$L2
是3个基本块的标签。您对它们的功能大致是正确的。
问题1:.frame在做什么
这不是MIPS指令。它是描述此功能(堆栈)框架的元数据:
$sp
指向堆栈,$29
的别名。$16-$23
。$31
)有关MIPS调用约定的更多信息,请参见此doc。
[[问题2:为什么用$ 3,4($ 4)代替$ 3,0($ 4)
这是由于循环的优化。通常,加载和存储的顺序为:$L3
中,并且包含load source[k]
和store dest[k]
。不是。有两个线索可以看到:main
块中的负载与循环外的任何负载都不对应$L3
中,存储在加载之前。 load source[0]
在名为main
的基本块中执行。然后,基本块$L3
中的循环为store dest[k];load source[k+1];
。因此,加载使用的偏移量比存储的偏移量大4,因为它正在加载下一次迭代的整数。 问题3:lo / hi语法是什么?
这与指令编码和指针有关。让我们假设一个32位体系结构,即一个指针是32位。像大多数固定大小的指令ISA一样,让我们假设指令大小也是32位。从source
/ dest
数组加载和存储之前,需要将它们的指针分别加载到寄存器$6
和$7
中。因此,您需要一条指令将32位常量地址加载到寄存器中。但是,一条32位指令必须包含一些位来编码操作码(指令是该操作),目标寄存器等。因此,一条指令还剩下不到32位用于编码常数(称为立即数)。因此,您需要两条指令将32位常量加载到寄存器中,每条指令加载16位。lo
/ hi
指的是常量的哪一半被加载。
示例:假设dest
位于地址0xabcd1234。有两条指令将此值加载到$7
。
lui $7,%hi(dest)
addiu $7,$7,%lo(dest)
lui
是立即加载上限。它将dest
(0xabcd
)的地址的高16位装入$7
的高16位。现在,$7
的值为0xabcd0000。
addiu
是“添加立即无符号”。它将dest
(0x1234
)地址的低16位与$7
中的现有值相加,以获得新的$7
值。因此,$7
现在拥有dest
的地址0xabcd0000 + 0x1234 = 0xabcd1234。
类似地,lw $3,%lo(source)($6)
从由$6
指向的地址(该地址已经保存了source
的地址的高16位)开始加载,其偏移量为%lo(source)
(该地址的低16位)。有效地,它加载source
的第一个单词。