我将一个简单的 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 是 dest 数组的地址。
$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意味着 ifsource[k]为零则退出循环,否则跳转到 label $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;
}
Run Code Online (Sandbox Code Playgroud)
集会
.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"
Run Code Online (Sandbox Code Playgroud)
首先,main,$L3和$L2对于3个基本块的标签。您对它们的功能大致正确。
问题 1:.frame 在做什么
这不是 MIPS 指令。它是描述此函数的(堆栈)帧的元数据:
$sp,它是 的别名$29。$16-$23。$31用于 MIPS 调用约定)有关 MIPS 调用约定的更多信息,请参阅此文档。
问题 2:为什么 lw $3,4($4) 而不是 lw $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)
Run Code Online (Sandbox Code Playgroud)
lui是立即加载上限。它将dest( 0xabcd)的地址的前 16 位加载到 的前 16 位$7。现在, 的值为$70xabcd0000。
addiu是添加立即未签名。它将dest( 0x1234)的地址的低 16 位与现有值相加$7得到 的新值$7。因此,$7现在持有 0xabcd0000 + 0x1234 = 0xabcd1234,即dest.
类似地,lw $3,%lo(source)($6)从 指向的地址$6(它已经保存了 的地址的前 16 位source)在偏移量%lo(source)(该地址的后 16 位)处加载。实际上,它会加载source.